Add an ether_type field to struct net_device_path_ctx to allow IPv6 tunnel drivers to select the appropriate L3 protocol based on the encapsulated traffic. Update the airoha and mtk Ethernet drivers to use the new dev_fill_forward_path() signature. This is a preliminary patch to enable sw flowtable acceleration for IPv4 over IPv6 tunnels. Signed-off-by: Lorenzo Bianconi --- drivers/net/ethernet/airoha/airoha_ppe.c | 13 ++++++++----- drivers/net/ethernet/mediatek/mtk_ppe_offload.c | 13 ++++++++----- include/linux/netdevice.h | 4 +++- net/core/dev.c | 6 ++++-- net/ipv6/ip6_tunnel.c | 5 ++++- net/netfilter/nf_flow_table_path.c | 8 +++++--- 6 files changed, 32 insertions(+), 17 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c index e7c78293002a..06d128c67c8c 100644 --- a/drivers/net/ethernet/airoha/airoha_ppe.c +++ b/drivers/net/ethernet/airoha/airoha_ppe.c @@ -276,7 +276,8 @@ static int airoha_ppe_flow_mangle_ipv4(const struct flow_action_entry *act, return 0; } -static int airoha_ppe_get_wdma_info(struct net_device *dev, const u8 *addr, +static int airoha_ppe_get_wdma_info(struct net_device *dev, + const u8 *addr, __be16 ether_type, struct airoha_wdma_info *info) { struct net_device_path_stack stack; @@ -287,7 +288,7 @@ static int airoha_ppe_get_wdma_info(struct net_device *dev, const u8 *addr, return -ENODEV; rcu_read_lock(); - err = dev_fill_forward_path(dev, addr, &stack); + err = dev_fill_forward_path(dev, addr, ether_type, &stack); rcu_read_unlock(); if (err) return err; @@ -331,7 +332,7 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth, struct airoha_foe_entry *hwe, struct net_device *netdev, int type, struct airoha_flow_data *data, - int l4proto) + __be16 ether_type, int l4proto) { u32 qdata = FIELD_PREP(AIROHA_FOE_SHAPER_ID, 0x7f), ports_pad, val; int wlan_etype = -EINVAL, dsa_port = airoha_get_dsa_port(&netdev); @@ -354,7 +355,7 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth, struct airoha_wdma_info info = {}; if (!airoha_ppe_get_wdma_info(netdev, data->eth.h_dest, - &info)) { + ether_type, &info)) { val |= FIELD_PREP(AIROHA_FOE_IB2_NBQ, info.idx) | FIELD_PREP(AIROHA_FOE_IB2_PSE_PORT, FE_PSE_PORT_CDM4); @@ -1081,6 +1082,7 @@ static int airoha_ppe_flow_offload_replace(struct airoha_eth *eth, struct flow_action_entry *act; struct airoha_foe_entry hwe; int err, i, offload_type; + __be16 ether_type = 0; u16 addr_type = 0; u8 l4proto = 0; @@ -1107,6 +1109,7 @@ static int airoha_ppe_flow_offload_replace(struct airoha_eth *eth, struct flow_match_basic match; flow_rule_match_basic(rule, &match); + ether_type = match.key->n_proto; l4proto = match.key->ip_proto; } else { return -EOPNOTSUPP; @@ -1177,7 +1180,7 @@ static int airoha_ppe_flow_offload_replace(struct airoha_eth *eth, return -EINVAL; err = airoha_ppe_foe_entry_prepare(eth, &hwe, odev, offload_type, - &data, l4proto); + &data, ether_type, l4proto); if (err) return err; diff --git a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c index cc8c4ef8038f..2601c17b29c8 100644 --- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c +++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c @@ -89,7 +89,8 @@ mtk_flow_offload_mangle_eth(const struct flow_action_entry *act, void *eth) } static int -mtk_flow_get_wdma_info(struct net_device *dev, const u8 *addr, struct mtk_wdma_info *info) +mtk_flow_get_wdma_info(struct net_device *dev, const u8 *addr, + __be16 ether_type, struct mtk_wdma_info *info) { struct net_device_path_stack stack; struct net_device_path *path; @@ -102,7 +103,7 @@ mtk_flow_get_wdma_info(struct net_device *dev, const u8 *addr, struct mtk_wdma_i return -1; rcu_read_lock(); - err = dev_fill_forward_path(dev, addr, &stack); + err = dev_fill_forward_path(dev, addr, ether_type, &stack); rcu_read_unlock(); if (err) return err; @@ -190,12 +191,12 @@ mtk_flow_get_dsa_port(struct net_device **dev) static int mtk_flow_set_output_device(struct mtk_eth *eth, struct mtk_foe_entry *foe, struct net_device *dev, const u8 *dest_mac, - int *wed_index) + __be16 ether_type, int *wed_index) { struct mtk_wdma_info info = {}; int pse_port, dsa_port, queue; - if (mtk_flow_get_wdma_info(dev, dest_mac, &info) == 0) { + if (mtk_flow_get_wdma_info(dev, dest_mac, ether_type, &info) == 0) { mtk_foe_entry_set_wdma(eth, foe, info.wdma_idx, info.queue, info.bss, info.wcid, info.amsdu); if (mtk_is_netsys_v2_or_greater(eth)) { @@ -273,6 +274,7 @@ mtk_flow_offload_replace(struct mtk_eth *eth, struct flow_cls_offload *f, struct mtk_flow_data data = {}; struct mtk_foe_entry foe; struct mtk_flow_entry *entry; + __be16 ether_type = 0; int offload_type = 0; int wed_index = -1; u16 addr_type = 0; @@ -319,6 +321,7 @@ mtk_flow_offload_replace(struct mtk_eth *eth, struct flow_cls_offload *f, struct flow_match_basic match; flow_rule_match_basic(rule, &match); + ether_type = match.key->n_proto; l4proto = match.key->ip_proto; } else { return -EOPNOTSUPP; @@ -481,7 +484,7 @@ mtk_flow_offload_replace(struct mtk_eth *eth, struct flow_cls_offload *f, mtk_foe_entry_set_pppoe(eth, &foe, data.pppoe.sid); err = mtk_flow_set_output_device(eth, &foe, odev, data.eth.h_dest, - &wed_index); + ether_type, &wed_index); if (err) return err; diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 8db25b79573e..3ed53f390606 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -938,6 +938,7 @@ struct net_device_path_stack { struct net_device_path_ctx { const struct net_device *dev; u8 daddr[ETH_ALEN]; + __be16 ether_type; int num_vlans; struct { @@ -3425,7 +3426,8 @@ void dev_remove_offload(struct packet_offload *po); int dev_get_iflink(const struct net_device *dev); int dev_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb); -int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr, +int dev_fill_forward_path(const struct net_device *dev, + const u8 *daddr, __be16 ether_type, struct net_device_path_stack *stack); struct net_device *dev_get_by_name(struct net *net, const char *name); struct net_device *dev_get_by_name_rcu(struct net *net, const char *name); diff --git a/net/core/dev.c b/net/core/dev.c index 7c21bc0a1e34..e66447ccf0ea 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -750,12 +750,14 @@ static struct net_device_path *dev_fwd_path(struct net_device_path_stack *stack) return &stack->path[k]; } -int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr, +int dev_fill_forward_path(const struct net_device *dev, + const u8 *daddr, __be16 ether_type, struct net_device_path_stack *stack) { const struct net_device *last_dev; struct net_device_path_ctx ctx = { - .dev = dev, + .dev = dev, + .ether_type = ether_type, }; struct net_device_path *path; int ret = 0; diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index bf8e40af60b0..38da07101601 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -1863,7 +1863,10 @@ static int ip6_tnl_fill_forward_path(struct net_device_path_ctx *ctx, path->type = DEV_PATH_TUN; path->tun.src_v6 = t->parms.laddr; path->tun.dst_v6 = t->parms.raddr; - path->tun.l3_proto = IPPROTO_IPV6; + if (ctx->ether_type == cpu_to_be16(ETH_P_IP)) + path->tun.l3_proto = IPPROTO_IPIP; + else + path->tun.l3_proto = IPPROTO_IPV6; path->dev = ctx->dev; ctx->dev = dst->dev; } diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c index 98c03b487f52..c8011ec36532 100644 --- a/net/netfilter/nf_flow_table_path.c +++ b/net/netfilter/nf_flow_table_path.c @@ -45,7 +45,8 @@ static bool nft_is_valid_ether_device(const struct net_device *dev) static int nft_dev_fill_forward_path(const struct nf_flow_route *route, const struct dst_entry *dst_cache, const struct nf_conn *ct, - enum ip_conntrack_dir dir, u8 *ha, + enum ip_conntrack_dir dir, + u8 *ha, __be16 ether_type, struct net_device_path_stack *stack) { const void *daddr = &ct->tuplehash[!dir].tuple.src.u3; @@ -72,7 +73,7 @@ static int nft_dev_fill_forward_path(const struct nf_flow_route *route, return -1; out: - return dev_fill_forward_path(dev, ha, stack); + return dev_fill_forward_path(dev, ha, ether_type, stack); } struct nft_forward_info { @@ -255,7 +256,8 @@ static int nft_dev_forward_path(const struct nft_pktinfo *pkt, unsigned char ha[ETH_ALEN]; int i; - if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, &stack) < 0 || + if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, pkt->ethertype, + &stack) < 0 || nft_dev_path_info(&stack, &info, ha, &ft->data) < 0) return -ENOENT; -- 2.55.0 Add encap_proto (AF_INET or AF_INET6) to struct flow_offload_tunnel to allow its use as part of the hash table key during flowtable entry lookup. This is a preliminary change to support IPv4 over IPv6 tunneling via the flowtable infrastructure for software acceleration. Signed-off-by: Lorenzo Bianconi --- include/linux/netdevice.h | 1 + include/net/netfilter/nf_flow_table.h | 1 + net/ipv4/ipip.c | 1 + net/ipv6/ip6_tunnel.c | 1 + net/netfilter/nf_flow_table_ip.c | 2 ++ net/netfilter/nf_flow_table_path.c | 2 ++ 6 files changed, 8 insertions(+) diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 3ed53f390606..0630c07e543e 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -902,6 +902,7 @@ struct net_device_path { }; u8 l3_proto; + u8 encap_proto; } tun; struct { enum { diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h index ce414118962f..bae0f55743e9 100644 --- a/include/net/netfilter/nf_flow_table.h +++ b/include/net/netfilter/nf_flow_table.h @@ -118,6 +118,7 @@ struct flow_offload_tunnel { }; u8 l3_proto; + u8 encap_proto; }; struct flow_offload_tuple { diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c index d1aa048a6099..4d2195b4ae3e 100644 --- a/net/ipv4/ipip.c +++ b/net/ipv4/ipip.c @@ -370,6 +370,7 @@ static int ipip_fill_forward_path(struct net_device_path_ctx *ctx, path->tun.src_v4.s_addr = tiph->saddr; path->tun.dst_v4.s_addr = tiph->daddr; path->tun.l3_proto = IPPROTO_IPIP; + path->tun.encap_proto = AF_INET; path->dev = ctx->dev; ctx->dev = rt->dst.dev; diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index 38da07101601..a121f715afd2 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -1863,6 +1863,7 @@ static int ip6_tnl_fill_forward_path(struct net_device_path_ctx *ctx, path->type = DEV_PATH_TUN; path->tun.src_v6 = t->parms.laddr; path->tun.dst_v6 = t->parms.raddr; + path->tun.encap_proto = AF_INET6; if (ctx->ether_type == cpu_to_be16(ETH_P_IP)) path->tun.l3_proto = IPPROTO_IPIP; else diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c index 0b78decce8a9..f29c69c362f5 100644 --- a/net/netfilter/nf_flow_table_ip.c +++ b/net/netfilter/nf_flow_table_ip.c @@ -198,6 +198,7 @@ static void nf_flow_tuple_encap(struct nf_flowtable_ctx *ctx, tuple->tun.dst_v4.s_addr = iph->daddr; tuple->tun.src_v4.s_addr = iph->saddr; tuple->tun.l3_proto = IPPROTO_IPIP; + tuple->tun.encap_proto = AF_INET; } break; case htons(ETH_P_IPV6): @@ -206,6 +207,7 @@ static void nf_flow_tuple_encap(struct nf_flowtable_ctx *ctx, tuple->tun.dst_v6 = ip6h->daddr; tuple->tun.src_v6 = ip6h->saddr; tuple->tun.l3_proto = IPPROTO_IPV6; + tuple->tun.encap_proto = AF_INET6; } break; default: diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c index c8011ec36532..caaf48c5fd2a 100644 --- a/net/netfilter/nf_flow_table_path.c +++ b/net/netfilter/nf_flow_table_path.c @@ -129,6 +129,7 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack, info->tun.src_v6 = path->tun.src_v6; info->tun.dst_v6 = path->tun.dst_v6; info->tun.l3_proto = path->tun.l3_proto; + info->tun.encap_proto = path->tun.encap_proto; info->num_tuns++; } else { if (info->num_encaps >= NF_FLOW_TABLE_ENCAP_MAX) @@ -278,6 +279,7 @@ static int nft_dev_forward_path(const struct nft_pktinfo *pkt, route->tuple[!dir].in.tun.src_v6 = info.tun.dst_v6; route->tuple[!dir].in.tun.dst_v6 = info.tun.src_v6; route->tuple[!dir].in.tun.l3_proto = info.tun.l3_proto; + route->tuple[!dir].in.tun.encap_proto = info.tun.encap_proto; route->tuple[!dir].in.num_tuns = info.num_tuns; } -- 2.55.0 Introduce sw flowtable acceleration for the TX/RX paths of IPv4 over IPv6 tunnels, relying on the netfilter flowtable infrastructure. The feature can be tested with a forwarding scenario between two NICs (eth0 and eth1), where an IPv4 over IPv6 tunnel is used to reach a remote site via eth1 as the underlay device: ETH0 -- TUN0 <==> ETH1 -- [IP network] -- TUN1 (2001:db8:2::2) [IP configuration] 6: eth0: mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 00:00:22:33:11:55 brd ff:ff:ff:ff:ff:ff inet 192.168.0.2/24 scope global eth0 valid_lft forever preferred_lft forever 7: eth1: mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 00:11:22:33:11:55 brd ff:ff:ff:ff:ff:ff inet6 2001:db8:2::1/64 scope global nodad valid_lft forever preferred_lft forever 8: tun0@NONE: mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000 link/tunnel6 2001:db8:2::1 peer 2001:db8:2::2 permaddr ce9c:2940:7dcc:: inet 192.168.100.1/24 scope global tun0 valid_lft forever preferred_lft forever $ ip route show default via 192.168.100.2 dev tun0 192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.2 192.168.100.0/24 dev tun0 proto kernel scope link src 192.168.100.1 $ ip -6 route show 2001:db8:2::/64 dev eth1 proto kernel metric 256 pref medium $ nft list ruleset table inet filter { flowtable ft { hook ingress priority filter devices = { eth0, eth1 } } chain forward { type filter hook forward priority filter; policy accept; meta l4proto { tcp, udp } flow add @ft } } When reproducing this scenario using veth interfaces, the following results were observed: - TCP stream received from IPv4 over IPv6 tunnel: - net-next (baseline): ~126 Gbps - net-next + IP6IP flowtable support: ~138 Gbps - TCP stream transmitted to IPv4 over IPv6 tunnel: - net-next (baseline): ~127 Gbps - net-next + IP6IP flowtable support: ~140 Gbps Signed-off-by: Lorenzo Bianconi --- net/netfilter/nf_flow_table_core.c | 19 +++- net/netfilter/nf_flow_table_ip.c | 190 +++++++++++++++++++++++++------------ net/netfilter/nf_flow_table_path.c | 10 +- 3 files changed, 147 insertions(+), 72 deletions(-) diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c index 2a829b5e8240..56e58926fd31 100644 --- a/net/netfilter/nf_flow_table_core.c +++ b/net/netfilter/nf_flow_table_core.c @@ -76,9 +76,14 @@ struct flow_offload *flow_offload_alloc(struct nf_conn *ct) } EXPORT_SYMBOL_GPL(flow_offload_alloc); -static u32 flow_offload_dst_cookie(struct flow_offload_tuple *flow_tuple) +static u32 flow_offload_dst_cookie(struct flow_offload_tuple *flow_tuple, + u8 tun_encap_proto) { - if (flow_tuple->l3proto == NFPROTO_IPV6) + bool dst_v6; + + dst_v6 = tun_encap_proto ? tun_encap_proto == NFPROTO_IPV6 + : flow_tuple->l3proto == NFPROTO_IPV6; + if (dst_v6) return rt6_get_cookie(dst_rt6_info(flow_tuple->dst_cache)); return 0; @@ -99,10 +104,12 @@ static int flow_offload_fill_route(struct flow_offload *flow, enum flow_offload_tuple_dir dir) { struct flow_offload_tuple *flow_tuple = &flow->tuplehash[dir].tuple; + u8 l3proto, encap_proto = route->tuple[!dir].in.tun.encap_proto; struct dst_entry *dst = nft_route_dst_fetch(route, dir); int i, j = 0; - switch (flow_tuple->l3proto) { + l3proto = encap_proto ? encap_proto : flow_tuple->l3proto; + switch (l3proto) { case NFPROTO_IPV4: flow_tuple->mtu = ip_dst_mtu_maybe_forward(dst, true); break; @@ -130,7 +137,8 @@ static int flow_offload_fill_route(struct flow_offload *flow, if (flow_tuple->tun_num) { flow_tuple->dst_cache = dst; flow_tuple->dst_cookie = - flow_offload_dst_cookie(flow_tuple); + flow_offload_dst_cookie(flow_tuple, + encap_proto); } memcpy(flow_tuple->out.h_dest, route->tuple[dir].out.h_dest, ETH_ALEN); @@ -144,7 +152,8 @@ static int flow_offload_fill_route(struct flow_offload *flow, case FLOW_OFFLOAD_XMIT_NEIGH: flow_tuple->ifidx = route->tuple[dir].out.ifindex; flow_tuple->dst_cache = dst; - flow_tuple->dst_cookie = flow_offload_dst_cookie(flow_tuple); + flow_tuple->dst_cookie = flow_offload_dst_cookie(flow_tuple, + encap_proto); break; default: WARN_ON_ONCE(1); diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c index f29c69c362f5..9432aa140657 100644 --- a/net/netfilter/nf_flow_table_ip.c +++ b/net/netfilter/nf_flow_table_ip.c @@ -191,27 +191,27 @@ static void nf_flow_tuple_encap(struct nf_flowtable_ctx *ctx, break; } - switch (inner_proto) { - case htons(ETH_P_IP): - iph = (struct iphdr *)(skb_network_header(skb) + offset); - if (ctx->tun.proto == IPPROTO_IPIP) { + if (ctx->tun.proto == IPPROTO_IPIP || ctx->tun.proto == IPPROTO_IPV6) { + switch (inner_proto) { + case htons(ETH_P_IP): + iph = (struct iphdr *)(skb_network_header(skb) + + offset); tuple->tun.dst_v4.s_addr = iph->daddr; tuple->tun.src_v4.s_addr = iph->saddr; - tuple->tun.l3_proto = IPPROTO_IPIP; + tuple->tun.l3_proto = ctx->tun.proto; tuple->tun.encap_proto = AF_INET; - } - break; - case htons(ETH_P_IPV6): - ip6h = (struct ipv6hdr *)(skb_network_header(skb) + offset); - if (ctx->tun.proto == IPPROTO_IPV6) { + break; + case htons(ETH_P_IPV6): + ip6h = (struct ipv6hdr *)(skb_network_header(skb) + + offset); tuple->tun.dst_v6 = ip6h->daddr; tuple->tun.src_v6 = ip6h->saddr; - tuple->tun.l3_proto = IPPROTO_IPV6; + tuple->tun.l3_proto = ctx->tun.proto; tuple->tun.encap_proto = AF_INET6; + break; + default: + break; } - break; - default: - break; } } @@ -362,7 +362,7 @@ static bool nf_flow_ip6_tunnel_proto(struct nf_flowtable_ctx *ctx, if (ipv6_ext_hdr(ip6h->nexthdr)) return false; - if (ip6h->nexthdr == IPPROTO_IPV6) { + if (ip6h->nexthdr == IPPROTO_IPIP || ip6h->nexthdr == IPPROTO_IPV6) { ctx->tun.proto = ip6h->nexthdr; ctx->tun.hdr_size = sizeof(*ip6h); ctx->offset += ctx->tun.hdr_size; @@ -383,6 +383,10 @@ static void nf_flow_ip_tunnel_pop(struct nf_flowtable_ctx *ctx, skb_pull(skb, ctx->tun.hdr_size); skb_reset_network_header(skb); + if (ctx->tun.proto == IPPROTO_IPIP) + skb->protocol = htons(ETH_P_IP); + else + skb->protocol = htons(ETH_P_IPV6); } static bool nf_flow_skb_encap_protocol(struct nf_flowtable_ctx *ctx, @@ -488,8 +492,16 @@ static int nf_flow_offload_forward(struct nf_flowtable_ctx *ctx, flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]); mtu = flow->tuplehash[dir].tuple.mtu + ctx->offset; - if (flow->tuplehash[!dir].tuple.tun_num) + switch (flow->tuplehash[!dir].tuple.tun.encap_proto) { + case AF_INET: mtu -= sizeof(*iph); + break; + case AF_INET6: + mtu -= sizeof(struct ipv6hdr); + break; + default: + break; + } if (unlikely(nf_flow_exceeds_mtu(skb, mtu))) return 0; @@ -616,6 +628,7 @@ static int nf_flow_tunnel_ipip_push(struct net *net, struct sk_buff *skb, /* Push down and install the IP header. */ skb_push(skb, sizeof(*iph)); skb_reset_network_header(skb); + skb->protocol = htons(ETH_P_IP); iph = ip_hdr(skb); iph->version = 4; @@ -635,57 +648,57 @@ static int nf_flow_tunnel_ipip_push(struct net *net, struct sk_buff *skb, return 0; } -static int nf_flow_tunnel_v4_push(struct net *net, struct sk_buff *skb, - struct flow_offload_tuple *tuple, - struct dst_entry *dst, __be32 *ip_daddr) +static int nf_flow_tunnel_ip6_push(struct net *net, struct sk_buff *skb, + struct flow_offload_tuple *tuple, + struct dst_entry *dst, + struct in6_addr **ip6_daddr) { - if (tuple->tun_num) - return nf_flow_tunnel_ipip_push(net, skb, tuple, dst, ip_daddr); - - return 0; -} - -static int nf_flow_tunnel_ip6ip6_push(struct net *net, struct sk_buff *skb, - struct flow_offload_tuple *tuple, - struct dst_entry *dst, - struct in6_addr **ip6_daddr) -{ - struct ipv6hdr *ip6h = (struct ipv6hdr *)skb_network_header(skb); - __u8 dsfield = ipv6_get_dsfield(ip6h); - struct rtable *rt = dst_rtable(dst); struct flowi6 fl6 = { .daddr = tuple->tun.src_v6, .saddr = tuple->tun.dst_v6, - .flowi6_proto = IPPROTO_IPV6, + .flowi6_proto = tuple->tun.l3_proto, }; - u8 hop_limit = ip6h->hop_limit; + u8 hop_limit, dsfield; + struct ipv6hdr *ip6h; int err, mtu; u32 headroom; + if (tuple->tun.l3_proto == IPPROTO_IPIP) { + struct iphdr *iph = (struct iphdr *)skb_network_header(skb); + + dsfield = ipv4_get_dsfield(iph); + hop_limit = iph->ttl; + } else { + ip6h = (struct ipv6hdr *)skb_network_header(skb); + dsfield = ipv6_get_dsfield(ip6h); + hop_limit = ip6h->hop_limit; + } + err = iptunnel_handle_offloads(skb, SKB_GSO_IPXIP6); if (err) return err; - skb_set_inner_ipproto(skb, IPPROTO_IPV6); - headroom = sizeof(*ip6h) + LL_RESERVED_SPACE(rt->dst.dev) + - rt->dst.header_len; + skb_set_inner_ipproto(skb, tuple->tun.l3_proto); + headroom = sizeof(*ip6h) + LL_RESERVED_SPACE(dst->dev) + + dst->header_len; err = skb_cow_head(skb, headroom); if (err) return err; skb_scrub_packet(skb, true); - mtu = dst_mtu(&rt->dst) - sizeof(*ip6h); + mtu = dst_mtu(dst) - sizeof(*ip6h); mtu = max(mtu, IPV6_MIN_MTU); skb_dst_update_pmtu_no_confirm(skb, mtu); skb_push(skb, sizeof(*ip6h)); skb_reset_network_header(skb); + skb->protocol = htons(ETH_P_IPV6); ip6h = ipv6_hdr(skb); ip6_flow_hdr(ip6h, dsfield, ip6_make_flowlabel(net, skb, fl6.flowlabel, true, &fl6)); ip6h->hop_limit = hop_limit; - ip6h->nexthdr = IPPROTO_IPV6; + ip6h->nexthdr = tuple->tun.l3_proto; ip6h->daddr = tuple->tun.src_v6; ip6h->saddr = tuple->tun.dst_v6; ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(*ip6h)); @@ -696,15 +709,21 @@ static int nf_flow_tunnel_ip6ip6_push(struct net *net, struct sk_buff *skb, return 0; } -static int nf_flow_tunnel_v6_push(struct net *net, struct sk_buff *skb, - struct flow_offload_tuple *tuple, - struct dst_entry *dst, - struct in6_addr **ip6_daddr) +static int nf_flow_tunnel_push(struct net *net, struct sk_buff *skb, + struct flow_offload_tuple *tuple, + struct dst_entry *dst, __be32 *ip_daddr, + struct in6_addr **ip6_daddr) { - if (tuple->tun_num) - return nf_flow_tunnel_ip6ip6_push(net, skb, tuple, dst, ip6_daddr); - - return 0; + switch (tuple->tun.encap_proto) { + case AF_INET: + return nf_flow_tunnel_ipip_push(net, skb, tuple, dst, + ip_daddr); + case AF_INET6: + return nf_flow_tunnel_ip6_push(net, skb, tuple, dst, + ip6_daddr); + default: + return 0; + } } static int nf_flow_encap_push(struct sk_buff *skb, @@ -816,6 +835,7 @@ nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb, .in = state->in, }; struct nf_flow_xmit xmit = {}; + struct in6_addr *ip6_daddr; struct flow_offload *flow; struct neighbour *neigh; struct rtable *rt; @@ -844,27 +864,45 @@ nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb, flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]); other_tuple = &flow->tuplehash[!dir].tuple; ip_daddr = other_tuple->src_v4.s_addr; + ip6_daddr = &other_tuple->src_v6; - if (nf_flow_tunnel_v4_push(state->net, skb, other_tuple, - tuplehash->tuple.dst_cache, &ip_daddr) < 0) + if (nf_flow_tunnel_push(state->net, skb, other_tuple, + tuplehash->tuple.dst_cache, + &ip_daddr, &ip6_daddr) < 0) return NF_DROP; switch (tuplehash->tuple.xmit_type) { - case FLOW_OFFLOAD_XMIT_NEIGH: - rt = dst_rtable(tuplehash->tuple.dst_cache); + case FLOW_OFFLOAD_XMIT_NEIGH: { + struct dst_entry *dst; + xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.ifidx); if (!xmit.outdev) { flow_offload_teardown(flow); return NF_DROP; } - neigh = ip_neigh_gw4(rt->dst.dev, rt_nexthop(rt, ip_daddr)); + if (other_tuple->tun.encap_proto ? + other_tuple->tun.encap_proto == AF_INET6 : + ctx.tun.proto == IPPROTO_IPV6) { + struct rt6_info *rt6; + + rt6 = dst_rt6_info(tuplehash->tuple.dst_cache); + neigh = ip_neigh_gw6(rt6->dst.dev, + rt6_nexthop(rt6, ip6_daddr)); + dst = &rt6->dst; + } else { + rt = dst_rtable(tuplehash->tuple.dst_cache); + neigh = ip_neigh_gw4(rt->dst.dev, + rt_nexthop(rt, ip_daddr)); + dst = &rt->dst; + } if (IS_ERR(neigh)) { flow_offload_teardown(flow); return NF_DROP; } xmit.dest = neigh->ha; - skb_dst_set_noref(skb, &rt->dst); + skb_dst_set_noref(skb, dst); break; + } case FLOW_OFFLOAD_XMIT_DIRECT: xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.out.ifidx); if (!xmit.outdev) { @@ -1114,8 +1152,12 @@ nf_flow_offload_ipv6_lookup(struct nf_flowtable_ctx *ctx, if (!nf_flow_skb_encap_protocol(ctx, skb, htons(ETH_P_IPV6))) return NULL; - if (nf_flow_tuple_ipv6(ctx, skb, &tuple) < 0) + if (ctx->tun.proto == IPPROTO_IPIP) { + if (nf_flow_tuple_ip(ctx, skb, &tuple) < 0) + return NULL; + } else if (nf_flow_tuple_ipv6(ctx, skb, &tuple) < 0) { return NULL; + } return flow_offload_lookup(flow_table, &tuple); } @@ -1136,13 +1178,18 @@ nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb, struct flow_offload *flow; struct neighbour *neigh; struct rt6_info *rt; + __be32 ip_daddr; int ret; tuplehash = nf_flow_offload_ipv6_lookup(&ctx, flow_table, skb); if (tuplehash == NULL) return NF_ACCEPT; - ret = nf_flow_offload_ipv6_forward(&ctx, flow_table, tuplehash, skb); + if (ctx.tun.proto == IPPROTO_IPIP) + ret = nf_flow_offload_forward(&ctx, flow_table, tuplehash, skb); + else + ret = nf_flow_offload_ipv6_forward(&ctx, flow_table, tuplehash, + skb); if (ret < 0) return NF_DROP; else if (ret == 0) @@ -1159,29 +1206,46 @@ nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb, dir = tuplehash->tuple.dir; flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]); other_tuple = &flow->tuplehash[!dir].tuple; + ip_daddr = other_tuple->src_v4.s_addr; ip6_daddr = &other_tuple->src_v6; - if (nf_flow_tunnel_v6_push(state->net, skb, other_tuple, - tuplehash->tuple.dst_cache, - &ip6_daddr) < 0) + if (nf_flow_tunnel_push(state->net, skb, other_tuple, + tuplehash->tuple.dst_cache, + &ip_daddr, &ip6_daddr) < 0) return NF_DROP; switch (tuplehash->tuple.xmit_type) { - case FLOW_OFFLOAD_XMIT_NEIGH: - rt = dst_rt6_info(tuplehash->tuple.dst_cache); + case FLOW_OFFLOAD_XMIT_NEIGH: { + struct dst_entry *dst; + xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.ifidx); if (!xmit.outdev) { flow_offload_teardown(flow); return NF_DROP; } - neigh = ip_neigh_gw6(rt->dst.dev, rt6_nexthop(rt, ip6_daddr)); + if (other_tuple->tun.encap_proto ? + other_tuple->tun.encap_proto == AF_INET : + ctx.tun.proto == IPPROTO_IPIP) { + struct rtable *rt4; + + rt4 = dst_rtable(tuplehash->tuple.dst_cache); + neigh = ip_neigh_gw4(rt4->dst.dev, + rt_nexthop(rt4, ip_daddr)); + dst = &rt4->dst; + } else { + rt = dst_rt6_info(tuplehash->tuple.dst_cache); + neigh = ip_neigh_gw6(rt->dst.dev, + rt6_nexthop(rt, ip6_daddr)); + dst = &rt->dst; + } if (IS_ERR(neigh)) { flow_offload_teardown(flow); return NF_DROP; } xmit.dest = neigh->ha; - skb_dst_set_noref(skb, &rt->dst); + skb_dst_set_noref(skb, dst); break; + } case FLOW_OFFLOAD_XMIT_DIRECT: xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.out.ifidx); if (!xmit.outdev) { diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c index caaf48c5fd2a..5e84b7f18a26 100644 --- a/net/netfilter/nf_flow_table_path.c +++ b/net/netfilter/nf_flow_table_path.c @@ -216,12 +216,13 @@ static int nft_flow_tunnel_update_route(const struct nft_pktinfo *pkt, struct dst_entry *tun_dst = NULL; struct flowi fl = {}; - switch (nft_pf(pkt)) { + switch (tun->encap_proto) { case NFPROTO_IPV4: fl.u.ip4.daddr = tun->dst_v4.s_addr; fl.u.ip4.saddr = tun->src_v4.s_addr; fl.u.ip4.flowi4_iif = nft_in(pkt)->ifindex; - fl.u.ip4.flowi4_dscp = ip4h_dscp(ip_hdr(pkt->skb)); + if (nft_pf(pkt) == NFPROTO_IPV4) + fl.u.ip4.flowi4_dscp = ip4h_dscp(ip_hdr(pkt->skb)); fl.u.ip4.flowi4_mark = pkt->skb->mark; fl.u.ip4.flowi4_flags = FLOWI_FLAG_ANYSRC; break; @@ -229,13 +230,14 @@ static int nft_flow_tunnel_update_route(const struct nft_pktinfo *pkt, fl.u.ip6.daddr = tun->dst_v6; fl.u.ip6.saddr = tun->src_v6; fl.u.ip6.flowi6_iif = nft_in(pkt)->ifindex; - fl.u.ip6.flowlabel = ip6_flowinfo(ipv6_hdr(pkt->skb)); + if (nft_pf(pkt) == NFPROTO_IPV6) + fl.u.ip6.flowlabel = ip6_flowinfo(ipv6_hdr(pkt->skb)); fl.u.ip6.flowi6_mark = pkt->skb->mark; fl.u.ip6.flowi6_flags = FLOWI_FLAG_ANYSRC; break; } - nf_route(nft_net(pkt), &tun_dst, &fl, false, nft_pf(pkt)); + nf_route(nft_net(pkt), &tun_dst, &fl, false, tun->encap_proto); if (!tun_dst) return -ENOENT; -- 2.55.0 Similar to IPIP and IP6IP6, introduce specific selftest for IPv4 over IPv6 flowtable sw acceleration in nft_flowtable.sh Signed-off-by: Lorenzo Bianconi --- .../selftests/net/netfilter/nft_flowtable.sh | 33 +++++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/net/netfilter/nft_flowtable.sh b/tools/testing/selftests/net/netfilter/nft_flowtable.sh index d9a21ca8ed2c..1cb5c65351b7 100755 --- a/tools/testing/selftests/net/netfilter/nft_flowtable.sh +++ b/tools/testing/selftests/net/netfilter/nft_flowtable.sh @@ -579,9 +579,8 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 ""; then ip netns exec "$nsr1" nft list ruleset fi -# IPIP tunnel test: -# Add IPIP tunnel interfaces and check flowtable acceleration. -test_ipip() { +# IP tunnel tests: +test_ip_tnls() { if ! ip -net "$nsr1" link add name tun0 type ipip \ local 192.168.10.1 remote 192.168.10.2 >/dev/null;then echo "SKIP: could not add ipip tunnel" @@ -594,7 +593,9 @@ ip netns exec "$nsr1" sysctl net.ipv4.conf.tun0.forwarding=1 > /dev/null ip -net "$nsr1" link add name tun6 type ip6tnl local fee1:2::1 remote fee1:2::2 encaplimit none ip -net "$nsr1" link set tun6 up +ip -net "$nsr1" addr add 192.168.210.1/24 dev tun6 ip -net "$nsr1" addr add fee1:3::1/64 dev tun6 nodad +ip netns exec "$nsr1" sysctl net.ipv4.conf.tun6.forwarding=1 > /dev/null ip -net "$nsr2" link add name tun0 type ipip local 192.168.10.2 remote 192.168.10.1 ip -net "$nsr2" link set tun0 up @@ -603,7 +604,9 @@ ip netns exec "$nsr2" sysctl net.ipv4.conf.tun0.forwarding=1 > /dev/null ip -net "$nsr2" link add name tun6 type ip6tnl local fee1:2::2 remote fee1:2::1 encaplimit none || ret=1 ip -net "$nsr2" link set tun6 up +ip -net "$nsr2" addr add 192.168.210.2/24 dev tun6 ip -net "$nsr2" addr add fee1:3::2/64 dev tun6 nodad +ip netns exec "$nsr2" sysctl net.ipv4.conf.tun6.forwarding=1 > /dev/null ip -net "$nsr1" route change default via 192.168.100.2 ip -net "$nsr2" route change default via 192.168.100.1 @@ -640,6 +643,15 @@ else ret=1 fi +ip -net "$nsr1" route change default via 192.168.210.2 +ip -net "$nsr2" route change default via 192.168.210.1 + +if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IP4IP6 tunnel"; then + echo "FAIL: flow offload for ns1/ns2 with IP4IP6 tunnel" 1>&2 + ip netns exec "$nsr1" nft list ruleset + ret=1 +fi + # Create vlan tagged devices for IPIP traffic. ip -net "$nsr1" link add link veth1 name veth1.10 type vlan id 10 ip -net "$nsr1" link set veth1.10 up @@ -661,7 +673,9 @@ ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun0.10 a ip -net "$nsr1" link add name tun6.10 type ip6tnl local fee1:4::1 remote fee1:4::2 encaplimit none ip -net "$nsr1" link set tun6.10 up +ip -net "$nsr1" addr add 192.168.220.1/24 dev tun6.10 ip -net "$nsr1" addr add fee1:5::1/64 dev tun6.10 nodad +ip netns exec "$nsr1" sysctl net.ipv4.conf.tun6/10.forwarding=1 > /dev/null ip -6 -net "$nsr1" route delete default ip -6 -net "$nsr1" route add default via fee1:5::2 ip netns exec "$nsr1" nft -a insert rule inet filter forward \ @@ -682,7 +696,9 @@ ip netns exec "$nsr2" sysctl net.ipv4.conf.tun0/10.forwarding=1 > /dev/null ip -net "$nsr2" link add name tun6.10 type ip6tnl local fee1:4::2 remote fee1:4::1 encaplimit none || ret=1 ip -net "$nsr2" link set tun6.10 up +ip -net "$nsr2" addr add 192.168.220.2/24 dev tun6.10 ip -net "$nsr2" addr add fee1:5::2/64 dev tun6.10 nodad +ip netns exec "$nsr2" sysctl net.ipv4.conf.tun6/10.forwarding=1 > /dev/null ip -6 -net "$nsr2" route delete default ip -6 -net "$nsr2" route add default via fee1:5::1 @@ -700,6 +716,15 @@ else ret=1 fi +ip -net "$nsr1" route change default via 192.168.220.2 +ip -net "$nsr2" route change default via 192.168.220.1 + +if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IP4IP6 tunnel over vlan"; then + echo "FAIL: flow offload for ns1/ns2 with IP4IP6 tunnel over vlan" 1>&2 + ip netns exec "$nsr1" nft list ruleset + ret=1 +fi + # Restore the previous configuration ip -net "$nsr1" route change default via 192.168.10.2 ip -net "$nsr2" route change default via 192.168.10.1 @@ -792,7 +817,7 @@ ip -net "$nsr1" addr add dead:1::1/64 dev veth0 nodad ip -net "$nsr1" link set up dev veth0 } -test_ipip +test_ip_tnls test_bridge -- 2.55.0 Introduce sw flowtable acceleration for the TX/RX paths of SIT tunnels, relying on the netfilter flowtable infrastructure. The feature can be tested with a forwarding scenario between two NICs (eth0 and eth1), where a SIT tunnel is used to reach a remote site via eth1 as the underlay device: ETH0 -- TUN0 <==> ETH1 -- [IP network] -- TUN1 (192.168.2.2) [IP configuration] 6: eth0: mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 00:00:22:33:11:55 brd ff:ff:ff:ff:ff:ff inet6 2001:db8:1::2/64 scope global nodad valid_lft forever preferred_lft forever 7: eth1: mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 00:11:22:33:11:55 brd ff:ff:ff:ff:ff:ff inet 192.168.2.1/24 scope global eth1 valid_lft forever preferred_lft forever 8: tun0@NONE: mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000 link/sit 192.168.2.1 peer 192.168.2.2 inet6 2001:db8:200::1/64 scope global nodad valid_lft forever preferred_lft forever $ ip route show 192.168.2.0/24 dev eth1 proto kernel scope link src 192.168.2.1 $ ip -6 route show 2001:db8:1::/64 dev eth0 proto kernel metric 256 pref medium 2001:db8:200::/64 dev tun0 proto kernel metric 256 pref medium default via 2001:db8:200::2 dev tun0 metric 1024 pref medium $ nft list ruleset table inet filter { flowtable ft { hook ingress priority filter devices = { eth0, eth1 } } chain forward { type filter hook forward priority filter; policy accept; meta l4proto { tcp, udp } flow add @ft } } When reproducing this scenario using veth interfaces, the following results were observed: - TCP stream received from SIT tunnel: - net-next (baseline): ~118 Gbps - net-next + SIT flowtable support: ~148 Gbps - TCP stream transmitted to SIT tunnel: - net-next (baseline): ~131 Gbps - net-next + SIT flowtable support: ~147 Gbps Signed-off-by: Lorenzo Bianconi --- net/ipv6/sit.c | 60 ++++++++ net/netfilter/nf_flow_table_ip.c | 286 +++++++++++++++++++++------------------ 2 files changed, 218 insertions(+), 128 deletions(-) diff --git a/net/ipv6/sit.c b/net/ipv6/sit.c index a38b24fb8384..47150df789e7 100644 --- a/net/ipv6/sit.c +++ b/net/ipv6/sit.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -1365,6 +1366,64 @@ ipip6_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p, } } +static int ipip6_tunnel_fill_forward_path(struct net_device_path_ctx *ctx, + struct net_device_path *path) +{ + struct ip_tunnel *tunnel = netdev_priv(ctx->dev); + const struct iphdr *tiph = &tunnel->parms.iph; + struct rtable *rt; + + /* NBMA tunnels (ISATAP, 6to4, 6rd) resolve the outer destination + * per-packet from the inner IPv6 address; not offloadable. + */ + if (ctx->dev->priv_flags & IFF_ISATAP) + return -EOPNOTSUPP; + + if (!tiph->daddr) + return -EOPNOTSUPP; + + /* FOU/GUE encapsulation is handled in the slow path by + * ip_tunnel_encap(); the fast path only pushes a plain + * IPv4 header and would skip the UDP encapsulation. + */ + if (tunnel->encap.type != TUNNEL_ENCAP_NONE) + return -EOPNOTSUPP; + + rt = ip_route_output(dev_net(ctx->dev), tiph->daddr, tiph->saddr, + inet_dsfield_to_dscp(tiph->tos), + tunnel->parms.link, RT_SCOPE_UNIVERSE); + if (IS_ERR(rt)) + return PTR_ERR(rt); + + path->type = DEV_PATH_TUN; + if (tiph->saddr) + path->tun.src_v4.s_addr = tiph->saddr; + else + path->tun.src_v4.s_addr = inet_select_addr(rt->dst.dev, + tiph->daddr, + RT_SCOPE_UNIVERSE); + path->tun.dst_v4.s_addr = tiph->daddr; + path->tun.encap_proto = AF_INET; + path->dev = ctx->dev; + + switch (ctx->ether_type) { + case cpu_to_be16(ETH_P_IP): + path->tun.l3_proto = IPPROTO_IPIP; + break; + case cpu_to_be16(ETH_P_IPV6): + path->tun.l3_proto = IPPROTO_IPV6; + break; + default: + ip_rt_put(rt); + return -EOPNOTSUPP; + } + + ctx->dev = rt->dst.dev; + ip_rt_put(rt); + + return 0; +} + static int ipip6_tunnel_siocdevprivate(struct net_device *dev, struct ifreq *ifr, void __user *data, int cmd) @@ -1401,6 +1460,7 @@ static const struct net_device_ops ipip6_netdev_ops = { .ndo_siocdevprivate = ipip6_tunnel_siocdevprivate, .ndo_get_iflink = ip_tunnel_get_iflink, .ndo_tunnel_ctl = ipip6_tunnel_ctl, + .ndo_fill_forward_path = ipip6_tunnel_fill_forward_path, }; static void ipip6_dev_free(struct net_device *dev) diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c index 9432aa140657..03e399380937 100644 --- a/net/netfilter/nf_flow_table_ip.c +++ b/net/netfilter/nf_flow_table_ip.c @@ -337,7 +337,7 @@ static bool nf_flow_ip4_tunnel_proto(struct nf_flowtable_ctx *ctx, if (iph->ttl <= 1) return false; - if (iph->protocol == IPPROTO_IPIP) { + if (iph->protocol == IPPROTO_IPIP || iph->protocol == IPPROTO_IPV6) { ctx->tun.proto = iph->protocol; ctx->tun.hdr_size = size; ctx->offset += ctx->tun.hdr_size; @@ -463,21 +463,6 @@ static void nf_flow_encap_pop(struct nf_flowtable_ctx *ctx, nf_flow_ip_tunnel_pop(ctx, skb); } -static struct flow_offload_tuple_rhash * -nf_flow_offload_lookup(struct nf_flowtable_ctx *ctx, - struct nf_flowtable *flow_table, struct sk_buff *skb) -{ - struct flow_offload_tuple tuple = {}; - - if (!nf_flow_skb_encap_protocol(ctx, skb, htons(ETH_P_IP))) - return NULL; - - if (nf_flow_tuple_ip(ctx, skb, &tuple) < 0) - return NULL; - - return flow_offload_lookup(flow_table, &tuple); -} - static int nf_flow_offload_forward(struct nf_flowtable_ctx *ctx, struct nf_flowtable *flow_table, struct flow_offload_tuple_rhash *tuplehash, @@ -601,23 +586,38 @@ static int nf_flow_pppoe_push(struct sk_buff *skb, u16 id, return 0; } -static int nf_flow_tunnel_ipip_push(struct net *net, struct sk_buff *skb, - struct flow_offload_tuple *tuple, - struct dst_entry *dst, __be32 *ip_daddr) +static int nf_flow_tunnel_ip_push(struct net *net, struct sk_buff *skb, + struct flow_offload_tuple *tuple, + struct dst_entry *dst, __be32 *ip_daddr) { - struct iphdr *iph = (struct iphdr *)skb_network_header(skb); struct rtable *rt = dst_rtable(dst); - u8 tos = iph->tos, ttl = iph->ttl; - __be16 frag_off = iph->frag_off; - u32 headroom = sizeof(*iph); + __be16 frag_off = 0; + struct iphdr *iph; + u8 tos = 0, ttl; + u32 headroom; int err; + if (tuple->tun.l3_proto == IPPROTO_IPV6) { + struct ipv6hdr *ip6h; + + ip6h = (struct ipv6hdr *)skb_network_header(skb); + tos = ipv6_get_dsfield(ip6h); + ttl = ip6h->hop_limit; + frag_off = htons(IP_DF); + } else { + iph = (struct iphdr *)skb_network_header(skb); + frag_off = iph->frag_off; + tos = iph->tos; + ttl = iph->ttl; + } + err = iptunnel_handle_offloads(skb, SKB_GSO_IPXIP4); if (err) return err; - skb_set_inner_ipproto(skb, IPPROTO_IPIP); - headroom += LL_RESERVED_SPACE(rt->dst.dev) + rt->dst.header_len; + skb_set_inner_ipproto(skb, tuple->tun.l3_proto); + headroom = sizeof(*iph) + LL_RESERVED_SPACE(rt->dst.dev) + + rt->dst.header_len; err = skb_cow_head(skb, headroom); if (err) return err; @@ -716,8 +716,7 @@ static int nf_flow_tunnel_push(struct net *net, struct sk_buff *skb, { switch (tuple->tun.encap_proto) { case AF_INET: - return nf_flow_tunnel_ipip_push(net, skb, tuple, dst, - ip_daddr); + return nf_flow_tunnel_ip_push(net, skb, tuple, dst, ip_daddr); case AF_INET6: return nf_flow_tunnel_ip6_push(net, skb, tuple, dst, ip6_daddr); @@ -823,106 +822,6 @@ static unsigned int nf_flow_queue_xmit(struct net *net, struct sk_buff *skb, return NF_STOLEN; } -unsigned int -nf_flow_offload_ip_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 *other_tuple; - enum flow_offload_tuple_dir dir; - struct nf_flowtable_ctx ctx = { - .in = state->in, - }; - struct nf_flow_xmit xmit = {}; - struct in6_addr *ip6_daddr; - struct flow_offload *flow; - struct neighbour *neigh; - struct rtable *rt; - __be32 ip_daddr; - int ret; - - tuplehash = nf_flow_offload_lookup(&ctx, flow_table, skb); - if (!tuplehash) - return NF_ACCEPT; - - ret = nf_flow_offload_forward(&ctx, flow_table, tuplehash, skb); - if (ret < 0) - return NF_DROP; - else if (ret == 0) - return NF_ACCEPT; - - if (unlikely(tuplehash->tuple.xmit_type == FLOW_OFFLOAD_XMIT_XFRM)) { - rt = dst_rtable(tuplehash->tuple.dst_cache); - memset(skb->cb, 0, sizeof(struct inet_skb_parm)); - IPCB(skb)->iif = skb->dev->ifindex; - IPCB(skb)->flags = IPSKB_FORWARDED; - return nf_flow_xmit_xfrm(skb, state, &rt->dst); - } - - dir = tuplehash->tuple.dir; - flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]); - other_tuple = &flow->tuplehash[!dir].tuple; - ip_daddr = other_tuple->src_v4.s_addr; - ip6_daddr = &other_tuple->src_v6; - - if (nf_flow_tunnel_push(state->net, skb, other_tuple, - tuplehash->tuple.dst_cache, - &ip_daddr, &ip6_daddr) < 0) - return NF_DROP; - - switch (tuplehash->tuple.xmit_type) { - case FLOW_OFFLOAD_XMIT_NEIGH: { - struct dst_entry *dst; - - xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.ifidx); - if (!xmit.outdev) { - flow_offload_teardown(flow); - return NF_DROP; - } - if (other_tuple->tun.encap_proto ? - other_tuple->tun.encap_proto == AF_INET6 : - ctx.tun.proto == IPPROTO_IPV6) { - struct rt6_info *rt6; - - rt6 = dst_rt6_info(tuplehash->tuple.dst_cache); - neigh = ip_neigh_gw6(rt6->dst.dev, - rt6_nexthop(rt6, ip6_daddr)); - dst = &rt6->dst; - } else { - rt = dst_rtable(tuplehash->tuple.dst_cache); - neigh = ip_neigh_gw4(rt->dst.dev, - rt_nexthop(rt, ip_daddr)); - dst = &rt->dst; - } - if (IS_ERR(neigh)) { - flow_offload_teardown(flow); - return NF_DROP; - } - xmit.dest = neigh->ha; - skb_dst_set_noref(skb, dst); - break; - } - case FLOW_OFFLOAD_XMIT_DIRECT: - xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.out.ifidx); - if (!xmit.outdev) { - flow_offload_teardown(flow); - return NF_DROP; - } - xmit.dest = tuplehash->tuple.out.h_dest; - xmit.source = tuplehash->tuple.out.h_source; - break; - default: - WARN_ON_ONCE(1); - return NF_DROP; - } - xmit.tuple = other_tuple; - xmit.needs_gso_segment = tuplehash->tuple.needs_gso_segment; - - return nf_flow_queue_xmit(state->net, skb, &xmit); -} -EXPORT_SYMBOL_GPL(nf_flow_offload_ip_hook); - static void nf_flow_nat_ipv6_tcp(struct sk_buff *skb, unsigned int thoff, struct in6_addr *addr, struct in6_addr *new_addr, @@ -1107,8 +1006,16 @@ static int nf_flow_offload_ipv6_forward(struct nf_flowtable_ctx *ctx, flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]); mtu = flow->tuplehash[dir].tuple.mtu + ctx->offset; - if (flow->tuplehash[!dir].tuple.tun_num) + switch (flow->tuplehash[!dir].tuple.tun.encap_proto) { + case AF_INET: + mtu -= sizeof(struct iphdr); + break; + case AF_INET6: mtu -= sizeof(*ip6h); + break; + default: + break; + } if (unlikely(nf_flow_exceeds_mtu(skb, mtu))) return 0; @@ -1142,6 +1049,25 @@ static int nf_flow_offload_ipv6_forward(struct nf_flowtable_ctx *ctx, return 1; } +static struct flow_offload_tuple_rhash * +nf_flow_offload_lookup(struct nf_flowtable_ctx *ctx, + struct nf_flowtable *flow_table, struct sk_buff *skb) +{ + struct flow_offload_tuple tuple = {}; + + if (!nf_flow_skb_encap_protocol(ctx, skb, htons(ETH_P_IP))) + return NULL; + + if (ctx->tun.proto == IPPROTO_IPV6) { + if (nf_flow_tuple_ipv6(ctx, skb, &tuple) < 0) + return NULL; + } else if (nf_flow_tuple_ip(ctx, skb, &tuple) < 0) { + return NULL; + } + + return flow_offload_lookup(flow_table, &tuple); +} + static struct flow_offload_tuple_rhash * nf_flow_offload_ipv6_lookup(struct nf_flowtable_ctx *ctx, struct nf_flowtable *flow_table, @@ -1162,6 +1088,110 @@ nf_flow_offload_ipv6_lookup(struct nf_flowtable_ctx *ctx, return flow_offload_lookup(flow_table, &tuple); } +unsigned int +nf_flow_offload_ip_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 *other_tuple; + enum flow_offload_tuple_dir dir; + struct nf_flowtable_ctx ctx = { + .in = state->in, + }; + struct nf_flow_xmit xmit = {}; + struct in6_addr *ip6_daddr; + struct flow_offload *flow; + struct neighbour *neigh; + struct rtable *rt; + __be32 ip_daddr; + int ret; + + tuplehash = nf_flow_offload_lookup(&ctx, flow_table, skb); + if (!tuplehash) + return NF_ACCEPT; + + if (ctx.tun.proto == IPPROTO_IPV6) + ret = nf_flow_offload_ipv6_forward(&ctx, flow_table, tuplehash, + skb); + else + ret = nf_flow_offload_forward(&ctx, flow_table, tuplehash, skb); + if (ret < 0) + return NF_DROP; + else if (ret == 0) + return NF_ACCEPT; + + if (unlikely(tuplehash->tuple.xmit_type == FLOW_OFFLOAD_XMIT_XFRM)) { + rt = dst_rtable(tuplehash->tuple.dst_cache); + memset(skb->cb, 0, sizeof(struct inet_skb_parm)); + IPCB(skb)->iif = skb->dev->ifindex; + IPCB(skb)->flags = IPSKB_FORWARDED; + return nf_flow_xmit_xfrm(skb, state, &rt->dst); + } + + dir = tuplehash->tuple.dir; + flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]); + other_tuple = &flow->tuplehash[!dir].tuple; + ip_daddr = other_tuple->src_v4.s_addr; + ip6_daddr = &other_tuple->src_v6; + + if (nf_flow_tunnel_push(state->net, skb, other_tuple, + tuplehash->tuple.dst_cache, + &ip_daddr, &ip6_daddr) < 0) + return NF_DROP; + + switch (tuplehash->tuple.xmit_type) { + case FLOW_OFFLOAD_XMIT_NEIGH: { + struct dst_entry *dst; + + xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.ifidx); + if (!xmit.outdev) { + flow_offload_teardown(flow); + return NF_DROP; + } + if (other_tuple->tun.encap_proto ? + other_tuple->tun.encap_proto == AF_INET6 : + ctx.tun.proto == IPPROTO_IPV6) { + struct rt6_info *rt6; + + rt6 = dst_rt6_info(tuplehash->tuple.dst_cache); + neigh = ip_neigh_gw6(rt6->dst.dev, + rt6_nexthop(rt6, ip6_daddr)); + dst = &rt6->dst; + } else { + rt = dst_rtable(tuplehash->tuple.dst_cache); + neigh = ip_neigh_gw4(rt->dst.dev, + rt_nexthop(rt, ip_daddr)); + dst = &rt->dst; + } + if (IS_ERR(neigh)) { + flow_offload_teardown(flow); + return NF_DROP; + } + xmit.dest = neigh->ha; + skb_dst_set_noref(skb, dst); + break; + } + case FLOW_OFFLOAD_XMIT_DIRECT: + xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.out.ifidx); + if (!xmit.outdev) { + flow_offload_teardown(flow); + return NF_DROP; + } + xmit.dest = tuplehash->tuple.out.h_dest; + xmit.source = tuplehash->tuple.out.h_source; + break; + default: + WARN_ON_ONCE(1); + return NF_DROP; + } + xmit.tuple = other_tuple; + xmit.needs_gso_segment = tuplehash->tuple.needs_gso_segment; + + return nf_flow_queue_xmit(state->net, skb, &xmit); +} +EXPORT_SYMBOL_GPL(nf_flow_offload_ip_hook); + unsigned int nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) -- 2.55.0 Similar to IPIP, IP6IP6 and IPv4 over IPv6, introduce specific selftest for SIT flowtable sw acceleration in nft_flowtable.sh. Also add tunnel-specific flow-add rules for sit1 and sit1.10 so that check_counters() properly verifies offload occurred, and restore IPv6 default routes at cleanup to prevent tunnel routes leaking to subsequent tests (e.g. IPsec). Signed-off-by: Lorenzo Bianconi --- tools/testing/selftests/net/netfilter/config | 1 + .../selftests/net/netfilter/nft_flowtable.sh | 53 ++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/tools/testing/selftests/net/netfilter/config b/tools/testing/selftests/net/netfilter/config index c3c121b6f300..a212b9edbcd9 100644 --- a/tools/testing/selftests/net/netfilter/config +++ b/tools/testing/selftests/net/netfilter/config @@ -35,6 +35,7 @@ CONFIG_IP_SCTP=m CONFIG_IPV6=y CONFIG_IPV6_MULTIPLE_TABLES=y CONFIG_IPV6_TUNNEL=m +CONFIG_IPV6_SIT=m CONFIG_IP_VS=m CONFIG_IP_VS_PROTO_TCP=y CONFIG_IP_VS_RR=m diff --git a/tools/testing/selftests/net/netfilter/nft_flowtable.sh b/tools/testing/selftests/net/netfilter/nft_flowtable.sh index 1cb5c65351b7..cda68070cd00 100755 --- a/tools/testing/selftests/net/netfilter/nft_flowtable.sh +++ b/tools/testing/selftests/net/netfilter/nft_flowtable.sh @@ -597,6 +597,10 @@ ip -net "$nsr1" addr add 192.168.210.1/24 dev tun6 ip -net "$nsr1" addr add fee1:3::1/64 dev tun6 nodad ip netns exec "$nsr1" sysctl net.ipv4.conf.tun6.forwarding=1 > /dev/null +ip -net "$nsr1" link add name sit1 type sit local 192.168.10.1 remote 192.168.10.2 ttl 255 +ip -net "$nsr1" link set sit1 up +ip -net "$nsr1" addr add fe01:3::1/64 dev sit1 nodad + ip -net "$nsr2" link add name tun0 type ipip local 192.168.10.2 remote 192.168.10.1 ip -net "$nsr2" link set tun0 up ip -net "$nsr2" addr add 192.168.100.2/24 dev tun0 @@ -608,6 +612,10 @@ ip -net "$nsr2" addr add 192.168.210.2/24 dev tun6 ip -net "$nsr2" addr add fee1:3::2/64 dev tun6 nodad ip netns exec "$nsr2" sysctl net.ipv4.conf.tun6.forwarding=1 > /dev/null +ip -net "$nsr2" link add name sit1 type sit local 192.168.10.2 remote 192.168.10.1 ttl 255 +ip -net "$nsr2" link set sit1 up +ip -net "$nsr2" addr add fe01:3::2/64 dev sit1 nodad + ip -net "$nsr1" route change default via 192.168.100.2 ip -net "$nsr2" route change default via 192.168.100.1 @@ -626,6 +634,9 @@ ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun0 acce ip netns exec "$nsr1" nft -a insert rule inet filter forward \ 'meta oif tun6 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept' ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun6 accept' +ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif sit1 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept' +ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif sit1 accept' ip netns exec "$nsr1" nft -a insert rule inet filter forward \ 'meta oif "veth0" tcp sport 12345 ct mark set 1 flow add @f1 counter name routed_repl accept' @@ -652,6 +663,19 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IP4IP6 tunnel"; then ret=1 fi +ip -6 -net "$nsr1" route delete default +ip -6 -net "$nsr1" route add default via fe01:3::2 +ip -6 -net "$nsr2" route delete default +ip -6 -net "$nsr2" route add default via fe01:3::1 + +if test_tcp_forwarding "$ns1" "$ns2" 1 6 "[dead:2::99]" 12345; then + check_counters "flow offload for ns1/ns2 SIT tunnel" +else + echo "FAIL: flow offload for ns1/ns2 with SIT tunnel" 1>&2 + ip netns exec "$nsr1" nft list ruleset + ret=1 +fi + # Create vlan tagged devices for IPIP traffic. ip -net "$nsr1" link add link veth1 name veth1.10 type vlan id 10 ip -net "$nsr1" link set veth1.10 up @@ -682,6 +706,13 @@ ip netns exec "$nsr1" nft -a insert rule inet filter forward \ 'meta oif tun6.10 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept' ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun6.10 accept' +ip -net "$nsr1" link add name sit1.10 type sit local 192.168.20.1 remote 192.168.20.2 ttl 255 +ip -net "$nsr1" link set sit1.10 up +ip -net "$nsr1" addr add fe01:5::1/64 dev sit1.10 nodad +ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif sit1.10 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept' +ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif sit1.10 accept' + ip -net "$nsr2" link add link veth0 name veth0.10 type vlan id 10 ip -net "$nsr2" link set veth0.10 up ip -net "$nsr2" addr add 192.168.20.2/24 dev veth0.10 @@ -699,6 +730,11 @@ ip -net "$nsr2" link set tun6.10 up ip -net "$nsr2" addr add 192.168.220.2/24 dev tun6.10 ip -net "$nsr2" addr add fee1:5::2/64 dev tun6.10 nodad ip netns exec "$nsr2" sysctl net.ipv4.conf.tun6/10.forwarding=1 > /dev/null + +ip -net "$nsr2" link add name sit1.10 type sit local 192.168.20.2 remote 192.168.20.1 ttl 255 +ip -net "$nsr2" link set sit1.10 up +ip -net "$nsr2" addr add fe01:5::2/64 dev sit1.10 nodad + ip -6 -net "$nsr2" route delete default ip -6 -net "$nsr2" route add default via fee1:5::1 @@ -725,11 +761,28 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IP4IP6 tunnel over vlan"; then ret=1 fi +ip -6 -net "$nsr1" route delete default +ip -6 -net "$nsr1" route add default via fe01:5::2 +ip -6 -net "$nsr2" route delete default +ip -6 -net "$nsr2" route add default via fe01:5::1 + +if test_tcp_forwarding "$ns1" "$ns2" 1 6 "[dead:2::99]" 12345; then + check_counters "flow offload for ns1/ns2 SIT tunnel over vlan" +else + echo "FAIL: flow offload for ns1/ns2 with SIT tunnel over vlan" 1>&2 + ip netns exec "$nsr1" nft list ruleset + ret=1 +fi + # Restore the previous configuration ip -net "$nsr1" route change default via 192.168.10.2 ip -net "$nsr2" route change default via 192.168.10.1 ip -net "$ns2" route del default via 10.0.2.1 ip -6 -net "$ns2" route del default via dead:2::1 +ip -6 -net "$nsr1" route delete default +ip -6 -net "$nsr1" route add default via fee1:2::2 +ip -6 -net "$nsr2" route delete default +ip -6 -net "$nsr2" route add default via fee1:2::1 } # Another test: -- 2.55.0