Work on the radix code, added support to compile on OpenWRT,
[ipfw.git] / dummynet / ipfw2_mod.c
index 3bf836a..667d487 100644 (file)
@@ -49,6 +49,8 @@
 #include <sys/mbuf.h>                  /* sizeof struct mbuf */
 #include <sys/param.h>                 /* NGROUPS */
 
+#include "missing.h"
+
 #ifdef __linux__
 #include <linux/module.h>
 #include <linux/kernel.h>
 #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 */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
+#warning --- inet_hashtables not present on 2.4
+#include <linux/tcp.h>
+#include <net/route.h>
+#include <net/sock.h>
+static inline int inet_iif(const struct sk_buff *skb)
+{
+        return ((struct rtable *)skb->dst)->rt_iif;
+}
+
+#else
 #include <net/inet_hashtables.h>       /* inet_lookup */
+#endif
 #include <net/route.h>                 /* inet_iif */
 
 /*
  * Here we allocate some global variables used in the firewall.
  */
-ip_dn_ctl_t    *ip_dn_ctl_ptr;
+//ip_dn_ctl_t    *ip_dn_ctl_ptr;
+int (*ip_dn_ctl_ptr)(struct sockopt *);
+
 ip_fw_ctl_t    *ip_fw_ctl_ptr;
 
-ip_dn_io_t     *ip_dn_io_ptr;
+int    (*ip_dn_io_ptr)(struct mbuf **m, int dir, struct ip_fw_args *fwa);
 ip_fw_chk_t    *ip_fw_chk_ptr;
 
 void           (*bridge_dn_p)(struct mbuf *, struct ifnet *);
@@ -99,19 +115,6 @@ 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
@@ -156,7 +159,7 @@ fini_children(void)
                 mods[i].mod->evhand(NULL, MOD_UNLOAD, mods[i].mod->priv);
         }
 }
-/*--- end of module bindinghelper functions ---*/
+/*--- end of module binding helper functions ---*/
 
 /*---
  * Control hooks:
@@ -256,18 +259,18 @@ static struct nf_sockopt_ops ipfw_sockopts = {
  * - 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
+ *     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
+ *     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
@@ -282,13 +285,14 @@ static struct nf_sockopt_ops ipfw_sockopts = {
 #endif
 
 /*
- * ipfw hooks the POST_ROUTING and the PRE_ROUTING chain.
- * PlanetLab tags the xid in the LOCAL_INPUT and in the
- * POST_ROUTING chain, so if we want to intercept the
- * traffic by using the id we need to hook the LOCAL_INPUT
- * chain instead of the PRE_ROUTING.
+ * 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.
  */
-#ifdef IPFW_PLANETLAB
+#if 0 // defined(IPFW_PLANETLAB)
 #define IPFW_HOOK_IN NF_IP_LOCAL_IN
 #else
 #define IPFW_HOOK_IN NF_IP_PRE_ROUTING
@@ -461,22 +465,41 @@ ip_output(struct mbuf *m, struct mbuf __unused *opt,
  *
  * We do this only on selected protocols: TCP, ...
  *
- * Note- for locally generated, outgoing packets we don't need to
- * do a lookup because the sk_buff already points to the socket where
+ * 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 sk_buff *skb, int dir, struct bsd_ucred *u)
 {
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,0)
+       return -1;
+#else
        struct sock *sk;
        int ret = -1;   /* default return value */
-       int uid = -1;   /* user id */
        int st = -1;    /* state */
 
-       if (proto != IPPROTO_TCP)
+
+       if (proto != IPPROTO_TCP)       /* XXX extend for UDP */
                return -1;
 
        if ((dir ? (void *)skb->dst : (void *)skb->dev) == NULL) {
@@ -484,41 +507,55 @@ linux_lookup(const int proto, const __be32 saddr, const __be16 sport,
                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 (skb->sk) {
+               sk = skb->sk;
+       } else {
+               /*
+                * Try a lookup. On a match, sk has a refcount that we must
+                * release on exit (we know it because skb->sk = NULL).
+                *
+                * inet_lookup above 2.6.24 has an additional 'net' parameter
+                * so we use a macro to conditionally supply it.
+                * swap dst and src depending on the direction.
+                */
 #if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,24)
 #define _OPT_NET_ARG
-#else   /* 2.6.25 and above */
+#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
-
-       sk =  (dir) ?
-               inet_lookup(_OPT_NET_ARG &tcp_hashinfo,
-                       daddr, dport, saddr, sport,     // matches outgoing for server sockets
+#endif
+               sk =  (dir) ? /* dir != 0 on output */
+                   inet_lookup(_OPT_NET_ARG &tcp_hashinfo,
+                       daddr, dport, saddr, sport,     // match outgoing
                        inet_iif(skb)) :
-               inet_lookup(_OPT_NET_ARG &tcp_hashinfo,
-                       saddr, sport, daddr, dport,     // matches incoming for server sockets
+                   inet_lookup(_OPT_NET_ARG &tcp_hashinfo,
+                       saddr, sport, daddr, dport,     // match incoming
                        skb->dev->ifindex);
-
 #undef _OPT_NET_ARG
-       /* no match, nothing to be done */
-       if (sk == NULL)
-               return -1;
 
+               if (sk == NULL) /* no match, nothing to be done */
+                       return -1;
+       }
+       ret = 1;        /* retrying won't make things better */
+       st = sk->sk_state;
+#ifdef CONFIG_VSERVER
+       u->xid = sk->sk_xid;
+       u->nid = sk->sk_nid;
+#else
+       u->xid = u->nid = 0;
+#endif
        /*
-        * 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.
+        * Exclude tcp states where sk points to a inet_timewait_sock which
+        * has no sk_socket field (surely TCP_TIME_WAIT, perhaps more).
+        * To be safe, use a whitelist and not a blacklist.
+        * Before dereferencing sk_socket grab a lock on sk_callback_lock.
         *
         * Once again we need conditional code because the UID and GID
-        * location changes between the two kernels.
+        * location changes between kernels.
         */
 #if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,28)
 /* use the current's real uid/gid */
@@ -529,19 +566,31 @@ linux_lookup(const int proto, const __be32 saddr, const __be16 sport,
 #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;
-               ret = 1;
+
+#define GOOD_STATES (  \
+       (1<<TCP_LISTEN) | (1<<TCP_SYN_RECV)   | (1<<TCP_SYN_SENT)   | \
+       (1<<TCP_ESTABLISHED)  | (1<<TCP_FIN_WAIT1) | (1<<TCP_FIN_WAIT2) )
+       // surely exclude TCP_CLOSE, TCP_TIME_WAIT, TCP_LAST_ACK
+       // uncertain TCP_CLOSE_WAIT and TCP_CLOSING
+
+       if ((1<<st) & GOOD_STATES) {
+               read_lock_bh(&sk->sk_callback_lock);
+               if (sk->sk_socket && sk->sk_socket->file) {
+                       u->uid = sk->sk_socket->file->_CURR_UID;
+                       u->gid = sk->sk_socket->file->_CURR_GID;
+               }
+               read_unlock_bh(&sk->sk_callback_lock);
+       } else {
+               u->uid = u->gid = 0;
        }
-       sock_put(sk);
+       if (!skb->sk) /* return the reference that came from the lookup */
+               sock_put(sk);
+#undef GOOD_STATES
 #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;
+
+#endif /* LINUX > 2.4 */
 }
 
 /*
@@ -616,6 +665,7 @@ static struct nf_hook_ops ipfw_ops[] __read_mostly = {
 extern moduledata_t *moddesc_ipfw;
 extern moduledata_t *moddesc_dummynet;
 
+extern void rn_init(void);
 /*
  * Module glue - init and exit function.
  */
@@ -626,6 +676,8 @@ ipfw_module_init(void)
 
        printf("%s in-hook %d svn id %s\n", __FUNCTION__, IPFW_HOOK_IN, "$Id$");
 
+       rn_init();
+
        my_mod_register(moddesc_ipfw, "ipfw",  1);
        my_mod_register(moddesc_dummynet, "dummynet",  2);
        init_children();