netfilter: defrag: only register defrag functionality if needed
authorFlorian Westphal <fw@strlen.de>
Tue, 15 Nov 2016 20:36:45 +0000 (21:36 +0100)
committerPablo Neira Ayuso <pablo@netfilter.org>
Tue, 6 Dec 2016 20:42:00 +0000 (21:42 +0100)
nf_defrag modules for ipv4 and ipv6 export an empty stub function.
Any module that needs the defragmentation hooks registered simply 'calls'
this empty function to create a phony module dependency -- modprobe will
then load the defrag module too.

This extends netfilter ipv4/ipv6 defragmentation modules to delay the hook
registration until the functionality is requested within a network namespace
instead of module load time for all namespaces.

Hooks are only un-registered on module unload or when a namespace that used
such defrag functionality exits.

We have to use struct net for this as the register hooks can be called
before netns initialization here from the ipv4/ipv6 conntrack module
init path.

There is no unregister functionality support, defrag will always be
active once it was requested inside a net namespace.

The reason is that defrag has impact on nft and iptables rulesets
(without defrag we might see framents).

Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
include/net/netfilter/ipv4/nf_defrag_ipv4.h
include/net/netfilter/ipv6/nf_defrag_ipv6.h
include/net/netns/netfilter.h
net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c
net/ipv4/netfilter/nf_defrag_ipv4.c
net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
net/ipv6/netfilter/nf_defrag_ipv6_hooks.c
net/netfilter/xt_TPROXY.c
net/netfilter/xt_socket.c

index f01ef208dff6ec151f82eb2a33626fdae7edea42..db405f70e538b446837deb314e77d95dc5cd412a 100644 (file)
@@ -1,6 +1,7 @@
 #ifndef _NF_DEFRAG_IPV4_H
 #define _NF_DEFRAG_IPV4_H
 
-void nf_defrag_ipv4_enable(void);
+struct net;
+int nf_defrag_ipv4_enable(struct net *);
 
 #endif /* _NF_DEFRAG_IPV4_H */
index ddf162f7966fc091557a44de849ceb5c174eec10..7664efe379741e134de28934ac0ea0b72f147421 100644 (file)
@@ -1,7 +1,8 @@
 #ifndef _NF_DEFRAG_IPV6_H
 #define _NF_DEFRAG_IPV6_H
 
-void nf_defrag_ipv6_enable(void);
+struct net;
+int nf_defrag_ipv6_enable(struct net *);
 
 int nf_ct_frag6_init(void);
 void nf_ct_frag6_cleanup(void);
index 58487b1cc99ab09e9d5f00e1cc9608ac9db606e9..cea396b53a60d9a2e9cc849d55103c5c19d7cb37 100644 (file)
@@ -17,5 +17,11 @@ struct netns_nf {
        struct ctl_table_header *nf_log_dir_header;
 #endif
        struct nf_hook_entry __rcu *hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];
+#if IS_ENABLED(CONFIG_NF_DEFRAG_IPV4)
+       bool                    defrag_ipv4;
+#endif
+#if IS_ENABLED(CONFIG_NF_DEFRAG_IPV6)
+       bool                    defrag_ipv6;
+#endif
 };
 #endif
index 6f375443a74b5ccacfda8feee9c4f64f6dbafabb..fcfd071f470576281f425f7f3f145db12643e7eb 100644 (file)
@@ -325,6 +325,12 @@ static int ipv4_hooks_register(struct net *net)
        if (cnet->users > 1)
                goto out_unlock;
 
+       err = nf_defrag_ipv4_enable(net);
+       if (err) {
+               cnet->users = 0;
+               goto out_unlock;
+       }
+
        err = nf_register_net_hooks(net, ipv4_conntrack_ops,
                                    ARRAY_SIZE(ipv4_conntrack_ops));
 
@@ -422,7 +428,6 @@ static int __init nf_conntrack_l3proto_ipv4_init(void)
        int ret = 0;
 
        need_conntrack();
-       nf_defrag_ipv4_enable();
 
        ret = nf_register_sockopt(&so_getorigdst);
        if (ret < 0) {
index d88da36b383ccb4fa5b431b6c5cb83b218cb68ad..49bd6a54404f17559d31722b90bb4687b6c4abc8 100644 (file)
@@ -11,6 +11,7 @@
 #include <linux/netfilter.h>
 #include <linux/module.h>
 #include <linux/skbuff.h>
+#include <net/netns/generic.h>
 #include <net/route.h>
 #include <net/ip.h>
 
@@ -22,6 +23,8 @@
 #endif
 #include <net/netfilter/nf_conntrack_zones.h>
 
+static DEFINE_MUTEX(defrag4_mutex);
+
 static int nf_ct_ipv4_gather_frags(struct net *net, struct sk_buff *skb,
                                   u_int32_t user)
 {
@@ -102,18 +105,50 @@ static struct nf_hook_ops ipv4_defrag_ops[] = {
        },
 };
 
+static void __net_exit defrag4_net_exit(struct net *net)
+{
+       if (net->nf.defrag_ipv4) {
+               nf_unregister_net_hooks(net, ipv4_defrag_ops,
+                                       ARRAY_SIZE(ipv4_defrag_ops));
+               net->nf.defrag_ipv4 = false;
+       }
+}
+
+static struct pernet_operations defrag4_net_ops = {
+       .exit = defrag4_net_exit,
+};
+
 static int __init nf_defrag_init(void)
 {
-       return nf_register_hooks(ipv4_defrag_ops, ARRAY_SIZE(ipv4_defrag_ops));
+       return register_pernet_subsys(&defrag4_net_ops);
 }
 
 static void __exit nf_defrag_fini(void)
 {
-       nf_unregister_hooks(ipv4_defrag_ops, ARRAY_SIZE(ipv4_defrag_ops));
+       unregister_pernet_subsys(&defrag4_net_ops);
 }
 
-void nf_defrag_ipv4_enable(void)
+int nf_defrag_ipv4_enable(struct net *net)
 {
+       int err = 0;
+
+       might_sleep();
+
+       if (net->nf.defrag_ipv4)
+               return 0;
+
+       mutex_lock(&defrag4_mutex);
+       if (net->nf.defrag_ipv4)
+               goto out_unlock;
+
+       err = nf_register_net_hooks(net, ipv4_defrag_ops,
+                                   ARRAY_SIZE(ipv4_defrag_ops));
+       if (err == 0)
+               net->nf.defrag_ipv4 = true;
+
+ out_unlock:
+       mutex_unlock(&defrag4_mutex);
+       return err;
 }
 EXPORT_SYMBOL_GPL(nf_defrag_ipv4_enable);
 
index 72fe48075b7f45c654843bb6fe1914c6d8a36582..4e34024868334427194992d8033d0d58a0361d3b 100644 (file)
@@ -325,6 +325,12 @@ static int ipv6_hooks_register(struct net *net)
        if (cnet->users > 1)
                goto out_unlock;
 
+       err = nf_defrag_ipv6_enable(net);
+       if (err < 0) {
+               cnet->users = 0;
+               goto out_unlock;
+       }
+
        err = nf_register_net_hooks(net, ipv6_conntrack_ops,
                                    ARRAY_SIZE(ipv6_conntrack_ops));
        if (err)
@@ -427,7 +433,6 @@ static int __init nf_conntrack_l3proto_ipv6_init(void)
        int ret = 0;
 
        need_conntrack();
-       nf_defrag_ipv6_enable();
 
        ret = nf_register_sockopt(&so_getorigdst6);
        if (ret < 0) {
index f06b0471f39fc0130f9db69e6c05330e8d5c6909..8e0bdd0587871f03aa949b0eabc4d6ec9740b0cc 100644 (file)
@@ -30,6 +30,8 @@
 #include <net/netfilter/nf_conntrack_zones.h>
 #include <net/netfilter/ipv6/nf_defrag_ipv6.h>
 
+static DEFINE_MUTEX(defrag6_mutex);
+
 static enum ip6_defrag_users nf_ct6_defrag_user(unsigned int hooknum,
                                                struct sk_buff *skb)
 {
@@ -87,6 +89,19 @@ static struct nf_hook_ops ipv6_defrag_ops[] = {
        },
 };
 
+static void __net_exit defrag6_net_exit(struct net *net)
+{
+       if (net->nf.defrag_ipv6) {
+               nf_unregister_net_hooks(net, ipv6_defrag_ops,
+                                       ARRAY_SIZE(ipv6_defrag_ops));
+               net->nf.defrag_ipv6 = false;
+       }
+}
+
+static struct pernet_operations defrag6_net_ops = {
+       .exit = defrag6_net_exit,
+};
+
 static int __init nf_defrag_init(void)
 {
        int ret = 0;
@@ -96,9 +111,9 @@ static int __init nf_defrag_init(void)
                pr_err("nf_defrag_ipv6: can't initialize frag6.\n");
                return ret;
        }
-       ret = nf_register_hooks(ipv6_defrag_ops, ARRAY_SIZE(ipv6_defrag_ops));
+       ret = register_pernet_subsys(&defrag6_net_ops);
        if (ret < 0) {
-               pr_err("nf_defrag_ipv6: can't register hooks\n");
+               pr_err("nf_defrag_ipv6: can't register pernet ops\n");
                goto cleanup_frag6;
        }
        return ret;
@@ -111,12 +126,31 @@ cleanup_frag6:
 
 static void __exit nf_defrag_fini(void)
 {
-       nf_unregister_hooks(ipv6_defrag_ops, ARRAY_SIZE(ipv6_defrag_ops));
+       unregister_pernet_subsys(&defrag6_net_ops);
        nf_ct_frag6_cleanup();
 }
 
-void nf_defrag_ipv6_enable(void)
+int nf_defrag_ipv6_enable(struct net *net)
 {
+       int err = 0;
+
+       might_sleep();
+
+       if (net->nf.defrag_ipv6)
+               return 0;
+
+       mutex_lock(&defrag6_mutex);
+       if (net->nf.defrag_ipv6)
+               goto out_unlock;
+
+       err = nf_register_net_hooks(net, ipv6_defrag_ops,
+                                   ARRAY_SIZE(ipv6_defrag_ops));
+       if (err == 0)
+               net->nf.defrag_ipv6 = true;
+
+ out_unlock:
+       mutex_unlock(&defrag6_mutex);
+       return err;
 }
 EXPORT_SYMBOL_GPL(nf_defrag_ipv6_enable);
 
index dbd72cc40e42f74cfa2bc8d934399198dae45371..80cb7babeb6427d5768f9e636d5d9633d46f8413 100644 (file)
@@ -531,6 +531,11 @@ tproxy_tg6_v1(struct sk_buff *skb, const struct xt_action_param *par)
 static int tproxy_tg6_check(const struct xt_tgchk_param *par)
 {
        const struct ip6t_ip6 *i = par->entryinfo;
+       int err;
+
+       err = nf_defrag_ipv6_enable(par->net);
+       if (err)
+               return err;
 
        if ((i->proto == IPPROTO_TCP || i->proto == IPPROTO_UDP) &&
            !(i->invflags & IP6T_INV_PROTO))
@@ -545,6 +550,11 @@ static int tproxy_tg6_check(const struct xt_tgchk_param *par)
 static int tproxy_tg4_check(const struct xt_tgchk_param *par)
 {
        const struct ipt_ip *i = par->entryinfo;
+       int err;
+
+       err = nf_defrag_ipv4_enable(par->net);
+       if (err)
+               return err;
 
        if ((i->proto == IPPROTO_TCP || i->proto == IPPROTO_UDP)
            && !(i->invflags & IPT_INV_PROTO))
@@ -596,11 +606,6 @@ static struct xt_target tproxy_tg_reg[] __read_mostly = {
 
 static int __init tproxy_tg_init(void)
 {
-       nf_defrag_ipv4_enable();
-#ifdef XT_TPROXY_HAVE_IPV6
-       nf_defrag_ipv6_enable();
-#endif
-
        return xt_register_targets(tproxy_tg_reg, ARRAY_SIZE(tproxy_tg_reg));
 }
 
index 2198914707f58a0248fe0ff5df83294fb8b7d7a9..770bbec878f149f5688584982a552440ff5351fd 100644 (file)
@@ -147,9 +147,28 @@ socket_mt6_v1_v2_v3(const struct sk_buff *skb, struct xt_action_param *par)
 }
 #endif
 
+static int socket_mt_enable_defrag(struct net *net, int family)
+{
+       switch (family) {
+       case NFPROTO_IPV4:
+               return nf_defrag_ipv4_enable(net);
+#ifdef XT_SOCKET_HAVE_IPV6
+       case NFPROTO_IPV6:
+               return nf_defrag_ipv6_enable(net);
+#endif
+       }
+       WARN_ONCE(1, "Unknown family %d\n", family);
+       return 0;
+}
+
 static int socket_mt_v1_check(const struct xt_mtchk_param *par)
 {
        const struct xt_socket_mtinfo1 *info = (struct xt_socket_mtinfo1 *) par->matchinfo;
+       int err;
+
+       err = socket_mt_enable_defrag(par->net, par->family);
+       if (err)
+               return err;
 
        if (info->flags & ~XT_SOCKET_FLAGS_V1) {
                pr_info("unknown flags 0x%x\n", info->flags & ~XT_SOCKET_FLAGS_V1);
@@ -161,6 +180,11 @@ static int socket_mt_v1_check(const struct xt_mtchk_param *par)
 static int socket_mt_v2_check(const struct xt_mtchk_param *par)
 {
        const struct xt_socket_mtinfo2 *info = (struct xt_socket_mtinfo2 *) par->matchinfo;
+       int err;
+
+       err = socket_mt_enable_defrag(par->net, par->family);
+       if (err)
+               return err;
 
        if (info->flags & ~XT_SOCKET_FLAGS_V2) {
                pr_info("unknown flags 0x%x\n", info->flags & ~XT_SOCKET_FLAGS_V2);
@@ -173,7 +197,11 @@ static int socket_mt_v3_check(const struct xt_mtchk_param *par)
 {
        const struct xt_socket_mtinfo3 *info =
                                    (struct xt_socket_mtinfo3 *)par->matchinfo;
+       int err;
 
+       err = socket_mt_enable_defrag(par->net, par->family);
+       if (err)
+               return err;
        if (info->flags & ~XT_SOCKET_FLAGS_V3) {
                pr_info("unknown flags 0x%x\n",
                        info->flags & ~XT_SOCKET_FLAGS_V3);
@@ -268,11 +296,6 @@ static struct xt_match socket_mt_reg[] __read_mostly = {
 
 static int __init socket_mt_init(void)
 {
-       nf_defrag_ipv4_enable();
-#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
-       nf_defrag_ipv6_enable();
-#endif
-
        return xt_register_matches(socket_mt_reg, ARRAY_SIZE(socket_mt_reg));
 }