This adds the capability to conntrack 802.1ad, QinQ, PPPoE and PPPoE-in-Q packets that are passing a bridge, only when a conntrack zone is set. Signed-off-by: Eric Woudstra --- net/bridge/netfilter/nf_conntrack_bridge.c | 97 ++++++++++++++++++---- 1 file changed, 80 insertions(+), 17 deletions(-) diff --git a/net/bridge/netfilter/nf_conntrack_bridge.c b/net/bridge/netfilter/nf_conntrack_bridge.c index 6482de4d8750..d3745af60f3a 100644 --- a/net/bridge/netfilter/nf_conntrack_bridge.c +++ b/net/bridge/netfilter/nf_conntrack_bridge.c @@ -237,58 +237,121 @@ static int nf_ct_br_ipv6_check(const struct sk_buff *skb) return 0; } +/** + * nf_ct_bridge_pre_inner - advances network_header to the header that follows + * the pppoe- or vlan-header. + */ + +static int nf_ct_bridge_pre_inner(struct sk_buff *skb, __be16 *proto, u32 *len) +{ + switch (*proto) { + case htons(ETH_P_PPP_SES): { + struct ppp_hdr { + struct pppoe_hdr hdr; + __be16 proto; + } *ph; + + if (!pskb_may_pull(skb, PPPOE_SES_HLEN)) + return -1; + ph = (struct ppp_hdr *)(skb->data); + switch (ph->proto) { + case htons(PPP_IP): + *proto = htons(ETH_P_IP); + *len = ntohs(ph->hdr.length) - 2; + skb_set_network_header(skb, PPPOE_SES_HLEN); + return PPPOE_SES_HLEN; + case htons(PPP_IPV6): + *proto = htons(ETH_P_IPV6); + *len = ntohs(ph->hdr.length) - 2; + skb_set_network_header(skb, PPPOE_SES_HLEN); + return PPPOE_SES_HLEN; + } + break; + } + case htons(ETH_P_8021Q): { + struct vlan_hdr *vhdr; + + if (!pskb_may_pull(skb, VLAN_HLEN)) + return -1; + vhdr = (struct vlan_hdr *)(skb->data); + *proto = vhdr->h_vlan_encapsulated_proto; + skb_set_network_header(skb, VLAN_HLEN); + return VLAN_HLEN; + } + } + return 0; +} + static unsigned int nf_ct_bridge_pre(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct nf_hook_state bridge_state = *state; + int ret = NF_ACCEPT, offset = 0; enum ip_conntrack_info ctinfo; + u32 len, pppoe_len = 0; struct nf_conn *ct; - u32 len; - int ret; + __be16 proto; 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_IP): - if (!pskb_may_pull(skb, sizeof(struct iphdr))) + proto = skb->protocol; + + if (ct && nf_ct_zone_id(nf_ct_zone(ct), CTINFO2DIR(ctinfo)) != + NF_CT_DEFAULT_ZONE_ID) { + offset = nf_ct_bridge_pre_inner(skb, &proto, &pppoe_len); + if (offset < 0) return NF_ACCEPT; + } + + switch (proto) { + case htons(ETH_P_IP): + if (!pskb_may_pull(skb, offset + sizeof(struct iphdr))) + goto do_not_track; len = skb_ip_totlen(skb); - if (pskb_trim_rcsum(skb, len)) - return NF_ACCEPT; + if (pppoe_len && pppoe_len != len) + goto do_not_track; + if (pskb_trim_rcsum(skb, offset + len)) + 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; + if (!pskb_may_pull(skb, offset + sizeof(struct ipv6hdr))) + goto do_not_track; len = sizeof(struct ipv6hdr) + ntohs(ipv6_hdr(skb)->payload_len); - if (pskb_trim_rcsum(skb, len)) - return NF_ACCEPT; + if (pppoe_len && pppoe_len != len) + goto do_not_track; + if (pskb_trim_rcsum(skb, offset + len)) + 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); + +do_not_track: + if (offset && ret == NF_ACCEPT) + skb_reset_network_header(skb); - return nf_conntrack_in(skb, &bridge_state); + return ret; } static unsigned int nf_ct_bridge_in(void *priv, struct sk_buff *skb, -- 2.50.0