netfilter: conntrack: skip confirmation and nat hooks in postrouting for vrf
authorFlorian Westphal <fw@strlen.de>
Mon, 25 Oct 2021 14:13:59 +0000 (16:13 +0200)
committerDavid S. Miller <davem@davemloft.net>
Tue, 26 Oct 2021 12:21:09 +0000 (13:21 +0100)
The VRF driver invokes netfilter for output+postrouting hooks so that users
can create rules that check for 'oif $vrf' rather than lower device name.

Afterwards, ip stack calls those hooks again.

This is a problem when conntrack is used with IP masquerading.
masquerading has an internal check that re-validates the output
interface to account for route changes.

This check will trigger in the vrf case.

If the -j MASQUERADE rule matched on the first iteration, then round 2
finds state->out->ifindex != nat->masq_index: the latter is the vrf
index, but out->ifindex is the lower device.

The packet gets dropped and the conntrack entry is invalidated.

This change makes conntrack postrouting skip the nat hooks.
Also skip confirmation.  This allows the second round
(postrouting invocation from ipv4/ipv6) to create nat bindings.

This also prevents the second round from seeing packets that had their
source address changed by the nat hook.

Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/netfilter/nf_conntrack_proto.c
net/netfilter/nf_nat_core.c

index 8f7a983..d1f2d3c 100644 (file)
@@ -155,6 +155,16 @@ unsigned int nf_confirm(struct sk_buff *skb, unsigned int protoff,
 }
 EXPORT_SYMBOL_GPL(nf_confirm);
 
+static bool in_vrf_postrouting(const struct nf_hook_state *state)
+{
+#if IS_ENABLED(CONFIG_NET_L3_MASTER_DEV)
+       if (state->hook == NF_INET_POST_ROUTING &&
+           netif_is_l3_master(state->out))
+               return true;
+#endif
+       return false;
+}
+
 static unsigned int ipv4_confirm(void *priv,
                                 struct sk_buff *skb,
                                 const struct nf_hook_state *state)
@@ -166,6 +176,9 @@ static unsigned int ipv4_confirm(void *priv,
        if (!ct || ctinfo == IP_CT_RELATED_REPLY)
                return nf_conntrack_confirm(skb);
 
+       if (in_vrf_postrouting(state))
+               return NF_ACCEPT;
+
        return nf_confirm(skb,
                          skb_network_offset(skb) + ip_hdrlen(skb),
                          ct, ctinfo);
@@ -374,6 +387,9 @@ static unsigned int ipv6_confirm(void *priv,
        if (!ct || ctinfo == IP_CT_RELATED_REPLY)
                return nf_conntrack_confirm(skb);
 
+       if (in_vrf_postrouting(state))
+               return NF_ACCEPT;
+
        protoff = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &pnum,
                                   &frag_off);
        if (protoff < 0 || (frag_off & htons(~0x7)) != 0) {
index 2731176..4d50d51 100644 (file)
@@ -699,6 +699,16 @@ unsigned int nf_nat_packet(struct nf_conn *ct,
 }
 EXPORT_SYMBOL_GPL(nf_nat_packet);
 
+static bool in_vrf_postrouting(const struct nf_hook_state *state)
+{
+#if IS_ENABLED(CONFIG_NET_L3_MASTER_DEV)
+       if (state->hook == NF_INET_POST_ROUTING &&
+           netif_is_l3_master(state->out))
+               return true;
+#endif
+       return false;
+}
+
 unsigned int
 nf_nat_inet_fn(void *priv, struct sk_buff *skb,
               const struct nf_hook_state *state)
@@ -715,7 +725,7 @@ nf_nat_inet_fn(void *priv, struct sk_buff *skb,
         * packet filter it out, or implement conntrack/NAT for that
         * protocol. 8) --RR
         */
-       if (!ct)
+       if (!ct || in_vrf_postrouting(state))
                return NF_ACCEPT;
 
        nat = nfct_nat(ct);