Major changes:
[ipfw.git] / dummynet / ipfw2_mod.c
index ff5a92f..b072ab5 100644 (file)
@@ -47,6 +47,7 @@
 
 #include <sys/cdefs.h>
 #include <sys/mbuf.h>                  /* sizeof struct mbuf */
+#include <sys/param.h>                 /* NGROUPS */
 
 #ifdef __linux__
 #include <linux/module.h>
@@ -67,7 +68,9 @@
 #include <netinet/in.h>                        /* in_addr */
 #include <netinet/ip_fw.h>             /* ip_fw_ctl_t, ip_fw_chk_t */
 #include <netinet/ip_dummynet.h>       /* ip_dn_ctl_t, ip_dn_io_t */
-#include <net/pfil.h>  /* PFIL_IN, PFIL_OUT */
+#include <net/pfil.h>                  /* PFIL_IN, PFIL_OUT */
+#include <net/inet_hashtables.h>       /* inet_lookup */
+#include <net/route.h>                 /* inet_iif */
 
 /*
  * Here we allocate some global variables used in the firewall.
@@ -80,7 +83,7 @@ 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
@@ -96,6 +99,19 @@ struct mod_args {
 static unsigned int mod_idx;
 static struct mod_args mods[10];       /* hard limit to 10 modules */
 
+/*
+ * Data structure to cache our ucred related
+ * information. This structure only gets used if
+ * the user specified UID/GID based constraints in
+ * a firewall rule.
+ */
+struct ip_fw_ugid {
+       gid_t           fw_groups[NGROUPS];
+       int             fw_ngroups;
+       uid_t           fw_uid;
+       int             fw_prid;
+};
+
 /*
  * my_mod_register should be called automatically as the init
  * functions in the submodules. Unfortunately this compiler/linker
@@ -140,9 +156,9 @@ fini_children(void)
                 mods[i].mod->evhand(NULL, MOD_UNLOAD, mods[i].mod->priv);
         }
 }
-/* end of module bindinghelper functions */
+/*--- 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.
@@ -164,7 +180,7 @@ ipfw_ctl_h(struct sockopt *s, int cmd, int dir, int len, void __user *user)
        memset(&t, 0, sizeof(t));
        s->sopt_td = &t;
        
-       printf("%s called with cmd %d len %d\n", __FUNCTION__, cmd, len);
+       // 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);
@@ -233,6 +249,25 @@ static struct nf_sockopt_ops ipfw_sockopts = {
 #endif
 };
 
+/*----
+ * We need a number of macros to adapt to the various APIs in
+ * different linux versions. Among them:
+ *
+ * - the hook names change between macros (NF_IP*) and enum NF_INET_*
+ *
+ * - the second argument to the netfilter hook is
+ *     struct sk_buff ** in kernels <= 2.6.22
+ *     struct sk_buff * in kernels > 2.6.22
+ *
+ * - NF_STOP is not defined before 2.6 so we remap it to NF_ACCEPT
+ *
+ * - the packet descriptor passed to the queue handler is
+ *     struct nf_info          in kernels <= 2.6.24
+ *     struct nf_queue_entry   in kernels <= 2.6.24
+ *
+ * - the arguments to the queue handler also change;
+ */
 /*
  * declare hook to grab packets from the netfilter interface.
  * The NF_* names change in different versions of linux, in some
@@ -246,12 +281,27 @@ static struct nf_sockopt_ops ipfw_sockopts = {
 #define NF_IP_POST_ROUTING     NF_INET_POST_ROUTING
 #endif
 
+/*
+ * ipfw hooks into the POST_ROUTING and the PRE_ROUTING chains.
+ * PlanetLab sets skb_tag to the slice id in the LOCAL_INPUT and
+ * POST_ROUTING chains, so if we want to use that information we
+ * need to hook the LOCAL_INPUT chain instead of the PRE_ROUTING.
+ * However at the moment the skb_tag info is not reliable so
+ * we stay with the standard hooks.
+ */
+#if 0 // defined(IPFW_PLANETLAB)
+#define IPFW_HOOK_IN NF_IP_LOCAL_IN
+#else
+#define IPFW_HOOK_IN NF_IP_PRE_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**
+ * XXX note that in 2.4 and up to 2.6.22 the skbuf is passed as sk_buff**
+ * so we have an #ifdef to set the proper argument type.
  */
 static unsigned int
 call_ipfw(unsigned int __unused hooknum,
@@ -273,7 +323,13 @@ call_ipfw(unsigned int __unused hooknum,
 
 #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
 
+/*
+ * nf_queue_entry is a recent addition, in previous versions
+ * of the code the struct is called nf_info.
+ */
 #define nf_queue_entry nf_info /* for simplicity */
+
+/* also, 2.4 and perhaps something else have different arguments */
 #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
@@ -339,8 +395,9 @@ ipfw2_queue_handler(QH_ARGS)
 #else
        m->m_data = skb_network_header(skb);
 #endif
+
        /* XXX add the interface */
-       if (info->hook == NF_IP_PRE_ROUTING) {
+       if (info->hook == IPFW_HOOK_IN) {
                ret = ipfw_check_in(NULL, &m, info->indev, PFIL_IN, NULL);
        } else {
                ret = ipfw_check_out(NULL, &m, info->outdev, PFIL_OUT, NULL);
@@ -348,7 +405,7 @@ ipfw2_queue_handler(QH_ARGS)
 
        if (m != NULL) {        /* Accept. reinject and free the mbuf */
                REINJECT(info, NF_STOP);
-               free(m, M_IPFW);
+               m_freem(m);
        } else if (ret == 0) {
                /* dummynet has kept the packet, will reinject later. */
        } else {
@@ -377,7 +434,8 @@ netisr_dispatch(int num, struct mbuf *m)
        struct nf_queue_entry *info = m->queue_entry;
        struct sk_buff *skb = m->m_skb; /* always used */
 
-       free(m, M_IPFW);
+       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 */
@@ -396,6 +454,127 @@ ip_output(struct mbuf *m, struct mbuf __unused *opt,
         return 0;
 }
 
+/*
+ * socket lookup function for linux.
+ * This code is used to associate uid, gid, jail/xid to packets,
+ * and store the info in a cache *ugp where they can be accessed quickly.
+ * The function returns 1 if the info is found, -1 otherwise.
+ *
+ * We do this only on selected protocols: TCP, ...
+ *
+ * The chain is the following
+ *   sk_buff*  sock*  socket*    file*
+ *     skb  ->  sk ->sk_socket->file ->f_owner    ->pid
+ *     skb  ->  sk ->sk_socket->file ->f_uid (direct)
+ *     skb  ->  sk ->sk_socket->file ->f_cred->fsuid (2.6.29+)
+ *
+ * Related headers:
+ * linux/skbuff.h      struct skbuff
+ * net/sock.h          struct sock
+ * linux/net.h         struct socket
+ * linux/fs.h          struct file
+ *
+ * With vserver we may have sk->sk_xid and sk->sk_nid that
+ * which we store in fw_groups[1] (matches O_JAIL) and fw_groups[2]
+ * (no matches yet)
+ *
+ * Note- for locally generated, outgoing packets we should not need
+ * need a lookup because the sk_buff already points to the socket where
+ * the info is.
+ */
+extern struct inet_hashinfo tcp_hashinfo;
+int
+linux_lookup(const int proto, const __be32 saddr, const __be16 sport,
+               const __be32 daddr, const __be16 dport,
+               struct sk_buff *skb, int dir, struct ip_fw_ugid *ugp)
+{
+       struct sock *sk;
+       int ret = -1;   /* default return value */
+       int uid = -1;   /* user id */
+       int st = -1;    /* state */
+
+       if (proto != IPPROTO_TCP)
+               return -1;
+
+       if ((dir ? (void *)skb->dst : (void *)skb->dev) == NULL) {
+               panic(" -- this should not happen\n");
+               return -1;
+       }
+
+       /*
+        * inet_lookup above 2.6.24 has an additional 'net' parameter
+        * so we use a macro to conditionally supply it.
+        * Also we need to switch dst and src depending on the direction.
+        */
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,24)
+#define _OPT_NET_ARG
+#else
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
+/* there is no dev_net() on 2.6.25 */
+#define _OPT_NET_ARG (skb->dev->nd_net),
+#else  /* 2.6.26 and above */
+#define _OPT_NET_ARG dev_net(skb->dev),
+#endif
+#endif
+
+       if (0 && skb->sk) {
+               sk=skb->sk;
+       } else {
+       sk =  (dir) ?
+               inet_lookup(_OPT_NET_ARG &tcp_hashinfo,
+                       daddr, dport, saddr, sport,     // matches outgoing for server sockets
+                       inet_iif(skb)) :
+               inet_lookup(_OPT_NET_ARG &tcp_hashinfo,
+                       saddr, sport, daddr, dport,     // matches incoming for server sockets
+                       skb->dev->ifindex);
+       }
+
+#undef _OPT_NET_ARG
+       /* no match, nothing to be done */
+       if (sk == NULL)
+               return -1;
+
+       /*
+        * On a match, sk is returned with a refcount.
+        * In tcp some states reference a valid struct sock
+        * which is what we want, otherwise the struct sock
+        * referenced can be invalid, as in the case of the
+        * TCP_TIME_WAIT state, when it references a
+        * struct inet_timewait_sock which does not point to credentials.
+        * To be safe we exclude TCP_CLOSE and TCP_LAST_ACK states too.
+        *
+        * Once again we need conditional code because the UID and GID
+        * location changes between the two kernels.
+        */
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,28)
+/* use the current's real uid/gid */
+#define _CURR_UID f_uid
+#define _CURR_GID f_gid
+#else /* 2.6.29 and above */
+/* use the current's file access real uid/gid */
+#define _CURR_UID f_cred->fsuid
+#define _CURR_GID f_cred->fsgid
+#endif
+       st = sk->sk_state;
+       if (st != TCP_TIME_WAIT && st != TCP_CLOSE && st != TCP_LAST_ACK &&
+                       sk->sk_socket && sk->sk_socket->file) {
+               ugp->fw_uid = sk->sk_socket->file->_CURR_UID;
+               uid = ugp->fw_uid;
+               ugp->fw_groups[0] = sk->sk_socket->file->_CURR_GID;
+#ifdef CONFIG_VSERVER
+               ugp->fw_groups[1] = sk->sk_xid;
+               ugp->fw_groups[2] = sk->sk_nid;
+#endif
+               ret = 1;
+       }
+       if (1 && !skb->sk) /* the reference came from the lookup */
+               sock_put(sk);
+#undef _CURR_UID
+#undef _CURR_GID
+
+       //printf("%s dir %d sb>dst %p sb>dev %p ret %d id %d st%d\n", __FUNCTION__, dir, skb->dst, skb->dev, ret, uid, st);
+       return ret;
+}
 
 /*
  * Now prepare to hook the various functions.
@@ -451,7 +630,7 @@ static struct nf_hook_ops ipfw_ops[] __read_mostly = {
         {
                 .hook           = call_ipfw,
                 .pf             = PF_INET,
-                .hooknum        = NF_IP_PRE_ROUTING,
+                .hooknum        = IPFW_HOOK_IN,
                 .priority       = NF_IP_PRI_FILTER,
                 SET_MOD_OWNER
         },
@@ -477,7 +656,7 @@ ipfw_module_init(void)
 {
        int ret = 0;
 
-       printf("%s called\n", __FUNCTION__);
+       printf("%s in-hook %d svn id %s\n", __FUNCTION__, IPFW_HOOK_IN, "$Id$");
 
        my_mod_register(moddesc_ipfw, "ipfw",  1);
        my_mod_register(moddesc_dummynet, "dummynet",  2);
@@ -541,5 +720,5 @@ ipfw_module_exit(void)
 #ifdef __linux__
 module_init(ipfw_module_init)
 module_exit(ipfw_module_exit)
-MODULE_LICENSE("GPL"); /* mandatory */
+MODULE_LICENSE("Dual BSD/GPL"); /* the code here is all BSD. */
 #endif