kernel: backport netfilter NAT offload support to 4.14
[openwrt/openwrt.git] / target / linux / generic / backport-4.14 / 355-netfilter-nf_flow_table-move-ipv6-offload-hook-code-.patch
diff --git a/target/linux/generic/backport-4.14/355-netfilter-nf_flow_table-move-ipv6-offload-hook-code-.patch b/target/linux/generic/backport-4.14/355-netfilter-nf_flow_table-move-ipv6-offload-hook-code-.patch
new file mode 100644 (file)
index 0000000..20ab0ed
--- /dev/null
@@ -0,0 +1,483 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Sat, 17 Feb 2018 11:55:51 +0100
+Subject: [PATCH] netfilter: nf_flow_table: move ipv6 offload hook code to
+ nf_flow_table
+
+Useful as preparation for adding iptables support for offload
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/net/ipv6/netfilter/nf_flow_table_ipv6.c
++++ b/net/ipv6/netfilter/nf_flow_table_ipv6.c
+@@ -3,240 +3,8 @@
+ #include <linux/module.h>
+ #include <linux/netfilter.h>
+ #include <linux/rhashtable.h>
+-#include <linux/ipv6.h>
+-#include <linux/netdevice.h>
+-#include <net/ipv6.h>
+-#include <net/ip6_route.h>
+-#include <net/neighbour.h>
+ #include <net/netfilter/nf_flow_table.h>
+ #include <net/netfilter/nf_tables.h>
+-/* For layer 4 checksum field offset. */
+-#include <linux/tcp.h>
+-#include <linux/udp.h>
+-
+-static int nf_flow_nat_ipv6_tcp(struct sk_buff *skb, unsigned int thoff,
+-                              struct in6_addr *addr,
+-                              struct in6_addr *new_addr)
+-{
+-      struct tcphdr *tcph;
+-
+-      if (!pskb_may_pull(skb, thoff + sizeof(*tcph)) ||
+-          skb_try_make_writable(skb, thoff + sizeof(*tcph)))
+-              return -1;
+-
+-      tcph = (void *)(skb_network_header(skb) + thoff);
+-      inet_proto_csum_replace16(&tcph->check, skb, addr->s6_addr32,
+-                                new_addr->s6_addr32, true);
+-
+-      return 0;
+-}
+-
+-static int nf_flow_nat_ipv6_udp(struct sk_buff *skb, unsigned int thoff,
+-                              struct in6_addr *addr,
+-                              struct in6_addr *new_addr)
+-{
+-      struct udphdr *udph;
+-
+-      if (!pskb_may_pull(skb, thoff + sizeof(*udph)) ||
+-          skb_try_make_writable(skb, thoff + sizeof(*udph)))
+-              return -1;
+-
+-      udph = (void *)(skb_network_header(skb) + thoff);
+-      if (udph->check || skb->ip_summed == CHECKSUM_PARTIAL) {
+-              inet_proto_csum_replace16(&udph->check, skb, addr->s6_addr32,
+-                                        new_addr->s6_addr32, true);
+-              if (!udph->check)
+-                      udph->check = CSUM_MANGLED_0;
+-      }
+-
+-      return 0;
+-}
+-
+-static int nf_flow_nat_ipv6_l4proto(struct sk_buff *skb, struct ipv6hdr *ip6h,
+-                                  unsigned int thoff, struct in6_addr *addr,
+-                                  struct in6_addr *new_addr)
+-{
+-      switch (ip6h->nexthdr) {
+-      case IPPROTO_TCP:
+-              if (nf_flow_nat_ipv6_tcp(skb, thoff, addr, new_addr) < 0)
+-                      return NF_DROP;
+-              break;
+-      case IPPROTO_UDP:
+-              if (nf_flow_nat_ipv6_udp(skb, thoff, addr, new_addr) < 0)
+-                      return NF_DROP;
+-              break;
+-      }
+-
+-      return 0;
+-}
+-
+-static int nf_flow_snat_ipv6(const struct flow_offload *flow,
+-                           struct sk_buff *skb, struct ipv6hdr *ip6h,
+-                           unsigned int thoff,
+-                           enum flow_offload_tuple_dir dir)
+-{
+-      struct in6_addr addr, new_addr;
+-
+-      switch (dir) {
+-      case FLOW_OFFLOAD_DIR_ORIGINAL:
+-              addr = ip6h->saddr;
+-              new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_v6;
+-              ip6h->saddr = new_addr;
+-              break;
+-      case FLOW_OFFLOAD_DIR_REPLY:
+-              addr = ip6h->daddr;
+-              new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.src_v6;
+-              ip6h->daddr = new_addr;
+-              break;
+-      default:
+-              return -1;
+-      }
+-
+-      return nf_flow_nat_ipv6_l4proto(skb, ip6h, thoff, &addr, &new_addr);
+-}
+-
+-static int nf_flow_dnat_ipv6(const struct flow_offload *flow,
+-                           struct sk_buff *skb, struct ipv6hdr *ip6h,
+-                           unsigned int thoff,
+-                           enum flow_offload_tuple_dir dir)
+-{
+-      struct in6_addr addr, new_addr;
+-
+-      switch (dir) {
+-      case FLOW_OFFLOAD_DIR_ORIGINAL:
+-              addr = ip6h->daddr;
+-              new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.src_v6;
+-              ip6h->daddr = new_addr;
+-              break;
+-      case FLOW_OFFLOAD_DIR_REPLY:
+-              addr = ip6h->saddr;
+-              new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.dst_v6;
+-              ip6h->saddr = new_addr;
+-              break;
+-      default:
+-              return -1;
+-      }
+-
+-      return nf_flow_nat_ipv6_l4proto(skb, ip6h, thoff, &addr, &new_addr);
+-}
+-
+-static int nf_flow_nat_ipv6(const struct flow_offload *flow,
+-                          struct sk_buff *skb,
+-                          enum flow_offload_tuple_dir dir)
+-{
+-      struct ipv6hdr *ip6h = ipv6_hdr(skb);
+-      unsigned int thoff = sizeof(*ip6h);
+-
+-      if (flow->flags & FLOW_OFFLOAD_SNAT &&
+-          (nf_flow_snat_port(flow, skb, thoff, ip6h->nexthdr, dir) < 0 ||
+-           nf_flow_snat_ipv6(flow, skb, ip6h, thoff, dir) < 0))
+-              return -1;
+-      if (flow->flags & FLOW_OFFLOAD_DNAT &&
+-          (nf_flow_dnat_port(flow, skb, thoff, ip6h->nexthdr, dir) < 0 ||
+-           nf_flow_dnat_ipv6(flow, skb, ip6h, thoff, dir) < 0))
+-              return -1;
+-
+-      return 0;
+-}
+-
+-static int nf_flow_tuple_ipv6(struct sk_buff *skb, const struct net_device *dev,
+-                            struct flow_offload_tuple *tuple)
+-{
+-      struct flow_ports *ports;
+-      struct ipv6hdr *ip6h;
+-      unsigned int thoff;
+-
+-      if (!pskb_may_pull(skb, sizeof(*ip6h)))
+-              return -1;
+-
+-      ip6h = ipv6_hdr(skb);
+-
+-      if (ip6h->nexthdr != IPPROTO_TCP &&
+-          ip6h->nexthdr != IPPROTO_UDP)
+-              return -1;
+-
+-      thoff = sizeof(*ip6h);
+-      if (!pskb_may_pull(skb, thoff + sizeof(*ports)))
+-              return -1;
+-
+-      ports = (struct flow_ports *)(skb_network_header(skb) + thoff);
+-
+-      tuple->src_v6           = ip6h->saddr;
+-      tuple->dst_v6           = ip6h->daddr;
+-      tuple->src_port         = ports->source;
+-      tuple->dst_port         = ports->dest;
+-      tuple->l3proto          = AF_INET6;
+-      tuple->l4proto          = ip6h->nexthdr;
+-      tuple->iifidx           = dev->ifindex;
+-
+-      return 0;
+-}
+-
+-/* Based on ip_exceeds_mtu(). */
+-static bool nf_flow_exceeds_mtu(const struct sk_buff *skb, unsigned int mtu)
+-{
+-      if (skb->len <= mtu)
+-              return false;
+-
+-      if (skb_is_gso(skb) && skb_gso_validate_mtu(skb, mtu))
+-              return false;
+-
+-      return true;
+-}
+-
+-unsigned int
+-nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
+-                        const struct nf_hook_state *state)
+-{
+-      struct flow_offload_tuple_rhash *tuplehash;
+-      struct nf_flowtable *flow_table = priv;
+-      struct flow_offload_tuple tuple = {};
+-      enum flow_offload_tuple_dir dir;
+-      struct flow_offload *flow;
+-      struct net_device *outdev;
+-      struct in6_addr *nexthop;
+-      struct ipv6hdr *ip6h;
+-      struct rt6_info *rt;
+-
+-      if (skb->protocol != htons(ETH_P_IPV6))
+-              return NF_ACCEPT;
+-
+-      if (nf_flow_tuple_ipv6(skb, state->in, &tuple) < 0)
+-              return NF_ACCEPT;
+-
+-      tuplehash = flow_offload_lookup(flow_table, &tuple);
+-      if (tuplehash == NULL)
+-              return NF_ACCEPT;
+-
+-      outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.oifidx);
+-      if (!outdev)
+-              return NF_ACCEPT;
+-
+-      dir = tuplehash->tuple.dir;
+-      flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
+-      rt = (struct rt6_info *)flow->tuplehash[dir].tuple.dst_cache;
+-
+-      if (unlikely(nf_flow_exceeds_mtu(skb, flow->tuplehash[dir].tuple.mtu)))
+-              return NF_ACCEPT;
+-
+-      if (skb_try_make_writable(skb, sizeof(*ip6h)))
+-              return NF_DROP;
+-
+-      if (flow->flags & (FLOW_OFFLOAD_SNAT | FLOW_OFFLOAD_DNAT) &&
+-          nf_flow_nat_ipv6(flow, skb, dir) < 0)
+-              return NF_DROP;
+-
+-      flow->timeout = (u32)jiffies + NF_FLOW_TIMEOUT;
+-      ip6h = ipv6_hdr(skb);
+-      ip6h->hop_limit--;
+-
+-      skb->dev = outdev;
+-      nexthop = rt6_nexthop(rt, &flow->tuplehash[!dir].tuple.src_v6);
+-      neigh_xmit(NEIGH_ND_TABLE, outdev, nexthop, skb);
+-
+-      return NF_STOLEN;
+-}
+-EXPORT_SYMBOL_GPL(nf_flow_offload_ipv6_hook);
+ static struct nf_flowtable_type flowtable_ipv6 = {
+       .family         = NFPROTO_IPV6,
+--- a/net/netfilter/nf_flow_table_ip.c
++++ b/net/netfilter/nf_flow_table_ip.c
+@@ -4,8 +4,11 @@
+ #include <linux/netfilter.h>
+ #include <linux/rhashtable.h>
+ #include <linux/ip.h>
++#include <linux/ipv6.h>
+ #include <linux/netdevice.h>
+ #include <net/ip.h>
++#include <net/ipv6.h>
++#include <net/ip6_route.h>
+ #include <net/neighbour.h>
+ #include <net/netfilter/nf_flow_table.h>
+ /* For layer 4 checksum field offset. */
+@@ -241,3 +244,215 @@ nf_flow_offload_ip_hook(void *priv, stru
+       return NF_STOLEN;
+ }
+ EXPORT_SYMBOL_GPL(nf_flow_offload_ip_hook);
++
++static int nf_flow_nat_ipv6_tcp(struct sk_buff *skb, unsigned int thoff,
++                              struct in6_addr *addr,
++                              struct in6_addr *new_addr)
++{
++      struct tcphdr *tcph;
++
++      if (!pskb_may_pull(skb, thoff + sizeof(*tcph)) ||
++          skb_try_make_writable(skb, thoff + sizeof(*tcph)))
++              return -1;
++
++      tcph = (void *)(skb_network_header(skb) + thoff);
++      inet_proto_csum_replace16(&tcph->check, skb, addr->s6_addr32,
++                                new_addr->s6_addr32, true);
++
++      return 0;
++}
++
++static int nf_flow_nat_ipv6_udp(struct sk_buff *skb, unsigned int thoff,
++                              struct in6_addr *addr,
++                              struct in6_addr *new_addr)
++{
++      struct udphdr *udph;
++
++      if (!pskb_may_pull(skb, thoff + sizeof(*udph)) ||
++          skb_try_make_writable(skb, thoff + sizeof(*udph)))
++              return -1;
++
++      udph = (void *)(skb_network_header(skb) + thoff);
++      if (udph->check || skb->ip_summed == CHECKSUM_PARTIAL) {
++              inet_proto_csum_replace16(&udph->check, skb, addr->s6_addr32,
++                                        new_addr->s6_addr32, true);
++              if (!udph->check)
++                      udph->check = CSUM_MANGLED_0;
++      }
++
++      return 0;
++}
++
++static int nf_flow_nat_ipv6_l4proto(struct sk_buff *skb, struct ipv6hdr *ip6h,
++                                  unsigned int thoff, struct in6_addr *addr,
++                                  struct in6_addr *new_addr)
++{
++      switch (ip6h->nexthdr) {
++      case IPPROTO_TCP:
++              if (nf_flow_nat_ipv6_tcp(skb, thoff, addr, new_addr) < 0)
++                      return NF_DROP;
++              break;
++      case IPPROTO_UDP:
++              if (nf_flow_nat_ipv6_udp(skb, thoff, addr, new_addr) < 0)
++                      return NF_DROP;
++              break;
++      }
++
++      return 0;
++}
++
++static int nf_flow_snat_ipv6(const struct flow_offload *flow,
++                           struct sk_buff *skb, struct ipv6hdr *ip6h,
++                           unsigned int thoff,
++                           enum flow_offload_tuple_dir dir)
++{
++      struct in6_addr addr, new_addr;
++
++      switch (dir) {
++      case FLOW_OFFLOAD_DIR_ORIGINAL:
++              addr = ip6h->saddr;
++              new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_v6;
++              ip6h->saddr = new_addr;
++              break;
++      case FLOW_OFFLOAD_DIR_REPLY:
++              addr = ip6h->daddr;
++              new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.src_v6;
++              ip6h->daddr = new_addr;
++              break;
++      default:
++              return -1;
++      }
++
++      return nf_flow_nat_ipv6_l4proto(skb, ip6h, thoff, &addr, &new_addr);
++}
++
++static int nf_flow_dnat_ipv6(const struct flow_offload *flow,
++                           struct sk_buff *skb, struct ipv6hdr *ip6h,
++                           unsigned int thoff,
++                           enum flow_offload_tuple_dir dir)
++{
++      struct in6_addr addr, new_addr;
++
++      switch (dir) {
++      case FLOW_OFFLOAD_DIR_ORIGINAL:
++              addr = ip6h->daddr;
++              new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.src_v6;
++              ip6h->daddr = new_addr;
++              break;
++      case FLOW_OFFLOAD_DIR_REPLY:
++              addr = ip6h->saddr;
++              new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.dst_v6;
++              ip6h->saddr = new_addr;
++              break;
++      default:
++              return -1;
++      }
++
++      return nf_flow_nat_ipv6_l4proto(skb, ip6h, thoff, &addr, &new_addr);
++}
++
++static int nf_flow_nat_ipv6(const struct flow_offload *flow,
++                          struct sk_buff *skb,
++                          enum flow_offload_tuple_dir dir)
++{
++      struct ipv6hdr *ip6h = ipv6_hdr(skb);
++      unsigned int thoff = sizeof(*ip6h);
++
++      if (flow->flags & FLOW_OFFLOAD_SNAT &&
++          (nf_flow_snat_port(flow, skb, thoff, ip6h->nexthdr, dir) < 0 ||
++           nf_flow_snat_ipv6(flow, skb, ip6h, thoff, dir) < 0))
++              return -1;
++      if (flow->flags & FLOW_OFFLOAD_DNAT &&
++          (nf_flow_dnat_port(flow, skb, thoff, ip6h->nexthdr, dir) < 0 ||
++           nf_flow_dnat_ipv6(flow, skb, ip6h, thoff, dir) < 0))
++              return -1;
++
++      return 0;
++}
++
++static int nf_flow_tuple_ipv6(struct sk_buff *skb, const struct net_device *dev,
++                            struct flow_offload_tuple *tuple)
++{
++      struct flow_ports *ports;
++      struct ipv6hdr *ip6h;
++      unsigned int thoff;
++
++      if (!pskb_may_pull(skb, sizeof(*ip6h)))
++              return -1;
++
++      ip6h = ipv6_hdr(skb);
++
++      if (ip6h->nexthdr != IPPROTO_TCP &&
++          ip6h->nexthdr != IPPROTO_UDP)
++              return -1;
++
++      thoff = sizeof(*ip6h);
++      if (!pskb_may_pull(skb, thoff + sizeof(*ports)))
++              return -1;
++
++      ports = (struct flow_ports *)(skb_network_header(skb) + thoff);
++
++      tuple->src_v6           = ip6h->saddr;
++      tuple->dst_v6           = ip6h->daddr;
++      tuple->src_port         = ports->source;
++      tuple->dst_port         = ports->dest;
++      tuple->l3proto          = AF_INET6;
++      tuple->l4proto          = ip6h->nexthdr;
++      tuple->iifidx           = dev->ifindex;
++
++      return 0;
++}
++
++unsigned int
++nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
++                        const struct nf_hook_state *state)
++{
++      struct flow_offload_tuple_rhash *tuplehash;
++      struct nf_flowtable *flow_table = priv;
++      struct flow_offload_tuple tuple = {};
++      enum flow_offload_tuple_dir dir;
++      struct flow_offload *flow;
++      struct net_device *outdev;
++      struct in6_addr *nexthop;
++      struct ipv6hdr *ip6h;
++      struct rt6_info *rt;
++
++      if (skb->protocol != htons(ETH_P_IPV6))
++              return NF_ACCEPT;
++
++      if (nf_flow_tuple_ipv6(skb, state->in, &tuple) < 0)
++              return NF_ACCEPT;
++
++      tuplehash = flow_offload_lookup(flow_table, &tuple);
++      if (tuplehash == NULL)
++              return NF_ACCEPT;
++
++      outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.oifidx);
++      if (!outdev)
++              return NF_ACCEPT;
++
++      dir = tuplehash->tuple.dir;
++      flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
++      rt = (struct rt6_info *)flow->tuplehash[dir].tuple.dst_cache;
++
++      if (unlikely(nf_flow_exceeds_mtu(skb, flow->tuplehash[dir].tuple.mtu)))
++              return NF_ACCEPT;
++
++      if (skb_try_make_writable(skb, sizeof(*ip6h)))
++              return NF_DROP;
++
++      if (flow->flags & (FLOW_OFFLOAD_SNAT | FLOW_OFFLOAD_DNAT) &&
++          nf_flow_nat_ipv6(flow, skb, dir) < 0)
++              return NF_DROP;
++
++      flow->timeout = (u32)jiffies + NF_FLOW_TIMEOUT;
++      ip6h = ipv6_hdr(skb);
++      ip6h->hop_limit--;
++
++      skb->dev = outdev;
++      nexthop = rt6_nexthop(rt, &flow->tuplehash[!dir].tuple.src_v6);
++      neigh_xmit(NEIGH_ND_TABLE, outdev, nexthop, skb);
++
++      return NF_STOLEN;
++}
++EXPORT_SYMBOL_GPL(nf_flow_offload_ipv6_hook);