netfilter: core: only allow one nat hook per hook point
authorFlorian Westphal <fw@strlen.de>
Fri, 8 Dec 2017 16:01:54 +0000 (17:01 +0100)
committerPablo Neira Ayuso <pablo@netfilter.org>
Mon, 8 Jan 2018 17:01:13 +0000 (18:01 +0100)
The netfilter NAT core cannot deal with more than one NAT hook per hook
location (prerouting, input ...), because the NAT hooks install a NAT null
binding in case the iptables nat table (iptable_nat hooks) or the
corresponding nftables chain (nft nat hooks) doesn't specify a nat
transformation.

Null bindings are needed to detect port collsisions between NAT-ed and
non-NAT-ed connections.

This causes nftables NAT rules to not work when iptable_nat module is
loaded, and vice versa because nat binding has already been attached
when the second nat hook is consulted.

The netfilter core is not really the correct location to handle this
(hooks are just hooks, the core has no notion of what kinds of side
 effects a hook implements), but its the only place where we can check
for conflicts between both iptables hooks and nftables hooks without
adding dependencies.

So add nat annotation to hook_ops to describe those hooks that will
add NAT bindings and then make core reject if such a hook already exists.
The annotation fills a padding hole, in case further restrictions appar
we might change this to a 'u8 type' instead of bool.

iptables error if nft nat hook active:
iptables -t nat -A POSTROUTING -j MASQUERADE
iptables v1.4.21: can't initialize iptables table `nat': File exists
Perhaps iptables or your kernel needs to be upgraded.

nftables error if iptables nat table present:
nft -f /etc/nftables/ipv4-nat
/usr/etc/nftables/ipv4-nat:3:1-2: Error: Could not process rule: File exists
table nat {
^^

Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
include/linux/netfilter.h
net/ipv4/netfilter/iptable_nat.c
net/ipv6/netfilter/ip6table_nat.c
net/netfilter/core.c
net/netfilter/nf_tables_api.c

index ee7a9cbd8d8120f45eb51c4ac3ea628f592495c3..85a0b0d599e62883515b4a0db0248017dfc8a6c5 100644 (file)
@@ -67,6 +67,7 @@ struct nf_hook_ops {
        struct net_device       *dev;
        void                    *priv;
        u_int8_t                pf;
+       bool                    nat_hook;
        unsigned int            hooknum;
        /* Hooks are ordered in ascending priority. */
        int                     priority;
index a1a07b338ccfd5468b4fe80557af693b0e4f8861..0f7255cc65ee14bc0f028e3d2bc793c33bcc57f8 100644 (file)
@@ -72,6 +72,7 @@ static const struct nf_hook_ops nf_nat_ipv4_ops[] = {
        {
                .hook           = iptable_nat_ipv4_in,
                .pf             = NFPROTO_IPV4,
+               .nat_hook       = true,
                .hooknum        = NF_INET_PRE_ROUTING,
                .priority       = NF_IP_PRI_NAT_DST,
        },
@@ -79,6 +80,7 @@ static const struct nf_hook_ops nf_nat_ipv4_ops[] = {
        {
                .hook           = iptable_nat_ipv4_out,
                .pf             = NFPROTO_IPV4,
+               .nat_hook       = true,
                .hooknum        = NF_INET_POST_ROUTING,
                .priority       = NF_IP_PRI_NAT_SRC,
        },
@@ -86,6 +88,7 @@ static const struct nf_hook_ops nf_nat_ipv4_ops[] = {
        {
                .hook           = iptable_nat_ipv4_local_fn,
                .pf             = NFPROTO_IPV4,
+               .nat_hook       = true,
                .hooknum        = NF_INET_LOCAL_OUT,
                .priority       = NF_IP_PRI_NAT_DST,
        },
@@ -93,6 +96,7 @@ static const struct nf_hook_ops nf_nat_ipv4_ops[] = {
        {
                .hook           = iptable_nat_ipv4_fn,
                .pf             = NFPROTO_IPV4,
+               .nat_hook       = true,
                .hooknum        = NF_INET_LOCAL_IN,
                .priority       = NF_IP_PRI_NAT_SRC,
        },
index 991512576c8c8601b54a41d4c7f417500fa5f53d..47306e45a80abf5225aab9c1209258302c63c161 100644 (file)
@@ -74,6 +74,7 @@ static const struct nf_hook_ops nf_nat_ipv6_ops[] = {
        {
                .hook           = ip6table_nat_in,
                .pf             = NFPROTO_IPV6,
+               .nat_hook       = true,
                .hooknum        = NF_INET_PRE_ROUTING,
                .priority       = NF_IP6_PRI_NAT_DST,
        },
@@ -81,6 +82,7 @@ static const struct nf_hook_ops nf_nat_ipv6_ops[] = {
        {
                .hook           = ip6table_nat_out,
                .pf             = NFPROTO_IPV6,
+               .nat_hook       = true,
                .hooknum        = NF_INET_POST_ROUTING,
                .priority       = NF_IP6_PRI_NAT_SRC,
        },
@@ -88,12 +90,14 @@ static const struct nf_hook_ops nf_nat_ipv6_ops[] = {
        {
                .hook           = ip6table_nat_local_fn,
                .pf             = NFPROTO_IPV6,
+               .nat_hook       = true,
                .hooknum        = NF_INET_LOCAL_OUT,
                .priority       = NF_IP6_PRI_NAT_DST,
        },
        /* After packet filtering, change source */
        {
                .hook           = ip6table_nat_fn,
+               .nat_hook       = true,
                .pf             = NFPROTO_IPV6,
                .hooknum        = NF_INET_LOCAL_IN,
                .priority       = NF_IP6_PRI_NAT_SRC,
index ed8618f4efd7227f5565c2056a7721e71ea8719a..3508a5c8edbb8a205e576471ec8c26ea5aa7b451 100644 (file)
@@ -160,6 +160,12 @@ nf_hook_entries_grow(const struct nf_hook_entries *old,
                        ++i;
                        continue;
                }
+
+               if (reg->nat_hook && orig_ops[i]->nat_hook) {
+                       kvfree(new);
+                       return ERR_PTR(-EEXIST);
+               }
+
                if (inserted || reg->priority > orig_ops[i]->priority) {
                        new_ops[nhooks] = (void *)orig_ops[i];
                        new->hooks[nhooks] = old->hooks[i];
index 10798b35748180746266aa0a84187dfd7f0f1ceb..838eb581b5ab122950aaca51e74744b6f06b7700 100644 (file)
@@ -1400,6 +1400,8 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask,
                                ops->hook = hookfn;
                        if (afi->hook_ops_init)
                                afi->hook_ops_init(ops, i);
+                       if (basechain->type->type == NFT_CHAIN_T_NAT)
+                               ops->nat_hook = true;
                }
 
                chain->flags |= NFT_BASE_CHAIN;