netfilter: nf_conntrack_bridge: add support for IPv6
authorPablo Neira Ayuso <pablo@netfilter.org>
Wed, 29 May 2019 11:25:38 +0000 (13:25 +0200)
committerDavid S. Miller <davem@davemloft.net>
Thu, 30 May 2019 21:18:18 +0000 (14:18 -0700)
br_defrag() and br_fragment() indirections are added in case that IPv6
support comes as a module, to avoid pulling innecessary dependencies in.

The new fraglist iterator and fragment transformer APIs are used to
implement the refragmentation code.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/netfilter_ipv6.h
net/bridge/netfilter/nf_conntrack_bridge.c
net/ipv6/netfilter.c

index 12113e50265639976f8564b19cdae43e4a232309..a21b8c9623ee2aff77ae311d2a3253567a461c1b 100644 (file)
@@ -19,6 +19,7 @@ struct ip6_rt_info {
 };
 
 struct nf_queue_entry;
+struct nf_ct_bridge_frag_data;
 
 /*
  * Hook functions for ipv6 to allow xt_* modules to be built-in even
@@ -39,6 +40,15 @@ struct nf_ipv6_ops {
        int (*fragment)(struct net *net, struct sock *sk, struct sk_buff *skb,
                        int (*output)(struct net *, struct sock *, struct sk_buff *));
        int (*reroute)(struct sk_buff *skb, const struct nf_queue_entry *entry);
+#if IS_MODULE(CONFIG_IPV6)
+       int (*br_defrag)(struct net *net, struct sk_buff *skb, u32 user);
+       int (*br_fragment)(struct net *net, struct sock *sk,
+                          struct sk_buff *skb,
+                          struct nf_ct_bridge_frag_data *data,
+                          int (*output)(struct net *, struct sock *sk,
+                                        const struct nf_ct_bridge_frag_data *data,
+                                        struct sk_buff *));
+#endif
 };
 
 #ifdef CONFIG_NETFILTER
@@ -86,6 +96,46 @@ static inline int nf_ip6_route(struct net *net, struct dst_entry **dst,
 #endif
 }
 
+static inline int nf_ipv6_br_defrag(struct net *net, struct sk_buff *skb,
+                                   u32 user)
+{
+#if IS_MODULE(CONFIG_IPV6)
+       const struct nf_ipv6_ops *v6_ops = nf_get_ipv6_ops();
+
+       if (!v6_ops)
+               return 1;
+
+       return v6_ops->br_defrag(net, skb, user);
+#else
+       return nf_ct_frag6_gather(net, skb, user);
+#endif
+}
+
+int br_ip6_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
+                   struct nf_ct_bridge_frag_data *data,
+                   int (*output)(struct net *, struct sock *sk,
+                                 const struct nf_ct_bridge_frag_data *data,
+                                 struct sk_buff *));
+
+static inline int nf_br_ip6_fragment(struct net *net, struct sock *sk,
+                                    struct sk_buff *skb,
+                                    struct nf_ct_bridge_frag_data *data,
+                                    int (*output)(struct net *, struct sock *sk,
+                                                  const struct nf_ct_bridge_frag_data *data,
+                                                  struct sk_buff *))
+{
+#if IS_MODULE(CONFIG_IPV6)
+       const struct nf_ipv6_ops *v6_ops = nf_get_ipv6_ops();
+
+       if (!v6_ops)
+               return 1;
+
+       return v6_ops->br_fragment(net, sk, skb, data, output);
+#else
+       return br_ip6_fragment(net, sk, skb, data, output);
+#endif
+}
+
 int ip6_route_me_harder(struct net *net, struct sk_buff *skb);
 
 static inline int nf_ip6_route_me_harder(struct net *net, struct sk_buff *skb)
index 2571528ed582c30e1405eb4da111fc2cbae0c594..b675cd7c1a82b9c54114ed4c48461865f9529603 100644 (file)
@@ -163,6 +163,31 @@ static unsigned int nf_ct_br_defrag4(struct sk_buff *skb,
        return NF_STOLEN;
 }
 
+static unsigned int nf_ct_br_defrag6(struct sk_buff *skb,
+                                    const struct nf_hook_state *state)
+{
+       u16 zone_id = NF_CT_DEFAULT_ZONE_ID;
+       enum ip_conntrack_info ctinfo;
+       struct br_input_skb_cb cb;
+       const struct nf_conn *ct;
+       int err;
+
+       ct = nf_ct_get(skb, &ctinfo);
+       if (ct)
+               zone_id = nf_ct_zone_id(nf_ct_zone(ct), CTINFO2DIR(ctinfo));
+
+       br_skb_cb_save(skb, &cb, sizeof(struct inet6_skb_parm));
+
+       err = nf_ipv6_br_defrag(state->net, skb,
+                               IP_DEFRAG_CONNTRACK_BRIDGE_IN + zone_id);
+       /* queued */
+       if (err == -EINPROGRESS)
+               return NF_STOLEN;
+
+       br_skb_cb_restore(skb, &cb, IP6CB(skb)->frag_max_size);
+       return err == 0 ? NF_ACCEPT : NF_DROP;
+}
+
 static int nf_ct_br_ip_check(const struct sk_buff *skb)
 {
        const struct iphdr *iph;
@@ -177,6 +202,23 @@ static int nf_ct_br_ip_check(const struct sk_buff *skb)
        len = ntohs(iph->tot_len);
        if (skb->len < nhoff + len ||
            len < (iph->ihl * 4))
+                return -1;
+
+       return 0;
+}
+
+static int nf_ct_br_ipv6_check(const struct sk_buff *skb)
+{
+       const struct ipv6hdr *hdr;
+       int nhoff, len;
+
+       nhoff = skb_network_offset(skb);
+       hdr = ipv6_hdr(skb);
+       if (hdr->version != 6)
+               return -1;
+
+       len = ntohs(hdr->payload_len) + sizeof(struct ipv6hdr) + nhoff;
+       if (skb->len < len)
                return -1;
 
        return 0;
@@ -212,7 +254,19 @@ static unsigned int nf_ct_bridge_pre(void *priv, struct sk_buff *skb,
                ret = nf_ct_br_defrag4(skb, &bridge_state);
                break;
        case htons(ETH_P_IPV6):
-               /* fall through */
+               if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
+                       return NF_ACCEPT;
+
+               len = sizeof(struct ipv6hdr) + ntohs(ipv6_hdr(skb)->payload_len);
+               if (pskb_trim_rcsum(skb, len))
+                       return NF_ACCEPT;
+
+               if (nf_ct_br_ipv6_check(skb))
+                       return NF_ACCEPT;
+
+               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;
@@ -254,7 +308,8 @@ nf_ct_bridge_refrag(struct sk_buff *skb, const struct nf_hook_state *state,
                nf_br_ip_fragment(state->net, state->sk, skb, &data, output);
                break;
        case htons(ETH_P_IPV6):
-               return NF_ACCEPT;
+               nf_br_ip6_fragment(state->net, state->sk, skb, &data, output);
+               break;
        default:
                WARN_ON_ONCE(1);
                return NF_DROP;
index 1240ccd57f394463f6bec15babb2f46f710898e3..c6665382acb5b2327bc9286b913bfaf0ab808751 100644 (file)
@@ -16,6 +16,9 @@
 #include <net/ip6_route.h>
 #include <net/xfrm.h>
 #include <net/netfilter/nf_queue.h>
+#include <net/netfilter/nf_conntrack_bridge.h>
+#include <net/netfilter/ipv6/nf_defrag_ipv6.h>
+#include "../bridge/br_private.h"
 
 int ip6_route_me_harder(struct net *net, struct sk_buff *skb)
 {
@@ -109,6 +112,122 @@ int __nf_ip6_route(struct net *net, struct dst_entry **dst,
 }
 EXPORT_SYMBOL_GPL(__nf_ip6_route);
 
+int br_ip6_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
+                   struct nf_ct_bridge_frag_data *data,
+                   int (*output)(struct net *, struct sock *sk,
+                                 const struct nf_ct_bridge_frag_data *data,
+                                 struct sk_buff *))
+{
+       int frag_max_size = BR_INPUT_SKB_CB(skb)->frag_max_size;
+       struct ip6_frag_state state;
+       u8 *prevhdr, nexthdr = 0;
+       unsigned int mtu, hlen;
+       int hroom, err = 0;
+       __be32 frag_id;
+
+       err = ip6_find_1stfragopt(skb, &prevhdr);
+       if (err < 0)
+               goto blackhole;
+       hlen = err;
+       nexthdr = *prevhdr;
+
+       mtu = skb->dev->mtu;
+       if (frag_max_size > mtu ||
+           frag_max_size < IPV6_MIN_MTU)
+               goto blackhole;
+
+       mtu = frag_max_size;
+       if (mtu < hlen + sizeof(struct frag_hdr) + 8)
+               goto blackhole;
+       mtu -= hlen + sizeof(struct frag_hdr);
+
+       frag_id = ipv6_select_ident(net, &ipv6_hdr(skb)->daddr,
+                                   &ipv6_hdr(skb)->saddr);
+
+       if (skb->ip_summed == CHECKSUM_PARTIAL &&
+           (err = skb_checksum_help(skb)))
+               goto blackhole;
+
+       hroom = LL_RESERVED_SPACE(skb->dev);
+       if (skb_has_frag_list(skb)) {
+               unsigned int first_len = skb_pagelen(skb);
+               struct ip6_fraglist_iter iter;
+               struct sk_buff *frag2;
+
+               if (first_len - hlen > mtu ||
+                   skb_headroom(skb) < (hroom + sizeof(struct frag_hdr)))
+                       goto blackhole;
+
+               if (skb_cloned(skb))
+                       goto slow_path;
+
+               skb_walk_frags(skb, frag2) {
+                       if (frag2->len > mtu ||
+                           skb_headroom(frag2) < (hlen + hroom + sizeof(struct frag_hdr)))
+                               goto blackhole;
+
+                       /* Partially cloned skb? */
+                       if (skb_shared(frag2))
+                               goto slow_path;
+               }
+
+               err = ip6_fraglist_init(skb, hlen, prevhdr, nexthdr, frag_id,
+                                       &iter);
+               if (err < 0)
+                       goto blackhole;
+
+               for (;;) {
+                       /* Prepare header of the next frame,
+                        * before previous one went down.
+                        */
+                       if (iter.frag)
+                               ip6_fraglist_prepare(skb, &iter);
+
+                       err = output(net, sk, data, skb);
+                       if (err || !iter.frag)
+                               break;
+
+                       skb = ip6_fraglist_next(&iter);
+               }
+
+               kfree(iter.tmp_hdr);
+               if (!err)
+                       return 0;
+
+               kfree_skb_list(iter.frag_list);
+               return err;
+       }
+slow_path:
+       /* This is a linearized skbuff, the original geometry is lost for us.
+        * This may also be a clone skbuff, we could preserve the geometry for
+        * the copies but probably not worth the effort.
+        */
+       ip6_frag_init(skb, hlen, mtu, skb->dev->needed_tailroom,
+                     LL_RESERVED_SPACE(skb->dev), prevhdr, nexthdr, frag_id,
+                     &state);
+
+       while (state.left > 0) {
+               struct sk_buff *skb2;
+
+               skb2 = ip6_frag_next(skb, &state);
+               if (IS_ERR(skb2)) {
+                       err = PTR_ERR(skb2);
+                       goto blackhole;
+               }
+
+               err = output(net, sk, data, skb2);
+               if (err)
+                       goto blackhole;
+       }
+       consume_skb(skb);
+       return err;
+
+blackhole:
+       kfree_skb(skb);
+       return 0;
+}
+EXPORT_SYMBOL_GPL(br_ip6_fragment);
+
 static const struct nf_ipv6_ops ipv6ops = {
 #if IS_MODULE(CONFIG_IPV6)
        .chk_addr               = ipv6_chk_addr,
@@ -119,6 +238,10 @@ static const struct nf_ipv6_ops ipv6ops = {
        .route_input            = ip6_route_input,
        .fragment               = ip6_fragment,
        .reroute                = nf_ip6_reroute,
+#if IS_MODULE(CONFIG_NF_CONNTRACK_BRIDGE)
+       .br_defrag              = nf_ct_frag6_gather,
+       .br_fragment            = br_ip6_fragment,
+#endif
 };
 
 int __init ipv6_netfilter_init(void)