In the conntrack hook it may not always be the case that: skb_network_header(skb) == skb->data. This is problematic when L4 function nf_conntrack_handle_packet() is accessing L3 data. This function uses thoff and ip_hdr() to finds it's data. But it also calculates the checksum. nf_checksum() and nf_checksum_partial() both use lower skb-checksum functions that are based on using skb->data. When skb_network_header(skb) != skb->data, adjust accordingly, so that the checksum is calculated correctly. Signed-off-by: Eric Woudstra --- net/netfilter/utils.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/net/netfilter/utils.c b/net/netfilter/utils.c index 008419db815a..daee035c25b8 100644 --- a/net/netfilter/utils.c +++ b/net/netfilter/utils.c @@ -124,16 +124,21 @@ __sum16 nf_checksum(struct sk_buff *skb, unsigned int hook, unsigned int dataoff, u8 protocol, unsigned short family) { + unsigned int nhpull = skb_network_header(skb) - skb->data; __sum16 csum = 0; + if (!pskb_may_pull(skb, nhpull)) + return -ENOMEM; + __skb_pull(skb, nhpull); switch (family) { case AF_INET: - csum = nf_ip_checksum(skb, hook, dataoff, protocol); + csum = nf_ip_checksum(skb, hook, dataoff - nhpull, protocol); break; case AF_INET6: - csum = nf_ip6_checksum(skb, hook, dataoff, protocol); + csum = nf_ip6_checksum(skb, hook, dataoff - nhpull, protocol); break; } + __skb_push(skb, nhpull); return csum; } @@ -143,18 +148,23 @@ __sum16 nf_checksum_partial(struct sk_buff *skb, unsigned int hook, unsigned int dataoff, unsigned int len, u8 protocol, unsigned short family) { + unsigned int nhpull = skb_network_header(skb) - skb->data; __sum16 csum = 0; + if (!pskb_may_pull(skb, nhpull)) + return -ENOMEM; + __skb_pull(skb, nhpull); switch (family) { case AF_INET: - csum = nf_ip_checksum_partial(skb, hook, dataoff, len, - protocol); + csum = nf_ip_checksum_partial(skb, hook, dataoff - nhpull, + len, protocol); break; case AF_INET6: - csum = nf_ip6_checksum_partial(skb, hook, dataoff, len, - protocol); + csum = nf_ip6_checksum_partial(skb, hook, dataoff - nhpull, + len, protocol); break; } + __skb_push(skb, nhpull); return csum; } -- 2.47.1 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 | 88 ++++++++++++++++++---- 1 file changed, 72 insertions(+), 16 deletions(-) diff --git a/net/bridge/netfilter/nf_conntrack_bridge.c b/net/bridge/netfilter/nf_conntrack_bridge.c index 6482de4d8750..5fcb1bdf2e31 100644 --- a/net/bridge/netfilter/nf_conntrack_bridge.c +++ b/net/bridge/netfilter/nf_conntrack_bridge.c @@ -242,53 +242,109 @@ 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; + u32 len, data_len = U32_MAX; + int ret, offset = 0; struct nf_conn *ct; - u32 len; - int ret; + __be16 outer_proto; ct = nf_ct_get(skb, &ctinfo); if ((ct && !nf_ct_is_template(ct)) || ctinfo == IP_CT_UNTRACKED) return NF_ACCEPT; + if (ct && nf_ct_zone_id(nf_ct_zone(ct), CTINFO2DIR(ctinfo)) != + NF_CT_DEFAULT_ZONE_ID) { + 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_set_network_header(skb, offset); + 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_set_network_header(skb, offset); + break; + } + } + } + + ret = NF_ACCEPT; switch (skb->protocol) { case htons(ETH_P_IP): - if (!pskb_may_pull(skb, sizeof(struct iphdr))) - return NF_ACCEPT; + 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 (data_len < len) + len = data_len; + if (pskb_trim_rcsum(skb, len + offset)) + 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 (data_len < len) + len = data_len; + if (pskb_trim_rcsum(skb, len + offset)) + 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_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 This adds the capability to evaluate 802.1ad, QinQ, PPPoE and PPPoE-in-Q packets in the bridge filter chain. Signed-off-by: Eric Woudstra --- net/netfilter/nft_chain_filter.c | 52 +++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/net/netfilter/nft_chain_filter.c b/net/netfilter/nft_chain_filter.c index 19a553550c76..8445ddfb9cea 100644 --- a/net/netfilter/nft_chain_filter.c +++ b/net/netfilter/nft_chain_filter.c @@ -232,11 +232,55 @@ nft_do_chain_bridge(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { + __be16 outer_proto, proto = 0; struct nft_pktinfo pkt; + int ret, offset = 0; nft_set_pktinfo(&pkt, skb, state); switch (eth_hdr(skb)->h_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)) + break; + offset = PPPOE_SES_HLEN; + outer_proto = skb->protocol; + ph = (struct ppp_hdr *)(skb->data); + switch (ph->proto) { + case htons(PPP_IP): + proto = htons(ETH_P_IP); + break; + case htons(PPP_IPV6): + proto = htons(ETH_P_IPV6); + break; + } + skb_set_network_header(skb, offset); + skb->protocol = proto; + break; + } + case htons(ETH_P_8021Q): { + struct vlan_hdr *vhdr; + + if (!pskb_may_pull(skb, VLAN_HLEN)) + break; + offset = VLAN_HLEN; + outer_proto = skb->protocol; + vhdr = (struct vlan_hdr *)(skb->data); + proto = vhdr->h_vlan_encapsulated_proto; + skb_set_network_header(skb, offset); + skb->protocol = proto; + break; + } + default: + proto = eth_hdr(skb)->h_proto; + break; + } + + switch (proto) { case htons(ETH_P_IP): nft_set_pktinfo_ipv4_validate(&pkt); break; @@ -248,7 +292,13 @@ nft_do_chain_bridge(void *priv, break; } - return nft_do_chain(&pkt, priv); + ret = nft_do_chain(&pkt, priv); + + if (offset) { + skb_reset_network_header(skb); + skb->protocol = outer_proto; + } + return ret; } static const struct nft_chain_type nft_chain_filter_bridge = { -- 2.47.1