netfilter: nf_flow_table_offload: add IPv6 support
authorPablo Neira Ayuso <pablo@netfilter.org>
Wed, 13 Nov 2019 13:08:01 +0000 (14:08 +0100)
committerPablo Neira Ayuso <pablo@netfilter.org>
Fri, 15 Nov 2019 22:44:47 +0000 (23:44 +0100)
Add nf_flow_rule_route_ipv6() and use it from the IPv6 and the inet
flowtable type definitions. Rename the nf_flow_rule_route() function to
nf_flow_rule_route_ipv4().

Adjust maximum number of actions, which now becomes 16 to leave
sufficient room for the IPv6 address mangling for NAT.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
include/net/netfilter/nf_flow_table.h
net/ipv4/netfilter/nf_flow_table_ipv4.c
net/ipv6/netfilter/nf_flow_table_ipv6.c
net/netfilter/nf_flow_table_inet.c
net/netfilter/nf_flow_table_offload.c

index eea66de328d3ee9500c2020cde76d1abf2924f3d..f0897b3c97fb8c3c9afdb4d3ba4835aab45c856a 100644 (file)
@@ -163,9 +163,12 @@ void nf_flow_table_offload_flush(struct nf_flowtable *flowtable);
 int nf_flow_table_offload_setup(struct nf_flowtable *flowtable,
                                struct net_device *dev,
                                enum flow_block_command cmd);
-int nf_flow_rule_route(struct net *net, const struct flow_offload *flow,
-                      enum flow_offload_tuple_dir dir,
-                      struct nf_flow_rule *flow_rule);
+int nf_flow_rule_route_ipv4(struct net *net, const struct flow_offload *flow,
+                           enum flow_offload_tuple_dir dir,
+                           struct nf_flow_rule *flow_rule);
+int nf_flow_rule_route_ipv6(struct net *net, const struct flow_offload *flow,
+                           enum flow_offload_tuple_dir dir,
+                           struct nf_flow_rule *flow_rule);
 
 int nf_flow_table_offload_init(void);
 void nf_flow_table_offload_exit(void);
index 168b72e18be05ec2c1d91770cdcc142444077261..e32e41b99f0f04ab5abf01bce08b5c1a4811c2df 100644 (file)
@@ -10,7 +10,7 @@ static struct nf_flowtable_type flowtable_ipv4 = {
        .family         = NFPROTO_IPV4,
        .init           = nf_flow_table_init,
        .setup          = nf_flow_table_offload_setup,
-       .action         = nf_flow_rule_route,
+       .action         = nf_flow_rule_route_ipv4,
        .free           = nf_flow_table_free,
        .hook           = nf_flow_offload_ip_hook,
        .owner          = THIS_MODULE,
index f069bc0dc0563897c0ba21d308b6e6ea89cb22e7..a8566ee12e831ac24528787fb51f6fd5f837ff94 100644 (file)
@@ -11,7 +11,7 @@ static struct nf_flowtable_type flowtable_ipv6 = {
        .family         = NFPROTO_IPV6,
        .init           = nf_flow_table_init,
        .setup          = nf_flow_table_offload_setup,
-       .action         = nf_flow_rule_route,
+       .action         = nf_flow_rule_route_ipv6,
        .free           = nf_flow_table_free,
        .hook           = nf_flow_offload_ipv6_hook,
        .owner          = THIS_MODULE,
index bfb910b874ce1832612b692d33f4b350e2a47c2a..88bedf1ff1ae1646081f190d162f7b1a2b9b98f6 100644 (file)
@@ -21,11 +21,34 @@ nf_flow_offload_inet_hook(void *priv, struct sk_buff *skb,
        return NF_ACCEPT;
 }
 
+static int nf_flow_rule_route_inet(struct net *net,
+                                  const struct flow_offload *flow,
+                                  enum flow_offload_tuple_dir dir,
+                                  struct nf_flow_rule *flow_rule)
+{
+       const struct flow_offload_tuple *flow_tuple = &flow->tuplehash[dir].tuple;
+       int err;
+
+       switch (flow_tuple->l3proto) {
+       case NFPROTO_IPV4:
+               err = nf_flow_rule_route_ipv4(net, flow, dir, flow_rule);
+               break;
+       case NFPROTO_IPV6:
+               err = nf_flow_rule_route_ipv6(net, flow, dir, flow_rule);
+               break;
+       default:
+               err = -1;
+               break;
+       }
+
+       return err;
+}
+
 static struct nf_flowtable_type flowtable_inet = {
        .family         = NFPROTO_INET,
        .init           = nf_flow_table_init,
        .setup          = nf_flow_table_offload_setup,
-       .action         = nf_flow_rule_route,
+       .action         = nf_flow_rule_route_inet,
        .free           = nf_flow_table_free,
        .hook           = nf_flow_offload_inet_hook,
        .owner          = THIS_MODULE,
index b9f669c80713a0eada49e3aa8f1602c6771b699d..a14932748bcf0bb9da9c6bc1097518bfe2fbfcf5 100644 (file)
@@ -236,6 +236,71 @@ static void flow_offload_ipv4_dnat(struct net *net,
                            (u8 *)&addr, (u8 *)&mask);
 }
 
+static void flow_offload_ipv6_mangle(struct nf_flow_rule *flow_rule,
+                                    unsigned int offset,
+                                    u8 *addr, u8 *mask)
+{
+       struct flow_action_entry *entry;
+       int i;
+
+       for (i = 0; i < sizeof(struct in6_addr) / sizeof(u32); i += sizeof(u32)) {
+               entry = flow_action_entry_next(flow_rule);
+               flow_offload_mangle(entry, FLOW_ACT_MANGLE_HDR_TYPE_IP6,
+                                   offset + i,
+                                   &addr[i], mask);
+       }
+}
+
+static void flow_offload_ipv6_snat(struct net *net,
+                                  const struct flow_offload *flow,
+                                  enum flow_offload_tuple_dir dir,
+                                  struct nf_flow_rule *flow_rule)
+{
+       u32 mask = ~htonl(0xffffffff);
+       const u8 *addr;
+       u32 offset;
+
+       switch (dir) {
+       case FLOW_OFFLOAD_DIR_ORIGINAL:
+               addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_v6.s6_addr;
+               offset = offsetof(struct ipv6hdr, saddr);
+               break;
+       case FLOW_OFFLOAD_DIR_REPLY:
+               addr = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.src_v6.s6_addr;
+               offset = offsetof(struct ipv6hdr, daddr);
+               break;
+       default:
+               return;
+       }
+
+       flow_offload_ipv6_mangle(flow_rule, offset, (u8 *)addr, (u8 *)&mask);
+}
+
+static void flow_offload_ipv6_dnat(struct net *net,
+                                  const struct flow_offload *flow,
+                                  enum flow_offload_tuple_dir dir,
+                                  struct nf_flow_rule *flow_rule)
+{
+       u32 mask = ~htonl(0xffffffff);
+       const u8 *addr;
+       u32 offset;
+
+       switch (dir) {
+       case FLOW_OFFLOAD_DIR_ORIGINAL:
+               addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.src_v6.s6_addr;
+               offset = offsetof(struct ipv6hdr, daddr);
+               break;
+       case FLOW_OFFLOAD_DIR_REPLY:
+               addr = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.dst_v6.s6_addr;
+               offset = offsetof(struct ipv6hdr, saddr);
+               break;
+       default:
+               return;
+       }
+
+       flow_offload_ipv6_mangle(flow_rule, offset, (u8 *)addr, (u8 *)&mask);
+}
+
 static int flow_offload_l4proto(const struct flow_offload *flow)
 {
        u8 protonum = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.l4proto;
@@ -342,9 +407,9 @@ static void flow_offload_redirect(const struct flow_offload *flow,
        dev_hold(rt->dst.dev);
 }
 
-int nf_flow_rule_route(struct net *net, const struct flow_offload *flow,
-                      enum flow_offload_tuple_dir dir,
-                      struct nf_flow_rule *flow_rule)
+int nf_flow_rule_route_ipv4(struct net *net, const struct flow_offload *flow,
+                           enum flow_offload_tuple_dir dir,
+                           struct nf_flow_rule *flow_rule)
 {
        if (flow_offload_eth_src(net, flow, dir, flow_rule) < 0 ||
            flow_offload_eth_dst(net, flow, dir, flow_rule) < 0)
@@ -366,7 +431,32 @@ int nf_flow_rule_route(struct net *net, const struct flow_offload *flow,
 
        return 0;
 }
-EXPORT_SYMBOL_GPL(nf_flow_rule_route);
+EXPORT_SYMBOL_GPL(nf_flow_rule_route_ipv4);
+
+int nf_flow_rule_route_ipv6(struct net *net, const struct flow_offload *flow,
+                           enum flow_offload_tuple_dir dir,
+                           struct nf_flow_rule *flow_rule)
+{
+       if (flow_offload_eth_src(net, flow, dir, flow_rule) < 0 ||
+           flow_offload_eth_dst(net, flow, dir, flow_rule) < 0)
+               return -1;
+
+       if (flow->flags & FLOW_OFFLOAD_SNAT) {
+               flow_offload_ipv6_snat(net, flow, dir, flow_rule);
+               flow_offload_port_snat(net, flow, dir, flow_rule);
+       }
+       if (flow->flags & FLOW_OFFLOAD_DNAT) {
+               flow_offload_ipv6_dnat(net, flow, dir, flow_rule);
+               flow_offload_port_dnat(net, flow, dir, flow_rule);
+       }
+
+       flow_offload_redirect(flow, dir, flow_rule);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(nf_flow_rule_route_ipv6);
+
+#define NF_FLOW_RULE_ACTION_MAX        16
 
 static struct nf_flow_rule *
 nf_flow_offload_rule_alloc(struct net *net,
@@ -383,7 +473,7 @@ nf_flow_offload_rule_alloc(struct net *net,
        if (!flow_rule)
                goto err_flow;
 
-       flow_rule->rule = flow_rule_alloc(10);
+       flow_rule->rule = flow_rule_alloc(NF_FLOW_RULE_ACTION_MAX);
        if (!flow_rule->rule)
                goto err_flow_rule;