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 8f7a9837349c18f967275fd5a8376ca6b15924ad..d1f2d3c8d2b1809360af93c6d16c41a96184b444 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 273117683922858eca82f7192f022533422de590..4d50d51db796276c84fbc3486b870bd591d93796 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);