[PATCH v9 nf 04/15] netfilter: bridge: Add conntrack double vlan and pppoe

Eric Woudstra
2025-03-05T10:30:10Z

This adds the capability to conntrack 802.1ad, QinQ, PPPoE and PPPoE-in-Q
packets that are passing a bridge.

Reviewed-by: Nikolay Aleksandrov <razor@blackwall.org>
Signed-off-by: Eric Woudstra <ericwouds@gmail.com>
---
net/bridge/netfilter/nf_conntrack_bridge.c | 83 ++++++++++++++++++----
1 file changed, 71 insertions(+), 12 deletions(-)

diff --git a/net/bridge/netfilter/nf_conntrack_bridge.c b/net/bridge/netfilter/nf_conntrack_bridge.c
index 816bb0fde718..4b4e3751fb13 100644
--- a/net/bridge/netfilter/nf_conntrack_bridge.c
+++ b/net/bridge/netfilter/nf_conntrack_bridge.c
@@ -242,53 +242,112 @@ static unsigned int nf_ct_bridge_pre(void *priv, struct sk_buff *skb,
{
struct nf_hook_state bridge_state = *state;
enum ip_conntrack_info ctinfo;
+ int ret, offset = 0;
struct nf_conn *ct;
- u32 len;
- int ret;
+ __be16 outer_proto;
+ u32 len, data_len;

ct = nf_ct_get(skb, &ctinfo);
if ((ct && !nf_ct_is_template(ct)) ||
ctinfo == IP_CT_UNTRACKED)
return NF_ACCEPT;

+ switch (skb->protocol) {
+ case htons(ETH_P_PPP_SES): {
+ struct ppp_hdr {
+ struct pppoe_hdr hdr;
+ __be16 proto;
+ } *ph;
+
+ offset = PPPOE_SES_HLEN;
+ if (!pskb_may_pull(skb, offset))
+ return NF_ACCEPT;
+ outer_proto = skb->protocol;
+ ph = (struct ppp_hdr *)(skb->data);
+ switch (ph->proto) {
+ case htons(PPP_IP):
+ skb->protocol = htons(ETH_P_IP);
+ break;
+ case htons(PPP_IPV6):
+ skb->protocol = htons(ETH_P_IPV6);
+ break;
+ default:
+ nf_ct_set(skb, NULL, IP_CT_UNTRACKED);
+ return NF_ACCEPT;
+ }
+ data_len = ntohs(ph->hdr.length) - 2;
+ skb_pull_rcsum(skb, offset);
+ skb_reset_network_header(skb);
+ break;
+ }
+ case htons(ETH_P_8021Q): {
+ struct vlan_hdr *vhdr;
+
+ offset = VLAN_HLEN;
+ if (!pskb_may_pull(skb, offset))
+ return NF_ACCEPT;
+ outer_proto = skb->protocol;
+ vhdr = (struct vlan_hdr *)(skb->data);
+ skb->protocol = vhdr->h_vlan_encapsulated_proto;
+ data_len = U32_MAX;
+ skb_pull_rcsum(skb, offset);
+ skb_reset_network_header(skb);
+ break;
+ }
+ default:
+ data_len = U32_MAX;
+ break;
+ }
+
+ ret = NF_ACCEPT;
switch (skb->protocol) {
case htons(ETH_P_IP):
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
- return NF_ACCEPT;
+ goto do_not_track;

len = skb_ip_totlen(skb);
+ if (data_len < len)
+ len = data_len;
if (pskb_trim_rcsum(skb, len))
- return NF_ACCEPT;
+ goto do_not_track;

if (nf_ct_br_ip_check(skb))
- return NF_ACCEPT;
+ goto do_not_track;

bridge_state.pf = NFPROTO_IPV4;
ret = nf_ct_br_defrag4(skb, &bridge_state);
break;
case htons(ETH_P_IPV6):
if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
- return NF_ACCEPT;
+ goto do_not_track;

len = sizeof(struct ipv6hdr) + ntohs(ipv6_hdr(skb)->payload_len);
+ if (data_len < len)
+ len = data_len;
if (pskb_trim_rcsum(skb, len))
- return NF_ACCEPT;
+ goto do_not_track;

if (nf_ct_br_ipv6_check(skb))
- return NF_ACCEPT;
+ goto do_not_track;

bridge_state.pf = NFPROTO_IPV6;
ret = nf_ct_br_defrag6(skb, &bridge_state);
break;
default:
nf_ct_set(skb, NULL, IP_CT_UNTRACKED);
- return NF_ACCEPT;
+ goto do_not_track;
}

- if (ret != NF_ACCEPT)
- return ret;
+ if (ret == NF_ACCEPT)
+ ret = nf_conntrack_in(skb, &bridge_state);

- return nf_conntrack_in(skb, &bridge_state);
+do_not_track:
+ if (offset) {
+ skb_push_rcsum(skb, offset);
+ skb_reset_network_header(skb);
+ skb->protocol = outer_proto;
+ }
+ return ret;
}

static unsigned int nf_ct_bridge_in(void *priv, struct sk_buff *skb,
--
2.47.1