/* * Copyright (C) 2009 Luigi Rizzo, Marta Carbone, Universita` di Pisa * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * $Id$ * * The main interface to build ipfw+dummynet as a linux module. * (and possibly as a windows module as well, though that part * is not complete yet). * * The control interface uses the sockopt mechanism * on a socket(AF_INET, SOCK_RAW, IPPROTO_RAW). * * The data interface uses the netfilter interface, at the moment * hooked to the PRE_ROUTING and POST_ROUTING hooks. * Unfortunately the netfilter interface is a moving target, * so we need a set of macros to adapt to the various cases. * * In the netfilter hook we just mark packet as 'QUEUE' and then * let the queue handler to do the whole work (filtering and * possibly emulation). * As we receive packets, we wrap them with an mbuf descriptor * so the existing ipfw+dummynet code runs unmodified. */ #include #include /* sizeof struct mbuf */ #ifdef __linux__ #include #include #include #include /* NF_IP_PRI_FILTER */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25) #include /* nf_queue */ #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,17) #define __read_mostly #endif #endif /* !__linux__ */ #include /* in_addr */ #include /* ip_fw_ctl_t, ip_fw_chk_t */ #include /* ip_dn_ctl_t, ip_dn_io_t */ #include /* PFIL_IN, PFIL_OUT */ /* * Here we allocate some global variables used in the firewall. */ ip_dn_ctl_t *ip_dn_ctl_ptr; ip_fw_ctl_t *ip_fw_ctl_ptr; ip_dn_io_t *ip_dn_io_ptr; ip_fw_chk_t *ip_fw_chk_ptr; void (*bridge_dn_p)(struct mbuf *, struct ifnet *); /* * Glue code to implement the registration of children with the parent. * Each child should call my_mod_register() when linking, so that * module_init() and module_exit() can call init_children() and * fini_children() to provide the necessary initialization. */ #include struct mod_args { struct moduledata *mod; const char *name; int order; }; static unsigned int mod_idx; static struct mod_args mods[10]; /* hard limit to 10 modules */ /* * my_mod_register should be called automatically as the init * functions in the submodules. Unfortunately this compiler/linker * trick is not supported yet so we call it manually. */ int my_mod_register(struct moduledata *mod, const char *name, int order) { struct mod_args m = { mod, name, order }; printf("%s %s called\n", __FUNCTION__, name); if (mod_idx < sizeof(mods) / sizeof(mods[0])) mods[mod_idx++] = m; return 0; } static void init_children(void) { unsigned int i; /* Call the functions registered at init time. */ printf("%s mod_idx value %d\n", __FUNCTION__, mod_idx); for (i = 0; i < mod_idx; i++) { printf("+++ start module %d %s %s at %p order 0x%x\n", i, mods[i].name, mods[i].mod->name, mods[i].mod, mods[i].order); mods[i].mod->evhand(NULL, MOD_LOAD, mods[i].mod->priv); } } static void fini_children(void) { int i; /* Call the functions registered at init time. */ for (i = mod_idx - 1; i >= 0; i--) { printf("+++ end module %d %s %s at %p order 0x%x\n", i, mods[i].name, mods[i].mod->name, mods[i].mod, mods[i].order); mods[i].mod->evhand(NULL, MOD_UNLOAD, mods[i].mod->priv); } } /* end of module bindinghelper functions */ /* * Control hooks: * ipfw_ctl_h() is a wrapper for linux to FreeBSD sockopt call convention. * then call the ipfw handler in order to manage requests. * In turn this is called by the linux set/get handlers. */ static int ipfw_ctl_h(struct sockopt *s, int cmd, int dir, int len, void __user *user) { struct thread t; int ret = EINVAL; memset(s, 0, sizeof(s)); s->sopt_name = cmd; s->sopt_dir = dir; s->sopt_valsize = len; s->sopt_val = user; /* sopt_td is not used but it is referenced */ memset(&t, 0, sizeof(t)); s->sopt_td = &t; printf("%s called with cmd %d len %d\n", __FUNCTION__, cmd, len); if (cmd < IP_DUMMYNET_CONFIGURE && ip_fw_ctl_ptr) ret = ip_fw_ctl_ptr(s); else if (cmd >= IP_DUMMYNET_CONFIGURE && ip_dn_ctl_ptr) ret = ip_dn_ctl_ptr(s); return -ret; /* errors are < 0 on linux */ } #ifdef _WIN32 void netisr_dispatch(int __unused num, struct mbuf *m) { } int ip_output(struct mbuf *m, struct mbuf __unused *opt, struct route __unused *ro, int __unused flags, struct ip_moptions __unused *imo, struct inpcb __unused *inp) { netisr_dispatch(0, m); return 0; } #else /* this is the linux glue */ /* * setsockopt hook has no return value other than the error code. */ static int do_ipfw_set_ctl(struct sock __unused *sk, int cmd, void __user *user, unsigned int len) { struct sockopt s; /* pass arguments */ return ipfw_ctl_h(&s, cmd, SOPT_SET, len, user); } /* * getsockopt can can return a block of data in response. */ static int do_ipfw_get_ctl(struct sock __unused *sk, int cmd, void __user *user, int *len) { struct sockopt s; /* pass arguments */ int ret = ipfw_ctl_h(&s, cmd, SOPT_GET, *len, user); *len = s.sopt_valsize; /* return lenght back to the caller */ return ret; } /* * declare our [get|set]sockopt hooks */ static struct nf_sockopt_ops ipfw_sockopts = { .pf = PF_INET, .set_optmin = _IPFW_SOCKOPT_BASE, .set_optmax = _IPFW_SOCKOPT_END, .set = do_ipfw_set_ctl, .get_optmin = _IPFW_SOCKOPT_BASE, .get_optmax = _IPFW_SOCKOPT_END, .get = do_ipfw_get_ctl, #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) .owner = THIS_MODULE, #endif }; /* * declare hook to grab packets from the netfilter interface. * The NF_* names change in different versions of linux, in some * cases they are #defines, in others they are enum, so we * need to adapt. */ #ifndef NF_IP_PRE_ROUTING #define NF_IP_PRE_ROUTING NF_INET_PRE_ROUTING #endif #ifndef NF_IP_POST_ROUTING #define NF_IP_POST_ROUTING NF_INET_POST_ROUTING #endif /* * The main netfilter hook. * To make life simple, we queue everything and then do all the * decision in the queue handler. * * XXX note that in 2.4 the skbuf is passed as sk_buff** */ static unsigned int call_ipfw(unsigned int __unused hooknum, #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23) // in 2.6.22 we have ** struct sk_buff __unused **skb, #else struct sk_buff __unused *skb, #endif const struct net_device __unused *in, const struct net_device __unused *out, int __unused (*okfn)(struct sk_buff *)) { return NF_QUEUE; } #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) #define NF_STOP NF_ACCEPT #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25) #define nf_queue_entry nf_info /* for simplicity */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) /* unsure on the exact boundary */ /* on 2.4 we use nf_info */ #define QH_ARGS struct sk_buff *skb, struct nf_info *info, void *data #else /* 2.6.1.. 2.6.24 */ #define QH_ARGS struct sk_buff *skb, struct nf_info *info, unsigned int qnum, void *data #endif #define DEFINE_SKB /* nothing, already an argument */ #define REINJECT(_inf, _verd) nf_reinject(skb, _inf, _verd) #else /* 2.6.25 and above */ #define QH_ARGS struct nf_queue_entry *info, unsigned int queuenum #define DEFINE_SKB struct sk_buff *skb = info->skb; #define REINJECT(_inf, _verd) nf_reinject(_inf, _verd) #endif /* * used by dummynet when dropping packets * XXX use dummynet_send() */ void reinject_drop(struct mbuf* m) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25) /* unsure on the exact boundary */ struct sk_buff *skb = (struct sk_buff *)m; #endif REINJECT(m->queue_entry, NF_DROP); } /* * The real call to the firewall. nf_queue_entry points to the skbuf, * and eventually we need to return both through nf_reinject(). */ static int ipfw2_queue_handler(QH_ARGS) { DEFINE_SKB /* no semicolon here, goes in the macro */ int ret = 0; /* return value */ struct mbuf *m; #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) if (skb->nh.iph == NULL) { printf("null dp, len %d reinject now\n", skb->len); REINJECT(info, NF_ACCEPT); return 0; } #endif m = malloc(sizeof(*m), 0, 0); if (m == NULL) { printf("malloc fail, len %d reinject now\n", skb->len); REINJECT(info, NF_ACCEPT); return 0; } m->m_skb = skb; m->m_len = skb->len; /* len in this skbuf */ m->m_pkthdr.len = skb->len; /* total packet len */ m->m_pkthdr.rcvif = info->indev; m->queue_entry = info; #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) m->m_data = skb->nh.iph; #else m->m_data = skb_network_header(skb); #endif /* XXX add the interface */ if (info->hook == NF_IP_PRE_ROUTING) { ret = ipfw_check_in(NULL, &m, info->indev, PFIL_IN, NULL); } else { ret = ipfw_check_out(NULL, &m, info->outdev, PFIL_OUT, NULL); } if (m != NULL) { /* Accept. reinject and free the mbuf */ REINJECT(info, NF_STOP); m_freem(m); } else if (ret == 0) { /* dummynet has kept the packet, will reinject later. */ } else { /* * Packet dropped by ipfw or dummynet, reinject as NF_DROP * mbuf already released by ipfw itself */ REINJECT(info, NF_DROP); } return 0; } struct route; struct ip_moptions; struct inpcb; /* XXX should include prototypes for netisr_dispatch and ip_output */ /* * The reinjection routine after a packet comes out from dummynet. * We must update the skb timestamp so ping reports the right time. */ void netisr_dispatch(int num, struct mbuf *m) { struct nf_queue_entry *info = m->queue_entry; struct sk_buff *skb = m->m_skb; /* always used */ m_freem(m); KASSERT((info != NULL), ("%s info null!\n", __FUNCTION__)); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22) // XXX above 2.6.x ? __net_timestamp(skb); /* update timestamp */ #endif /* XXX to obey one-pass, possibly call the queue handler here */ REINJECT(info, ((num == -1)?NF_DROP:NF_STOP)); /* accept but no more firewall */ } int ip_output(struct mbuf *m, struct mbuf __unused *opt, struct route __unused *ro, int __unused flags, struct ip_moptions __unused *imo, struct inpcb __unused *inp) { netisr_dispatch(0, m); return 0; } /* * Now prepare to hook the various functions. * Linux 2.4 has a different API so we need some adaptation * for register and unregister hooks * * the unregister function changed arguments between 2.6.22 and 2.6.24 */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) static int nf_register_hooks(struct nf_hook_ops *ops, int n) { int i, ret = 0; for (i = 0; i < n; i++) { ret = nf_register_hook(ops + i); if (ret < 0) break; } return ret; } static void nf_unregister_hooks(struct nf_hook_ops *ops, int n) { int i; for (i = 0; i < n; i++) { nf_unregister_hook(ops + i); } } #define REG_QH_ARG(fn) fn, NULL /* argument for nf_[un]register_queue_handler */ #define UNREG_QH_ARG(fn) //fn /* argument for nf_[un]register_queue_handler */ #define SET_MOD_OWNER #else /* linux >= 2.6.0 */ struct nf_queue_handler ipfw2_queue_handler_desc = { .outfn = ipfw2_queue_handler, .name = "ipfw2 dummynet queue", }; #define REG_QH_ARG(fn) &(fn ## _desc) #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) #define UNREG_QH_ARG(fn) //fn /* argument for nf_[un]register_queue_handler */ #else #define UNREG_QH_ARG(fn) , &(fn ## _desc) #endif /* 2.6.0 < LINUX > 2.6.24 */ #define SET_MOD_OWNER .owner = THIS_MODULE, #endif /* !LINUX < 2.6.0 */ static struct nf_hook_ops ipfw_ops[] __read_mostly = { { .hook = call_ipfw, .pf = PF_INET, .hooknum = NF_IP_PRE_ROUTING, .priority = NF_IP_PRI_FILTER, SET_MOD_OWNER }, { .hook = call_ipfw, .pf = PF_INET, .hooknum = NF_IP_POST_ROUTING, .priority = NF_IP_PRI_FILTER, SET_MOD_OWNER }, }; #endif /* !__linux__ */ /* descriptors for the children */ extern moduledata_t *moddesc_ipfw; extern moduledata_t *moddesc_dummynet; /* * Module glue - init and exit function. */ static int __init ipfw_module_init(void) { int ret = 0; printf("%s called\n", __FUNCTION__); my_mod_register(moddesc_ipfw, "ipfw", 1); my_mod_register(moddesc_dummynet, "dummynet", 2); init_children(); #ifdef _WIN32 return ret; #else /* linux hook */ /* sockopt register, in order to talk with user space */ ret = nf_register_sockopt(&ipfw_sockopts); if (ret < 0) { printf("error %d in nf_register_sockopt\n", ret); goto clean_modules; } /* queue handler registration, in order to get network * packet under a private queue */ ret = nf_register_queue_handler(PF_INET, REG_QH_ARG(ipfw2_queue_handler) ); if (ret < 0) /* queue busy */ goto unregister_sockopt; ret = nf_register_hooks(ipfw_ops, ARRAY_SIZE(ipfw_ops)); if (ret < 0) goto unregister_sockopt; printf("%s loaded\n", __FUNCTION__); return 0; /* handle errors on load */ unregister_sockopt: nf_unregister_queue_handler(PF_INET UNREG_QH_ARG(ipfw2_queue_handler) ); nf_unregister_sockopt(&ipfw_sockopts); clean_modules: fini_children(); printf("%s error\n", __FUNCTION__); return ret; #endif /* linux */ } /* module shutdown */ static void __exit ipfw_module_exit(void) { #ifdef _WIN32 #else /* linux hook */ nf_unregister_hooks(ipfw_ops, ARRAY_SIZE(ipfw_ops)); /* maybe drain the queue before unregistering ? */ nf_unregister_queue_handler(PF_INET UNREG_QH_ARG(ipfw2_queue_handler) ); nf_unregister_sockopt(&ipfw_sockopts); #endif /* linux */ fini_children(); printf("%s unloaded\n", __FUNCTION__); } #ifdef __linux__ module_init(ipfw_module_init) module_exit(ipfw_module_exit) MODULE_LICENSE("GPL"); /* mandatory */ #endif