netfilter: nf_conntrack: use per-conntrack locks for protocol data
[linux-2.6.git] / net / netfilter / nf_conntrack_proto_gre.c
index bdbead8..175a28c 100644 (file)
 #include <linux/list.h>
 #include <linux/seq_file.h>
 #include <linux/in.h>
+#include <linux/netdevice.h>
 #include <linux/skbuff.h>
-
+#include <net/dst.h>
+#include <net/net_namespace.h>
+#include <net/netns/generic.h>
 #include <net/netfilter/nf_conntrack_l4proto.h>
 #include <net/netfilter/nf_conntrack_helper.h>
 #include <net/netfilter/nf_conntrack_core.h>
 #define GRE_TIMEOUT            (30 * HZ)
 #define GRE_STREAM_TIMEOUT     (180 * HZ)
 
-static DEFINE_RWLOCK(nf_ct_gre_lock);
-static LIST_HEAD(gre_keymap_list);
+static int proto_gre_net_id;
+struct netns_proto_gre {
+       rwlock_t                keymap_lock;
+       struct list_head        keymap_list;
+};
 
-void nf_ct_gre_keymap_flush(void)
+void nf_ct_gre_keymap_flush(struct net *net)
 {
-       struct list_head *pos, *n;
+       struct netns_proto_gre *net_gre = net_generic(net, proto_gre_net_id);
+       struct nf_ct_gre_keymap *km, *tmp;
 
-       write_lock_bh(&nf_ct_gre_lock);
-       list_for_each_safe(pos, n, &gre_keymap_list) {
-               list_del(pos);
-               kfree(pos);
+       write_lock_bh(&net_gre->keymap_lock);
+       list_for_each_entry_safe(km, tmp, &net_gre->keymap_list, list) {
+               list_del(&km->list);
+               kfree(km);
        }
-       write_unlock_bh(&nf_ct_gre_lock);
+       write_unlock_bh(&net_gre->keymap_lock);
 }
 EXPORT_SYMBOL(nf_ct_gre_keymap_flush);
 
@@ -67,22 +74,23 @@ static inline int gre_key_cmpfn(const struct nf_ct_gre_keymap *km,
 }
 
 /* look up the source key for a given tuple */
-static __be16 gre_keymap_lookup(struct nf_conntrack_tuple *t)
+static __be16 gre_keymap_lookup(struct net *net, struct nf_conntrack_tuple *t)
 {
+       struct netns_proto_gre *net_gre = net_generic(net, proto_gre_net_id);
        struct nf_ct_gre_keymap *km;
        __be16 key = 0;
 
-       read_lock_bh(&nf_ct_gre_lock);
-       list_for_each_entry(km, &gre_keymap_list, list) {
+       read_lock_bh(&net_gre->keymap_lock);
+       list_for_each_entry(km, &net_gre->keymap_list, list) {
                if (gre_key_cmpfn(km, t)) {
                        key = km->tuple.src.u.gre.key;
                        break;
                }
        }
-       read_unlock_bh(&nf_ct_gre_lock);
+       read_unlock_bh(&net_gre->keymap_lock);
 
        pr_debug("lookup src key 0x%x for ", key);
-       NF_CT_DUMP_TUPLE(t);
+       nf_ct_dump_tuple(t);
 
        return key;
 }
@@ -91,16 +99,22 @@ static __be16 gre_keymap_lookup(struct nf_conntrack_tuple *t)
 int nf_ct_gre_keymap_add(struct nf_conn *ct, enum ip_conntrack_dir dir,
                         struct nf_conntrack_tuple *t)
 {
+       struct net *net = nf_ct_net(ct);
+       struct netns_proto_gre *net_gre = net_generic(net, proto_gre_net_id);
        struct nf_conn_help *help = nfct_help(ct);
        struct nf_ct_gre_keymap **kmp, *km;
 
        kmp = &help->help.ct_pptp_info.keymap[dir];
        if (*kmp) {
                /* check whether it's a retransmission */
-               list_for_each_entry(km, &gre_keymap_list, list) {
-                       if (gre_key_cmpfn(km, t) && km == *kmp)
+               read_lock_bh(&net_gre->keymap_lock);
+               list_for_each_entry(km, &net_gre->keymap_list, list) {
+                       if (gre_key_cmpfn(km, t) && km == *kmp) {
+                               read_unlock_bh(&net_gre->keymap_lock);
                                return 0;
+                       }
                }
+               read_unlock_bh(&net_gre->keymap_lock);
                pr_debug("trying to override keymap_%s for ct %p\n",
                         dir == IP_CT_DIR_REPLY ? "reply" : "orig", ct);
                return -EEXIST;
@@ -113,11 +127,11 @@ int nf_ct_gre_keymap_add(struct nf_conn *ct, enum ip_conntrack_dir dir,
        *kmp = km;
 
        pr_debug("adding new entry %p: ", km);
-       NF_CT_DUMP_TUPLE(&km->tuple);
+       nf_ct_dump_tuple(&km->tuple);
 
-       write_lock_bh(&nf_ct_gre_lock);
-       list_add_tail(&km->list, &gre_keymap_list);
-       write_unlock_bh(&nf_ct_gre_lock);
+       write_lock_bh(&net_gre->keymap_lock);
+       list_add_tail(&km->list, &net_gre->keymap_list);
+       write_unlock_bh(&net_gre->keymap_lock);
 
        return 0;
 }
@@ -126,12 +140,14 @@ EXPORT_SYMBOL_GPL(nf_ct_gre_keymap_add);
 /* destroy the keymap entries associated with specified master ct */
 void nf_ct_gre_keymap_destroy(struct nf_conn *ct)
 {
+       struct net *net = nf_ct_net(ct);
+       struct netns_proto_gre *net_gre = net_generic(net, proto_gre_net_id);
        struct nf_conn_help *help = nfct_help(ct);
        enum ip_conntrack_dir dir;
 
        pr_debug("entering for ct %p\n", ct);
 
-       write_lock_bh(&nf_ct_gre_lock);
+       write_lock_bh(&net_gre->keymap_lock);
        for (dir = IP_CT_DIR_ORIGINAL; dir < IP_CT_DIR_MAX; dir++) {
                if (help->help.ct_pptp_info.keymap[dir]) {
                        pr_debug("removing %p from list\n",
@@ -141,29 +157,31 @@ void nf_ct_gre_keymap_destroy(struct nf_conn *ct)
                        help->help.ct_pptp_info.keymap[dir] = NULL;
                }
        }
-       write_unlock_bh(&nf_ct_gre_lock);
+       write_unlock_bh(&net_gre->keymap_lock);
 }
 EXPORT_SYMBOL_GPL(nf_ct_gre_keymap_destroy);
 
 /* PUBLIC CONNTRACK PROTO HELPER FUNCTIONS */
 
 /* invert gre part of tuple */
-static int gre_invert_tuple(struct nf_conntrack_tuple *tuple,
-                           const struct nf_conntrack_tuple *orig)
+static bool gre_invert_tuple(struct nf_conntrack_tuple *tuple,
+                            const struct nf_conntrack_tuple *orig)
 {
        tuple->dst.u.gre.key = orig->src.u.gre.key;
        tuple->src.u.gre.key = orig->dst.u.gre.key;
-       return 1;
+       return true;
 }
 
 /* gre hdr info to tuple */
-static int gre_pkt_to_tuple(const struct sk_buff *skb,
-                          unsigned int dataoff,
-                          struct nf_conntrack_tuple *tuple)
+static bool gre_pkt_to_tuple(const struct sk_buff *skb, unsigned int dataoff,
+                            struct nf_conntrack_tuple *tuple)
 {
-       struct gre_hdr_pptp _pgrehdr, *pgrehdr;
+       struct net *net = dev_net(skb->dev ? skb->dev : skb->dst->dev);
+       const struct gre_hdr_pptp *pgrehdr;
+       struct gre_hdr_pptp _pgrehdr;
        __be16 srckey;
-       struct gre_hdr _grehdr, *grehdr;
+       const struct gre_hdr *grehdr;
+       struct gre_hdr _grehdr;
 
        /* first only delinearize old RFC1701 GRE header */
        grehdr = skb_header_pointer(skb, dataoff, sizeof(_grehdr), &_grehdr);
@@ -171,24 +189,24 @@ static int gre_pkt_to_tuple(const struct sk_buff *skb,
                /* try to behave like "nf_conntrack_proto_generic" */
                tuple->src.u.all = 0;
                tuple->dst.u.all = 0;
-               return 1;
+               return true;
        }
 
        /* PPTP header is variable length, only need up to the call_id field */
        pgrehdr = skb_header_pointer(skb, dataoff, 8, &_pgrehdr);
        if (!pgrehdr)
-               return 1;
+               return true;
 
        if (ntohs(grehdr->protocol) != GRE_PROTOCOL_PPTP) {
                pr_debug("GRE_VERSION_PPTP but unknown proto\n");
-               return 0;
+               return false;
        }
 
        tuple->dst.u.gre.key = pgrehdr->call_id;
-       srckey = gre_keymap_lookup(tuple);
+       srckey = gre_keymap_lookup(net, tuple);
        tuple->src.u.gre.key = srckey;
 
-       return 1;
+       return true;
 }
 
 /* print gre part of tuple */
@@ -201,8 +219,7 @@ static int gre_print_tuple(struct seq_file *s,
 }
 
 /* print private data for conntrack */
-static int gre_print_conntrack(struct seq_file *s,
-                              const struct nf_conn *ct)
+static int gre_print_conntrack(struct seq_file *s, struct nf_conn *ct)
 {
        return seq_printf(s, "timeout=%u, stream_timeout=%u ",
                          (ct->proto.gre.timeout / HZ),
@@ -214,7 +231,7 @@ static int gre_packet(struct nf_conn *ct,
                      const struct sk_buff *skb,
                      unsigned int dataoff,
                      enum ip_conntrack_info ctinfo,
-                     int pf,
+                     u_int8_t pf,
                      unsigned int hooknum)
 {
        /* If we've seen traffic both ways, this is a GRE connection.
@@ -224,7 +241,7 @@ static int gre_packet(struct nf_conn *ct,
                                   ct->proto.gre.stream_timeout);
                /* Also, more likely to be important, and not a probe. */
                set_bit(IPS_ASSURED_BIT, &ct->status);
-               nf_conntrack_event_cache(IPCT_STATUS, skb);
+               nf_conntrack_event_cache(IPCT_STATUS, ct);
        } else
                nf_ct_refresh_acct(ct, ctinfo, skb,
                                   ct->proto.gre.timeout);
@@ -233,18 +250,18 @@ static int gre_packet(struct nf_conn *ct,
 }
 
 /* Called when a new connection for this protocol found. */
-static int gre_new(struct nf_conn *ct, const struct sk_buff *skb,
-                  unsigned int dataoff)
+static bool gre_new(struct nf_conn *ct, const struct sk_buff *skb,
+                   unsigned int dataoff)
 {
        pr_debug(": ");
-       NF_CT_DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
+       nf_ct_dump_tuple(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
 
        /* initialize to sane value.  Ideally a conntrack helper
         * (e.g. in case of pptp) is increasing them */
        ct->proto.gre.stream_timeout = GRE_STREAM_TIMEOUT;
        ct->proto.gre.timeout = GRE_TIMEOUT;
 
-       return 1;
+       return true;
 }
 
 /* Called when a conntrack entry has already been removed from the hashes
@@ -274,20 +291,60 @@ static struct nf_conntrack_l4proto nf_conntrack_l4proto_gre4 __read_mostly = {
        .destroy         = gre_destroy,
        .me              = THIS_MODULE,
 #if defined(CONFIG_NF_CT_NETLINK) || defined(CONFIG_NF_CT_NETLINK_MODULE)
-       .tuple_to_nfattr = nf_ct_port_tuple_to_nfattr,
-       .nfattr_to_tuple = nf_ct_port_nfattr_to_tuple,
+       .tuple_to_nlattr = nf_ct_port_tuple_to_nlattr,
+       .nlattr_tuple_size = nf_ct_port_nlattr_tuple_size,
+       .nlattr_to_tuple = nf_ct_port_nlattr_to_tuple,
+       .nla_policy      = nf_ct_port_nla_policy,
 #endif
 };
 
+static int proto_gre_net_init(struct net *net)
+{
+       struct netns_proto_gre *net_gre;
+       int rv;
+
+       net_gre = kmalloc(sizeof(struct netns_proto_gre), GFP_KERNEL);
+       if (!net_gre)
+               return -ENOMEM;
+       rwlock_init(&net_gre->keymap_lock);
+       INIT_LIST_HEAD(&net_gre->keymap_list);
+
+       rv = net_assign_generic(net, proto_gre_net_id, net_gre);
+       if (rv < 0)
+               kfree(net_gre);
+       return rv;
+}
+
+static void proto_gre_net_exit(struct net *net)
+{
+       struct netns_proto_gre *net_gre = net_generic(net, proto_gre_net_id);
+
+       nf_ct_gre_keymap_flush(net);
+       kfree(net_gre);
+}
+
+static struct pernet_operations proto_gre_net_ops = {
+       .init = proto_gre_net_init,
+       .exit = proto_gre_net_exit,
+};
+
 static int __init nf_ct_proto_gre_init(void)
 {
-       return nf_conntrack_l4proto_register(&nf_conntrack_l4proto_gre4);
+       int rv;
+
+       rv = nf_conntrack_l4proto_register(&nf_conntrack_l4proto_gre4);
+       if (rv < 0)
+               return rv;
+       rv = register_pernet_gen_subsys(&proto_gre_net_id, &proto_gre_net_ops);
+       if (rv < 0)
+               nf_conntrack_l4proto_unregister(&nf_conntrack_l4proto_gre4);
+       return rv;
 }
 
-static void nf_ct_proto_gre_fini(void)
+static void __exit nf_ct_proto_gre_fini(void)
 {
        nf_conntrack_l4proto_unregister(&nf_conntrack_l4proto_gre4);
-       nf_ct_gre_keymap_flush();
+       unregister_pernet_gen_subsys(proto_gre_net_id, &proto_gre_net_ops);
 }
 
 module_init(nf_ct_proto_gre_init);