netfilter: flowtable: add offload support for xmit path types
authorPablo Neira Ayuso <pablo@netfilter.org>
Wed, 24 Mar 2021 01:30:46 +0000 (02:30 +0100)
committerDavid S. Miller <davem@davemloft.net>
Wed, 24 Mar 2021 19:48:39 +0000 (12:48 -0700)
When the flow tuple xmit_type is set to FLOW_OFFLOAD_XMIT_DIRECT, the
dst_cache pointer is not valid, and the h_source/h_dest/ifidx out fields
need to be used.

This patch also adds the FLOW_ACTION_VLAN_PUSH action to pass the VLAN
tag to the driver.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/netfilter/nf_flow_table_offload.c

index 1b979c8..3fbed44 100644 (file)
@@ -177,28 +177,45 @@ static int flow_offload_eth_src(struct net *net,
                                enum flow_offload_tuple_dir dir,
                                struct nf_flow_rule *flow_rule)
 {
-       const struct flow_offload_tuple *tuple = &flow->tuplehash[!dir].tuple;
        struct flow_action_entry *entry0 = flow_action_entry_next(flow_rule);
        struct flow_action_entry *entry1 = flow_action_entry_next(flow_rule);
-       struct net_device *dev;
+       const struct flow_offload_tuple *other_tuple, *this_tuple;
+       struct net_device *dev = NULL;
+       const unsigned char *addr;
        u32 mask, val;
        u16 val16;
 
-       dev = dev_get_by_index(net, tuple->iifidx);
-       if (!dev)
-               return -ENOENT;
+       this_tuple = &flow->tuplehash[dir].tuple;
+
+       switch (this_tuple->xmit_type) {
+       case FLOW_OFFLOAD_XMIT_DIRECT:
+               addr = this_tuple->out.h_source;
+               break;
+       case FLOW_OFFLOAD_XMIT_NEIGH:
+               other_tuple = &flow->tuplehash[!dir].tuple;
+               dev = dev_get_by_index(net, other_tuple->iifidx);
+               if (!dev)
+                       return -ENOENT;
+
+               addr = dev->dev_addr;
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
 
        mask = ~0xffff0000;
-       memcpy(&val16, dev->dev_addr, 2);
+       memcpy(&val16, addr, 2);
        val = val16 << 16;
        flow_offload_mangle(entry0, FLOW_ACT_MANGLE_HDR_TYPE_ETH, 4,
                            &val, &mask);
 
        mask = ~0xffffffff;
-       memcpy(&val, dev->dev_addr + 2, 4);
+       memcpy(&val, addr + 2, 4);
        flow_offload_mangle(entry1, FLOW_ACT_MANGLE_HDR_TYPE_ETH, 8,
                            &val, &mask);
-       dev_put(dev);
+
+       if (dev)
+               dev_put(dev);
 
        return 0;
 }
@@ -210,27 +227,40 @@ static int flow_offload_eth_dst(struct net *net,
 {
        struct flow_action_entry *entry0 = flow_action_entry_next(flow_rule);
        struct flow_action_entry *entry1 = flow_action_entry_next(flow_rule);
-       const void *daddr = &flow->tuplehash[!dir].tuple.src_v4;
+       const struct flow_offload_tuple *other_tuple, *this_tuple;
        const struct dst_entry *dst_cache;
        unsigned char ha[ETH_ALEN];
        struct neighbour *n;
+       const void *daddr;
        u32 mask, val;
        u8 nud_state;
        u16 val16;
 
-       dst_cache = flow->tuplehash[dir].tuple.dst_cache;
-       n = dst_neigh_lookup(dst_cache, daddr);
-       if (!n)
-               return -ENOENT;
+       this_tuple = &flow->tuplehash[dir].tuple;
 
-       read_lock_bh(&n->lock);
-       nud_state = n->nud_state;
-       ether_addr_copy(ha, n->ha);
-       read_unlock_bh(&n->lock);
-
-       if (!(nud_state & NUD_VALID)) {
+       switch (this_tuple->xmit_type) {
+       case FLOW_OFFLOAD_XMIT_DIRECT:
+               ether_addr_copy(ha, this_tuple->out.h_dest);
+               break;
+       case FLOW_OFFLOAD_XMIT_NEIGH:
+               other_tuple = &flow->tuplehash[!dir].tuple;
+               daddr = &other_tuple->src_v4;
+               dst_cache = this_tuple->dst_cache;
+               n = dst_neigh_lookup(dst_cache, daddr);
+               if (!n)
+                       return -ENOENT;
+
+               read_lock_bh(&n->lock);
+               nud_state = n->nud_state;
+               ether_addr_copy(ha, n->ha);
+               read_unlock_bh(&n->lock);
                neigh_release(n);
-               return -ENOENT;
+
+               if (!(nud_state & NUD_VALID))
+                       return -ENOENT;
+               break;
+       default:
+               return -EOPNOTSUPP;
        }
 
        mask = ~0xffffffff;
@@ -243,7 +273,6 @@ static int flow_offload_eth_dst(struct net *net,
        val = val16;
        flow_offload_mangle(entry1, FLOW_ACT_MANGLE_HDR_TYPE_ETH, 4,
                            &val, &mask);
-       neigh_release(n);
 
        return 0;
 }
@@ -465,27 +494,52 @@ static void flow_offload_ipv4_checksum(struct net *net,
        }
 }
 
-static void flow_offload_redirect(const struct flow_offload *flow,
+static void flow_offload_redirect(struct net *net,
+                                 const struct flow_offload *flow,
                                  enum flow_offload_tuple_dir dir,
                                  struct nf_flow_rule *flow_rule)
 {
-       struct flow_action_entry *entry = flow_action_entry_next(flow_rule);
-       struct rtable *rt;
+       const struct flow_offload_tuple *this_tuple, *other_tuple;
+       struct flow_action_entry *entry;
+       struct net_device *dev;
+       int ifindex;
 
-       rt = (struct rtable *)flow->tuplehash[dir].tuple.dst_cache;
+       this_tuple = &flow->tuplehash[dir].tuple;
+       switch (this_tuple->xmit_type) {
+       case FLOW_OFFLOAD_XMIT_DIRECT:
+               this_tuple = &flow->tuplehash[dir].tuple;
+               ifindex = this_tuple->out.ifidx;
+               break;
+       case FLOW_OFFLOAD_XMIT_NEIGH:
+               other_tuple = &flow->tuplehash[!dir].tuple;
+               ifindex = other_tuple->iifidx;
+               break;
+       default:
+               return;
+       }
+
+       dev = dev_get_by_index(net, ifindex);
+       if (!dev)
+               return;
+
+       entry = flow_action_entry_next(flow_rule);
        entry->id = FLOW_ACTION_REDIRECT;
-       entry->dev = rt->dst.dev;
-       dev_hold(rt->dst.dev);
+       entry->dev = dev;
 }
 
 static void flow_offload_encap_tunnel(const struct flow_offload *flow,
                                      enum flow_offload_tuple_dir dir,
                                      struct nf_flow_rule *flow_rule)
 {
+       const struct flow_offload_tuple *this_tuple;
        struct flow_action_entry *entry;
        struct dst_entry *dst;
 
-       dst = flow->tuplehash[dir].tuple.dst_cache;
+       this_tuple = &flow->tuplehash[dir].tuple;
+       if (this_tuple->xmit_type == FLOW_OFFLOAD_XMIT_DIRECT)
+               return;
+
+       dst = this_tuple->dst_cache;
        if (dst && dst->lwtstate) {
                struct ip_tunnel_info *tun_info;
 
@@ -502,10 +556,15 @@ static void flow_offload_decap_tunnel(const struct flow_offload *flow,
                                      enum flow_offload_tuple_dir dir,
                                      struct nf_flow_rule *flow_rule)
 {
+       const struct flow_offload_tuple *other_tuple;
        struct flow_action_entry *entry;
        struct dst_entry *dst;
 
-       dst = flow->tuplehash[!dir].tuple.dst_cache;
+       other_tuple = &flow->tuplehash[!dir].tuple;
+       if (other_tuple->xmit_type == FLOW_OFFLOAD_XMIT_DIRECT)
+               return;
+
+       dst = other_tuple->dst_cache;
        if (dst && dst->lwtstate) {
                struct ip_tunnel_info *tun_info;
 
@@ -517,10 +576,14 @@ static void flow_offload_decap_tunnel(const struct flow_offload *flow,
        }
 }
 
-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)
+static int
+nf_flow_rule_route_common(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 *other_tuple;
+       int i;
+
        flow_offload_decap_tunnel(flow, dir, flow_rule);
        flow_offload_encap_tunnel(flow, dir, flow_rule);
 
@@ -528,6 +591,26 @@ int nf_flow_rule_route_ipv4(struct net *net, const struct flow_offload *flow,
            flow_offload_eth_dst(net, flow, dir, flow_rule) < 0)
                return -1;
 
+       other_tuple = &flow->tuplehash[!dir].tuple;
+
+       for (i = 0; i < other_tuple->encap_num; i++) {
+               struct flow_action_entry *entry = flow_action_entry_next(flow_rule);
+
+               entry->id = FLOW_ACTION_VLAN_PUSH;
+               entry->vlan.vid = other_tuple->encap[i].id;
+               entry->vlan.proto = other_tuple->encap[i].proto;
+       }
+
+       return 0;
+}
+
+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 (nf_flow_rule_route_common(net, flow, dir, flow_rule) < 0)
+               return -1;
+
        if (test_bit(NF_FLOW_SNAT, &flow->flags)) {
                flow_offload_ipv4_snat(net, flow, dir, flow_rule);
                flow_offload_port_snat(net, flow, dir, flow_rule);
@@ -540,7 +623,7 @@ int nf_flow_rule_route_ipv4(struct net *net, const struct flow_offload *flow,
            test_bit(NF_FLOW_DNAT, &flow->flags))
                flow_offload_ipv4_checksum(net, flow, flow_rule);
 
-       flow_offload_redirect(flow, dir, flow_rule);
+       flow_offload_redirect(net, flow, dir, flow_rule);
 
        return 0;
 }
@@ -550,11 +633,7 @@ 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)
 {
-       flow_offload_decap_tunnel(flow, dir, flow_rule);
-       flow_offload_encap_tunnel(flow, dir, flow_rule);
-
-       if (flow_offload_eth_src(net, flow, dir, flow_rule) < 0 ||
-           flow_offload_eth_dst(net, flow, dir, flow_rule) < 0)
+       if (nf_flow_rule_route_common(net, flow, dir, flow_rule) < 0)
                return -1;
 
        if (test_bit(NF_FLOW_SNAT, &flow->flags)) {
@@ -566,7 +645,7 @@ int nf_flow_rule_route_ipv6(struct net *net, const struct flow_offload *flow,
                flow_offload_port_dnat(net, flow, dir, flow_rule);
        }
 
-       flow_offload_redirect(flow, dir, flow_rule);
+       flow_offload_redirect(net, flow, dir, flow_rule);
 
        return 0;
 }
@@ -580,10 +659,10 @@ nf_flow_offload_rule_alloc(struct net *net,
                           enum flow_offload_tuple_dir dir)
 {
        const struct nf_flowtable *flowtable = offload->flowtable;
+       const struct flow_offload_tuple *tuple, *other_tuple;
        const struct flow_offload *flow = offload->flow;
-       const struct flow_offload_tuple *tuple;
+       struct dst_entry *other_dst = NULL;
        struct nf_flow_rule *flow_rule;
-       struct dst_entry *other_dst;
        int err = -ENOMEM;
 
        flow_rule = kzalloc(sizeof(*flow_rule), GFP_KERNEL);
@@ -599,7 +678,10 @@ nf_flow_offload_rule_alloc(struct net *net,
        flow_rule->rule->match.key = &flow_rule->match.key;
 
        tuple = &flow->tuplehash[dir].tuple;
-       other_dst = flow->tuplehash[!dir].tuple.dst_cache;
+       other_tuple = &flow->tuplehash[!dir].tuple;
+       if (other_tuple->xmit_type == FLOW_OFFLOAD_XMIT_NEIGH)
+               other_dst = other_tuple->dst_cache;
+
        err = nf_flow_rule_match(&flow_rule->match, tuple, other_dst);
        if (err < 0)
                goto err_flow_match;