geneve: introduce double tunnel GSO/GRO support
Subsystems (3)
Paolo Abeni
maintainerv4 cover geneve: introduce double tunnel GSO/GRO support
This is the [belated] incarnation of topic discussed in the last Neconf [1].
In container orchestration in virtual environments there is a consistent usage of double UDP tunneling - specifically geneve. Such setup lack support of GRO and GSO for inter VM traffic.
After commit b430f6c38da6 ("Merge branch 'virtio_udp_tunnel_08_07_2025' of https://github.com/pabeni/linux-devel") and the qemu cunter-part, VMs are able to send/receive GSO over UDP aggregated packets.
This series introduces the missing bit for full end-to-end aggregation in the above mentioned scenario. Specifically:
- introduces a new netdev feature set to generalize existing per device driver GSO admission check.1
- adds GSO partial support for the geneve and vxlan drivers
- introduces and use a geneve option to assist double tunnel GRO
- adds some simple functional tests for the above.
The new device features set is not strictly needed for the following work, but avoids the introduction of trivial ndo_features_check to support GSO partial and thus possible performance regression due to the additional indirect call. Such feature set could be leveraged by a number of existing drivers (intel, meta and possibly wangxun) to avoid duplicate code/tests. Such part has been omitted here to keep the series small.
Both GSO partial support and double GRO support have some downsides. With the first in place, GSO partial packets will traverse the network stack 'downstream' the outer geneve UDP tunnel and will be visible by the udp/IP/IPv6 and by netfilter. Currently only H/W NICs implement GSO partial support and such packets are visible only via software taps.
Double UDP tunnel GRO will cook 'GSO partial' like aggregate packets, i.e. the inner UDP encapsulation headers set will still carry the wire-level lengths and csum, so that segmentation considering such headers parts of a giant, constant encapsulation header will yield the correct result.
The correct GSO packet layout is applied when the packet traverse the outermost geneve encapsulation.
Both GSO partial and double UDP encap are disabled by default and must be explicitly enabled via, respectively ethtool and geneve device configuration.
Finally note that the GSO partial feature could potentially be applied to all the other UDP tunnels, but this series limits its usage to geneve and vxlan devices.
---
v3 -> v4:
- better mangleid handling in patch 1
- use xfail_on_slow in patch 10
v3: https://lore.kernel.org/netdev/[email protected]/
v2 -> v3:
- addressed AI-reported possible UaF
v2: https://lore.kernel.org/netdev/[email protected]/
v1 -> v2:
- addressed AI and checker feedback
- more stable self-tests
- avoid GRO cells for double encap GSO pkts
v1: https://lore.kernel.org/netdev/[email protected]/#t
Paolo Abeni (10):
net: introduce mangleid_features
geneve: expose gso partial features for tunnel offload
vxlan: expose gso partial features for tunnel offload
geneve: add netlink support for GRO hint
geneve: constify geneve_hlen()
geneve: pass the geneve device ptr to geneve_build_skb()
geneve: add GRO hint output path
geneve: extract hint option at GRO stage
geneve: use GRO hint option in the RX path
selftests: net: tests for add double tunneling GRO/GSO
Documentation/netlink/specs/rt-link.yaml | 3 + drivers/net/geneve.c | 557 ++++++++++++++++-- drivers/net/vxlan/vxlan_core.c | 16 +- include/linux/netdevice.h | 5 +- include/net/udp_tunnel.h | 32 + include/uapi/linux/if_link.h | 1 + net/core/dev.c | 8 +- tools/testing/selftests/net/Makefile | 1 + tools/testing/selftests/net/config | 1 + .../testing/selftests/net/double_udp_encap.sh | 393 ++++++++++++ 10 files changed, 978 insertions(+), 39 deletions(-) create mode 100755 tools/testing/selftests/net/double_udp_encap.sh
Signature · 1 line
2.52.0
Paolo Abeni
maintainerPATCH v4 1/10 net: introduce mangleid_features
Some/most devices implementing gso_partial need to disable the GSO partial features when the IP ID can't be mangled; to that extend each of them implements something alike the following[1]:
if (skb->encapsulation && !(features & NETIF_F_TSO_MANGLEID)) features &= ~NETIF_F_TSO;
in the ndo_features_check() op, which leads to a bit of duplicate code.
Later patch in the series will implement GSO partial support for virtual devices, and the current status quo will require more duplicate code and a new indirect call in the TX path for them.
Introduce the mangleid_features mask, allowing the core to disable NIC features based on/requiring MANGLEID, without any further intervention from the driver.
The same functionality could be alternatively implemented adding a single boolean flag to the struct net_device, but would require an additional checks in ndo_features_check().
Also note that [1] is incorrect if the NIC additionally implements NETIF_F_GSO_UDP_L4, mangleid_features transparently handle even such a case.
---
v3 -> v4:
- ensure mangleid_features includes TSO_MANGLEID for better code in gso_features_check() - Eric
- some changelog clarifications.
--- include/linux/netdevice.h | 5 ++++- net/core/dev.c | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-)
include/linux/netdevice.h +4 -1
@@ -1830,7 +1830,9 @@ enum netdev_reg_state {* and drivers will need to set them appropriately. * * @mpls_features: Mask of features inheritable by MPLS -* @gso_partial_features: value(s) from NETIF_F_GSO\* +* @gso_partial_features: value(s) from NETIF_F_GSO +* @mangleid_features: Mask of features requiring MANGLEID, will be +* disabled together with the latter. * * @ifindex: interface index * @group: The group the device belongs to
@@ -2219,6 +2221,7 @@ struct net_device {netdev_features_t vlan_features;netdev_features_t hw_enc_features;netdev_features_t mpls_features; +netdev_features_t mangleid_features;unsigned int min_mtu;unsigned int max_mtu;
net/core/dev.c +7 -1
@@ -3802,7 +3802,7 @@ static netdev_features_t gso_features_check(const struct sk_buff *skb, inner_ip_hdr(skb) : ip_hdr(skb);if (!(iph->frag_off &htons (IP_DF ))) - features &= ~NETIF_F_TSO_MANGLEID ; + features &= ~dev->mangleid_features; }/* NETIF_F_IPV6_CSUM does not support IPv6 extension headers,
@@ -11385,6 +11385,12 @@ int register_netdevice(struct net_device *dev)if (dev->hw_enc_features &NETIF_F_TSO ) dev->hw_enc_features |=NETIF_F_TSO_MANGLEID ; +/* Any mangleid feature disables TSO_MANGLEID; including the latter +* in mangleid_features allows for better code in the fastpath. +*/ +if (dev->mangleid_features) + dev->mangleid_features |=NETIF_F_TSO_MANGLEID ; +/* Make NETIF_F_HIGHDMA inheritable to VLAN devices. */ dev->vlan_features |=NETIF_F_HIGHDMA ;
Signature · 1 line
2.52.0
Paolo Abeni
maintainerPATCH v4 2/10 geneve: expose gso partial features for tunnel offload
In reply to Paolo AbeniGSO partial features for tunnels do not require any kind of support from the underlying device: we can safely add them to the geneve UDP tunnel.
The only point of attention is the skb required features propagation in the device xmit op: partial features must be stripped, except for UDP_TUNNEL*.
Keep partial features disabled by default.
--- drivers/net/geneve.c | 11 ++++++++++- include/net/udp_tunnel.h | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-)
drivers/net/geneve.c +10 -1
@@ -774,6 +774,7 @@ static int geneve_build_skb(struct dst_entry *dst, struct sk_buff *skb,bool udp_sum =test_bit (IP_TUNNEL_CSUM_BIT , info->key.tun_flags);struct genevehdr *gnvh;__be16 inner_proto; +bool double_encap;int min_headroom;int err;
@@ -786,6 +787,7 @@ static int geneve_build_skb(struct dst_entry *dst, struct sk_buff *skb,if (unlikely (err)) goto free_dst; + double_encap =udp_tunnel_handle_partial (skb); err =udp_tunnel_handle_offloads (skb, udp_sum);if (err) goto free_dst;
@@ -793,7 +795,7 @@ static int geneve_build_skb(struct dst_entry *dst, struct sk_buff *skb, gnvh =__skb_push (skb,sizeof (*gnvh) + info->options_len); inner_proto = inner_proto_inherit ? skb->protocol :htons (ETH_P_TEB );geneve_build_header (gnvh, info, inner_proto); -skb_set_inner_protocol (skb, inner_proto); +udp_tunnel_set_inner_protocol (skb, double_encap, inner_proto);return 0 ; free_dst:
@@ -1211,9 +1213,16 @@ static void geneve_setup(struct net_device *dev) dev->features |=NETIF_F_RXCSUM ; dev->features |=NETIF_F_GSO_SOFTWARE ; +/* Partial features are disabled by default. */ dev->hw_features |=NETIF_F_SG |NETIF_F_HW_CSUM |NETIF_F_FRAGLIST ; dev->hw_features |=NETIF_F_RXCSUM ; dev->hw_features |=NETIF_F_GSO_SOFTWARE ; + dev->hw_features |=UDP_TUNNEL_PARTIAL_FEATURES ; + dev->hw_features |=NETIF_F_GSO_PARTIAL ; + + dev->hw_enc_features = dev->hw_features; + dev->gso_partial_features =UDP_TUNNEL_PARTIAL_FEATURES ; + dev->mangleid_features =NETIF_F_GSO_PARTIAL ; dev->pcpu_stat_type =NETDEV_PCPU_STAT_DSTATS ;/* MTU range: 68 - (something less than 65535) */
include/net/udp_tunnel.h +32 -0
@@ -10,6 +10,11 @@#include <net/ipv6_stubs.h> #endif +#define UDP_TUNNEL_PARTIAL_FEATURES NETIF_F_GSO_ENCAP_ALL +#define UDP_TUNNEL_STRIPPED_GSO_TYPES ((UDP_TUNNEL_PARTIAL_FEATURES | \ + NETIF_F_GSO_PARTIAL) >> \ + NETIF_F_GSO_SHIFT) +struct udp_port_cfg {u8 family;
@@ -145,6 +150,33 @@ void udp_tunnel6_xmit_skb(struct dst_entry *dst, struct sock *sk, __be16 src_port, __be16 dst_port, bool nocheck, u16 ip6cb_flags); +static inline bool udp_tunnel_handle_partial (struct sk_buff *skb) +{ +bool double_encap = !!(skb_shinfo (skb)->gso_type &SKB_GSO_PARTIAL ); + +/* +* If the skb went through partial segmentation, lower devices +* will not need to offload the related features - except for +* UDP_TUNNEL, that will be re-added by the later +* udp_tunnel_handle_offloads(). +*/ +if (double_encap) +skb_shinfo (skb)->gso_type &= ~UDP_TUNNEL_STRIPPED_GSO_TYPES ; +return double_encap; +} + +static inline void udp_tunnel_set_inner_protocol (struct sk_buff *skb, +bool double_encap, +__be16 inner_proto) +{ +/* +* The inner protocol has been set by the nested tunnel, don't +* overraid it. +*/ +if (!double_encap) +skb_set_inner_protocol (skb, inner_proto); +} +void udp_tunnel_sock_release (struct socket *sock); struct rtable *udp_tunnel_dst_lookup(struct sk_buff *skb,
Signature · 1 line
2.52.0
Paolo Abeni
maintainerPATCH v4 3/10 vxlan: expose gso partial features for tunnel offload
In reply to Paolo AbeniSimilar to the previous patch, reuse the same helpers to add tunnel GSO partial capabilities to vxlan devices.
--- drivers/net/vxlan/vxlan_core.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-)
drivers/net/vxlan/vxlan_core.c +13 -3
@@ -2183,11 +2183,12 @@ static int vxlan_build_skb(struct sk_buff *skb, struct dst_entry *dst, struct vxlan_metadata *md, u32 vxflags, bool udp_sum) { + int type = udp_sum ? SKB_GSO_UDP_TUNNEL_CSUM : SKB_GSO_UDP_TUNNEL; +__be16 inner_protocol =htons (ETH_P_TEB );struct vxlanhdr *vxh; +bool double_encap;int min_headroom;int err; -int type = udp_sum ?SKB_GSO_UDP_TUNNEL_CSUM :SKB_GSO_UDP_TUNNEL ; -__be16 inner_protocol =htons (ETH_P_TEB );if ((vxflags &VXLAN_F_REMCSUM_TX ) && skb->ip_summed ==CHECKSUM_PARTIAL ) {
@@ -2208,6 +2209,7 @@ static int vxlan_build_skb(struct sk_buff *skb, struct dst_entry *dst,if (unlikely (err))return err; + double_encap =udp_tunnel_handle_partial (skb); err =iptunnel_handle_offloads (skb, type);if (err)return err;
@@ -2238,7 +2240,7 @@ static int vxlan_build_skb(struct sk_buff *skb, struct dst_entry *dst, inner_protocol = skb->protocol; } -skb_set_inner_protocol (skb, inner_protocol); +udp_tunnel_set_inner_protocol (skb, double_encap, inner_protocol);return 0 ; }
@@ -3348,10 +3350,18 @@ static void vxlan_setup(struct net_device *dev) dev->features |=NETIF_F_RXCSUM ; dev->features |=NETIF_F_GSO_SOFTWARE ; +/* Partial features are disabled by default. */ dev->vlan_features = dev->features; dev->hw_features |=NETIF_F_SG |NETIF_F_HW_CSUM |NETIF_F_FRAGLIST ; dev->hw_features |=NETIF_F_RXCSUM ; dev->hw_features |=NETIF_F_GSO_SOFTWARE ; + dev->hw_features |=UDP_TUNNEL_PARTIAL_FEATURES ; + dev->hw_features |=NETIF_F_GSO_PARTIAL ; + + dev->hw_enc_features = dev->hw_features; + dev->gso_partial_features =UDP_TUNNEL_PARTIAL_FEATURES ; + dev->mangleid_features =NETIF_F_GSO_PARTIAL ; +netif_keep_dst (dev); dev->priv_flags |=IFF_NO_QUEUE ; dev->change_proto_down = true;
Signature · 1 line
2.52.0
Paolo Abeni
maintainerPATCH v4 4/10 geneve: add netlink support for GRO hint
In reply to Paolo AbeniAllow configuring and dumping the new device option, and cache its value into the geneve socket itself.
The new option is not tie to it any code yet.
---
v1 -> v2:
- use nla_put_flag() to dump the GRO_HINT nl info
--- Documentation/netlink/specs/rt-link.yaml | 3 +++ drivers/net/geneve.c | 29 ++++++++++++++++++++---- include/uapi/linux/if_link.h | 1 + 3 files changed, 29 insertions(+), 4 deletions(-)
Documentation/netlink/specs/rt-link.yaml +3 -0
@@ -1914,6 +1914,9 @@ attribute-sets: name: port-range type: binary struct: ifla-geneve-port-range + - + name: gro-hint + type: flag - name: linkinfo-hsr-attrs name-prefix: ifla-hsr-
drivers/net/geneve.c +25 -4
@@ -56,6 +56,7 @@ struct geneve_config {bool collect_md;bool use_udp6_rx_checksums;bool ttl_inherit; +bool gro_hint;enum ifla_geneve_df df;bool inner_proto_inherit;u16 port_min;
@@ -84,6 +85,7 @@ struct geneve_dev {struct geneve_sock {bool collect_md; +bool gro_hint;struct list_head list;struct socket *sock;struct rcu_head rcu;
@@ -659,13 +661,15 @@ static void geneve_sock_release(struct geneve_dev *geneve)static struct geneve_sock *geneve_find_sock (struct geneve_net *gn,sa_family_t family, -__be16 dst_port) +__be16 dst_port, +bool gro_hint) {struct geneve_sock *gs;list_for_each_entry (gs, &gn->sock_list, list) {if (inet_sk (gs->sock->sk)->inet_sport == dst_port && -geneve_get_sk_family (gs) == family) { +geneve_get_sk_family (gs) == family && + gs->gro_hint == gro_hint) {return gs; } }
@@ -676,12 +680,14 @@ static int geneve_sock_add(struct geneve_dev *geneve, bool ipv6) {struct net *net = geneve->net;struct geneve_net *gn =net_generic (net, geneve_net_id); +bool gro_hint = geneve->cfg.gro_hint;struct geneve_dev_node *node;struct geneve_sock *gs;__u8 vni[3 ];__u32 hash; - gs =geneve_find_sock (gn, ipv6 ?AF_INET6 :AF_INET , geneve->cfg.info.key.tp_dst); + gs =geneve_find_sock (gn, ipv6 ?AF_INET6 :AF_INET , + geneve->cfg.info.key.tp_dst, gro_hint);if (gs) { gs->refcnt++; goto out;
@@ -694,6 +700,7 @@ static int geneve_sock_add(struct geneve_dev *geneve, bool ipv6) out: gs->collect_md = geneve->cfg.collect_md; + gs->gro_hint = gro_hint;#if IS_ENABLED (CONFIG_IPV6 )if (ipv6) {rcu_assign_pointer (geneve->sock6, gs);
@@ -1257,6 +1264,7 @@ static const struct nla_policy geneve_policy[IFLA_GENEVE_MAX + 1] = { [IFLA_GENEVE_DF] = { .type = NLA_U8 }, [IFLA_GENEVE_INNER_PROTO_INHERIT] = { .type = NLA_FLAG }, [IFLA_GENEVE_PORT_RANGE] = NLA_POLICY_EXACT_LEN(sizeof(struct ifla_geneve_port_range)), + [IFLA_GENEVE_GRO_HINT] = { .type = NLA_FLAG }, }; static int geneve_validate(struct nlattr *tb[], struct nlattr *data[],
@@ -1607,10 +1615,18 @@ static int geneve_nl2info(struct nlattr *tb[], struct nlattr *data[], cfg->inner_proto_inherit = true; } +if (data[IFLA_GENEVE_GRO_HINT ]) { +if (changelink) { + attrtype =IFLA_GENEVE_GRO_HINT ; + goto change_notsup; + } + cfg->gro_hint = true; + } +return 0 ; change_notsup:NL_SET_ERR_MSG_ATTR (extack, data[attrtype], -"Changing VNI, Port, endpoint IP address family, external, inner_proto_inherit, and UDP checksum attributes are not supported" ); +"Changing VNI, Port, endpoint IP address family, external, inner_proto_inherit, gro_hint and UDP checksum attributes are not supported" );return -EOPNOTSUPP ; }
@@ -1793,6 +1809,7 @@ static size_t geneve_get_size(const struct net_device *dev)nla_total_size (sizeof (__u8)) +/* IFLA_GENEVE_TTL_INHERIT */ nla_total_size (0 ) +/* IFLA_GENEVE_INNER_PROTO_INHERIT */ nla_total_size (sizeof (struct ifla_geneve_port_range )) +/* IFLA_GENEVE_PORT_RANGE */ +nla_total_size (0 ) +/* IFLA_GENEVE_GRO_HINT */ 0 ; }
@@ -1865,6 +1882,10 @@ static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev)if (nla_put (skb,IFLA_GENEVE_PORT_RANGE ,sizeof (ports), &ports)) goto nla_put_failure; +if (geneve->cfg.gro_hint && +nla_put_flag (skb,IFLA_GENEVE_GRO_HINT )) + goto nla_put_failure; +return 0 ; nla_put_failure:
include/uapi/linux/if_link.h +1 -0
@@ -1443,6 +1443,7 @@ enum {IFLA_GENEVE_DF ,IFLA_GENEVE_INNER_PROTO_INHERIT ,IFLA_GENEVE_PORT_RANGE , +IFLA_GENEVE_GRO_HINT , __IFLA_GENEVE_MAX };#define IFLA_GENEVE_MAX (__IFLA_GENEVE_MAX - 1)
Signature · 1 line
2.52.0
Paolo Abeni
maintainerPATCH v4 5/10 geneve: constify geneve_hlen()
In reply to Paolo AbeniSuch helper does not modify the argument; constifying it will additionally simplify later patches.
Additionally move the definition earlier, still for later's patchesi sake.
--- drivers/net/geneve.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-)
drivers/net/geneve.c +5 -5
@@ -365,6 +365,11 @@ static void geneve_uninit(struct net_device *dev)gro_cells_destroy (&geneve->gro_cells); } +static int geneve_hlen (const struct genevehdr *gh) +{ +return sizeof (*gh) + gh->opt_len *4 ; +} +/* Callback from net/ipv4/udp.c to receive packets */ static int geneve_udp_encap_recv (struct sock *sk,struct sk_buff *skb) {
@@ -497,11 +502,6 @@ static struct socket *geneve_create_sock(struct net *net, bool ipv6,return sock; } -static int geneve_hlen (struct genevehdr *gh) -{ -return sizeof (*gh) + gh->opt_len *4 ; -} -static struct sk_buff *geneve_gro_receive (struct sock *sk,struct list_head *head,struct sk_buff *skb)
Signature · 1 line
2.52.0
Paolo Abeni
maintainerPATCH v4 6/10 geneve: pass the geneve device ptr to geneve_build_skb()
In reply to Paolo AbeniInstead of handing to it the geneve configuration in multiple arguments. This already avoids some code duplication and we are going to pass soon more arguments to such function.
--- drivers/net/geneve.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-)
drivers/net/geneve.c +8 -12
@@ -775,10 +775,11 @@ static void geneve_build_header(struct genevehdr *geneveh,static int geneve_build_skb (struct dst_entry *dst,struct sk_buff *skb,const struct ip_tunnel_info *info, -bool xnet,int ip_hdr_len, -bool inner_proto_inherit) +const struct geneve_dev *geneve,int ip_hdr_len) {bool udp_sum =test_bit (IP_TUNNEL_CSUM_BIT , info->key.tun_flags); +bool inner_proto_inherit = geneve->cfg.inner_proto_inherit; +bool xnet = !net_eq (geneve->net,dev_net (geneve->dev));struct genevehdr *gnvh;__be16 inner_proto;bool double_encap;
@@ -830,8 +831,6 @@ static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev,struct geneve_dev *geneve, const struct ip_tunnel_info *info) { - bool inner_proto_inherit = geneve->cfg.inner_proto_inherit; -bool xnet = !net_eq (geneve->net,dev_net (geneve->dev)); struct geneve_sock *gs4 = rcu_dereference(geneve->sock4);const struct ip_tunnel_key *key = &info->key;struct rtable *rt;
@@ -842,7 +841,7 @@ static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev,__be16 sport;int err; -if (skb_vlan_inet_prepare (skb, inner_proto_inherit)) +if (skb_vlan_inet_prepare (skb, geneve->cfg.inner_proto_inherit))return -EINVAL ;if (!gs4)
@@ -925,8 +924,8 @@ static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev, } } - err =geneve_build_skb (&rt->dst, skb, info, xnet,sizeof (struct iphdr ), - inner_proto_inherit); + err =geneve_build_skb (&rt->dst, skb, info, geneve, +sizeof (struct iphdr ));if (unlikely (err))return err;
@@ -943,8 +942,6 @@ static int geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev,struct geneve_dev *geneve, const struct ip_tunnel_info *info) { - bool inner_proto_inherit = geneve->cfg.inner_proto_inherit; -bool xnet = !net_eq (geneve->net,dev_net (geneve->dev)); struct geneve_sock *gs6 = rcu_dereference(geneve->sock6);const struct ip_tunnel_key *key = &info->key;struct dst_entry *dst =NULL ;
@@ -954,7 +951,7 @@ static int geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev,__be16 sport;int err; -if (skb_vlan_inet_prepare (skb, inner_proto_inherit)) +if (skb_vlan_inet_prepare (skb, geneve->cfg.inner_proto_inherit))return -EINVAL ;if (!gs6)
@@ -1017,8 +1014,7 @@ static int geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev, ttl = key->ttl; ttl = ttl ? :ip6_dst_hoplimit (dst); } - err =geneve_build_skb (dst, skb, info, xnet,sizeof (struct ipv6hdr ), - inner_proto_inherit); + err =geneve_build_skb (dst, skb, info, geneve,sizeof (struct ipv6hdr ));if (unlikely (err))return err;
Signature · 1 line
2.52.0
Paolo Abeni
maintainerPATCH v4 7/10 geneve: add GRO hint output path
In reply to Paolo AbeniIf a geneve egress packet contains nested UDP encap headers, add a geneve option including the information necessary on the RX side to perform GRO aggregation of the whole packets: the nested network and transport headers, and the nested protocol type.
Use geneve option class netdev, already registered in the Network Virtualization Overlay (NVO3) IANA registry:
https://www.iana.org/assignments/nvo3/nvo3.xhtml#Linux-NetDev.
To pass the GRO hint information across the different xmit path functions, store them in the skb control buffer, to avoid adding additional arguments.
---
v2 -> v3:
- fix index OoB found by AI review
--- drivers/net/geneve.c | 124 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 120 insertions(+), 4 deletions(-)
drivers/net/geneve.c +120 -4
@@ -38,6 +38,26 @@ MODULE_PARM_DESC(log_ecn_error, "Log packets received with corrupted ECN");#define GENEVE_IPV4_HLEN (ETH_HLEN + sizeof(struct iphdr) + GENEVE_BASE_HLEN)#define GENEVE_IPV6_HLEN (ETH_HLEN + sizeof(struct ipv6hdr) + GENEVE_BASE_HLEN) +#define GENEVE_OPT_NETDEV_CLASS 0x100 +#define GENEVE_OPT_GRO_HINT_SIZE 8 +#define GENEVE_OPT_GRO_HINT_TYPE 1 +#define GENEVE_OPT_GRO_HINT_LEN 1 + +struct geneve_opt_gro_hint { +u8 inner_proto_id:2 , + nested_is_v6:1 ; +u8 nested_nh_offset; +u8 nested_tp_offset; +u8 nested_hdr_len; +}; + +struct geneve_skb_cb { +unsigned int gro_hint_len; +struct geneve_opt_gro_hint gro_hint; +}; + +#define GENEVE_SKB_CB (__skb) ((struct geneve_skb_cb *)&((__skb)->cb[0])) +/* per-network namespace private data for this module */ struct geneve_net {struct list_head geneve_list;
@@ -93,6 +113,21 @@ struct geneve_sock {struct hlist_head vni_list[VNI_HASH_SIZE ]; }; +static const __be16 proto_id_map[] = {htons (ETH_P_TEB ), +htons (ETH_P_IPV6 ), +htons (ETH_P_IP ) }; + +static int proto_to_id (__be16 proto) +{ +int i; + +for (i =0 ; i <ARRAY_SIZE (proto_id_map); i++) +if (proto_id_map[i] == proto) +return i; + +return -1 ; +} +static inline __u32 geneve_net_vni_hash (u8 vni[3 ]) {__u32 vnid;
@@ -773,6 +808,78 @@ static void geneve_build_header(struct genevehdr *geneveh,ip_tunnel_info_opts_get (geneveh->options, info); } +static int geneve_build_gro_hint_opt (const struct geneve_dev *geneve, +struct sk_buff *skb) +{ +struct geneve_skb_cb *cb =GENEVE_SKB_CB (skb); +struct geneve_opt_gro_hint *hint; +unsigned int nhlen; +bool nested_is_v6; +int id; + +BUILD_BUG_ON (sizeof (skb->cb) <sizeof (struct geneve_skb_cb )); + cb->gro_hint_len =0 ; + +/* Try to add the GRO hint only in case of double encap. */ +if (!geneve->cfg.gro_hint || !skb->encapsulation) +return 0 ; + +/* +* The nested headers must fit the geneve opt len fields and the +* nested encap must carry a nested transport (UDP) header. +*/ + nhlen =skb_inner_mac_header (skb) - skb->data; +if (nhlen >255 || !skb_transport_header_was_set (skb) || + skb->inner_protocol_type !=ENCAP_TYPE_ETHER || + (skb_transport_offset (skb) +sizeof (struct udphdr ) > nhlen)) +return 0 ; + + id =proto_to_id (skb->inner_protocol); +if (id <0 ) +return 0 ; + + nested_is_v6 = skb->protocol ==htons (ETH_P_IPV6 ); +if (nested_is_v6) { +int start =skb_network_offset (skb) +sizeof (struct ipv6hdr ); +u8 proto =ipv6_hdr (skb)->nexthdr; +__be16 foff; + +if (ipv6_skip_exthdr (skb, start, &proto, &foff) <0 || + proto !=IPPROTO_UDP ) +return 0 ; + }else { +if (ip_hdr (skb)->protocol !=IPPROTO_UDP ) +return 0 ; + } + + hint = &cb->gro_hint; +memset (hint,0 ,sizeof (*hint)); + hint->inner_proto_id = id; + hint->nested_is_v6 = skb->protocol ==htons (ETH_P_IPV6 ); + hint->nested_nh_offset =skb_network_offset (skb); + hint->nested_tp_offset =skb_transport_offset (skb); + hint->nested_hdr_len = nhlen; + cb->gro_hint_len =GENEVE_OPT_GRO_HINT_SIZE ; +return GENEVE_OPT_GRO_HINT_SIZE ; +} + +static void geneve_put_gro_hint_opt (struct genevehdr *gnvh,int opt_size, +const struct geneve_opt_gro_hint *hint) +{ +struct geneve_opt *gro_opt; + +/* geneve_build_header() did not took in account the GRO hint. */ + gnvh->opt_len = (opt_size +GENEVE_OPT_GRO_HINT_SIZE ) >>2 ; + + gro_opt = (void *)(gnvh +1 ) + opt_size; +memset (gro_opt,0 ,sizeof (*gro_opt)); + + gro_opt->opt_class =htons (GENEVE_OPT_NETDEV_CLASS ); + gro_opt->type =GENEVE_OPT_GRO_HINT_TYPE ; + gro_opt->length =GENEVE_OPT_GRO_HINT_LEN ; +memcpy (gro_opt +1 , hint,sizeof (*hint)); +} +static int geneve_build_skb (struct dst_entry *dst,struct sk_buff *skb,const struct ip_tunnel_info *info,const struct geneve_dev *geneve,int ip_hdr_len)
@@ -780,17 +887,20 @@ static int geneve_build_skb(struct dst_entry *dst, struct sk_buff *skb,bool udp_sum =test_bit (IP_TUNNEL_CSUM_BIT , info->key.tun_flags);bool inner_proto_inherit = geneve->cfg.inner_proto_inherit;bool xnet = !net_eq (geneve->net,dev_net (geneve->dev)); +struct geneve_skb_cb *cb =GENEVE_SKB_CB (skb);struct genevehdr *gnvh;__be16 inner_proto;bool double_encap;int min_headroom; +int opt_size;int err;skb_reset_mac_header (skb);skb_scrub_packet (skb, xnet); + opt_size = info->options_len + cb->gro_hint_len; min_headroom =LL_RESERVED_SPACE (dst->dev) + dst->header_len + -GENEVE_BASE_HLEN + info->options_len + ip_hdr_len; +GENEVE_BASE_HLEN + opt_size + ip_hdr_len; err =skb_cow_head (skb, min_headroom);if (unlikely (err)) goto free_dst;
@@ -800,9 +910,13 @@ static int geneve_build_skb(struct dst_entry *dst, struct sk_buff *skb,if (err) goto free_dst; - gnvh =__skb_push (skb,sizeof (*gnvh) + info->options_len); + gnvh =__skb_push (skb,sizeof (*gnvh) + opt_size); inner_proto = inner_proto_inherit ? skb->protocol :htons (ETH_P_TEB );geneve_build_header (gnvh, info, inner_proto); + +if (cb->gro_hint_len) +geneve_put_gro_hint_opt (gnvh, info->options_len, &cb->gro_hint); +udp_tunnel_set_inner_protocol (skb, double_encap, inner_proto);return 0 ;
@@ -862,7 +976,8 @@ static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev,return PTR_ERR (rt); err =skb_tunnel_check_pmtu (skb, &rt->dst, -GENEVE_IPV4_HLEN + info->options_len, +GENEVE_IPV4_HLEN + info->options_len + +geneve_build_gro_hint_opt (geneve, skb),netif_is_any_bridge_port (dev));if (err <0 ) {dst_release (&rt->dst);
@@ -972,7 +1087,8 @@ static int geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev,return PTR_ERR (dst); err =skb_tunnel_check_pmtu (skb, dst, -GENEVE_IPV6_HLEN + info->options_len, +GENEVE_IPV6_HLEN + info->options_len + +geneve_build_gro_hint_opt (geneve, skb),netif_is_any_bridge_port (dev));if (err <0 ) {dst_release (dst);
Signature · 1 line
2.52.0
Paolo Abeni
maintainerQuoted 68 lines · Paolo Abeni
This is the [belated] incarnation of topic discussed in the last Neconf [1].
In container orchestration in virtual environments there is a consistent usage of double UDP tunneling - specifically geneve. Such setup lack support of GRO and GSO for inter VM traffic.
After commit
b430f6c38da6("Merge branch 'virtio_udp_tunnel_08_07_2025' of https://github.com/pabeni/linux-devel") and the qemu cunter-part, VMs are able to send/receive GSO over UDP aggregated packets.This series introduces the missing bit for full end-to-end aggregation in the above mentioned scenario. Specifically:
- introduces a new netdev feature set to generalize existing per device driver GSO admission check.1
- adds GSO partial support for the geneve and vxlan drivers
- introduces and use a geneve option to assist double tunnel GRO
- adds some simple functional tests for the above.The new device features set is not strictly needed for the following work, but avoids the introduction of trivial
ndo_features_checkto support GSO partial and thus possible performance regression due to the additional indirect call. Such feature set could be leveraged by a number of existing drivers (intel, meta and possibly wangxun) to avoid duplicate code/tests. Such part has been omitted here to keep the series small.Both GSO partial support and double GRO support have some downsides. With the first in place, GSO partial packets will traverse the network stack 'downstream' the outer geneve UDP tunnel and will be visible by the udp/IP/IPv6 and by netfilter. Currently only H/W NICs implement GSO partial support and such packets are visible only via software taps.
Double UDP tunnel GRO will cook 'GSO partial' like aggregate packets, i.e. the inner UDP encapsulation headers set will still carry the wire-level lengths and csum, so that segmentation considering such headers parts of a giant, constant encapsulation header will yield the correct result.
The correct GSO packet layout is applied when the packet traverse the outermost geneve encapsulation.
Both GSO partial and double UDP encap are disabled by default and must be explicitly enabled via, respectively ethtool and geneve device configuration.
Finally note that the GSO partial feature could potentially be applied to all the other UDP tunnels, but this series limits its usage to geneve and vxlan devices.
---
v3 -> v4:
- better mangleid handling in patch 1
- usexfail_on_slowin patch 10
v3: https://lore.kernel.org/netdev/[email protected]/v2 -> v3:
- addressed AI-reported possible UaF
v2: https://lore.kernel.org/netdev/[email protected]/v1 -> v2:
- addressed AI and checker feedback
- more stable self-tests
- avoid GRO cells for double encap GSO pkts
v1: https://lore.kernel.org/netdev/[email protected]/#t
Quoted 11 lines
Paolo Abeni (10):
net: introducemangleid_features
geneve: expose gso partial features for tunnel offload
vxlan: expose gso partial features for tunnel offload
geneve: add netlink support for GRO hint
geneve: constifygeneve_hlen()
geneve: pass the geneve device ptr togeneve_build_skb()
geneve: add GRO hint output path
geneve: extract hint option at GRO stage
geneve: use GRO hint option in the RX path
selftests: net: tests for add double tunneling GRO/GSO
It looks like the last 3 patches did not land on the ML. I have no idea about the root cause. I'll wait a bit more and I'll resend the whole series.
/P
Re: [PATCH v4 net-next 01/10] net: introduce mangleid_features
In reply to Paolo AbeniQuoted 74 lines · Paolo Abeni
Some/most devices implementing
gso_partialneed to disable the GSO partial features when the IP ID can't be mangled; to that extend each of them implements something alike the following[1]:if (skb->encapsulation && !(features & NETIF_F_TSO_MANGLEID)) features &= ~NETIF_F_TSO;in the
ndo_features_check()op, which leads to a bit of duplicate code.Later patch in the series will implement GSO partial support for virtual devices, and the current status quo will require more duplicate code and a new indirect call in the TX path for them.
Introduce the
mangleid_featuresmask, allowing the core to disable NIC features based on/requiring MANGLEID, without any further intervention from the driver.The same functionality could be alternatively implemented adding a single boolean flag to the struct
net_device, but would require an additional checks inndo_features_check().Also note that [1] is incorrect if the NIC additionally implements
NETIF_F_GSO_UDP_L4,mangleid_featurestransparently handle even such a case.Signed-off-by Paolo Abeni <[email protected]>---
v3 -> v4:
- ensuremangleid_featuresincludesTSO_MANGLEIDfor better code ingso_features_check()- Eric
- some changelog clarifications.--- include/linux/netdevice.h | 5 ++++- net/core/dev.c | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-)include/linux/netdevice.h +4
-1@@ -1830,7 +1830,9 @@ enum netdev_reg_state {* and drivers will need to set them appropriately. * * @mpls_features: Mask of features inheritable by MPLS -* @gso_partial_features: value(s) from NETIF_F_GSO\* +* @gso_partial_features: value(s) from NETIF_F_GSO +* @mangleid_features: Mask of features requiring MANGLEID, will be +* disabled together with the latter. * * @ifindex: interface index * @group: The group the device belongs to @@ -2219,6 +2221,7 @@ struct net_device {netdev_features_t vlan_features;netdev_features_t hw_enc_features;netdev_features_t mpls_features; +netdev_features_t mangleid_features;unsigned int min_mtu;unsigned int max_mtu;net/core/dev.c +1
-1@@ -3802,7 +3802,7 @@ static netdev_features_t gso_features_check(const struct sk_buff *skb, inner_ip_hdr(skb) : ip_hdr(skb);if (!(iph->frag_off &htons (IP_DF ))) - features &= ~NETIF_F_TSO_MANGLEID ; + features &= ~dev->mangleid_features; }/* NETIF_F_IPV6_CSUM does not support IPv6 extension headers,
net/core/dev.c int register_netdevice(struct net_device *dev) · line 11393
Quoted 9 lines
net/core/dev.c +6
-0@@ -11385,6 +11385,12 @@ int register_netdevice(struct net_device *dev)if (dev->hw_enc_features &NETIF_F_TSO ) dev->hw_enc_features |=NETIF_F_TSO_MANGLEID ; +/* Any mangleid feature disables TSO_MANGLEID; including the latter +* in mangleid_features allows for better code in the fastpath. +*/ +if (dev->mangleid_features) + dev->mangleid_features |=NETIF_F_TSO_MANGLEID ; +
It is a bit unclear why you test for anything being set in mangleid_features
I would force here the bit, without any condition ?
dev->mangleid_features |= NETIF_F_TSO_MANGLEID;
Quoted 6 lines
net/core/dev.c +0
-1/* Make NETIF_F_HIGHDMA inheritable to VLAN devices. */ dev->vlan_features |=NETIF_F_HIGHDMA ; --2.52.0
Paolo Abeni
maintainerRe: [PATCH v4 net-next 01/10] net: introduce mangleid_features
Quoted 14 lines · Eric Dumazet
net/core/dev.c int register_netdevice(struct net_device *dev) · line 11393
Quoted 10 lines · Paolo Abeni
net/core/dev.c +6
-0@@ -11385,6 +11385,12 @@ int register_netdevice(struct net_device *dev)if (dev->hw_enc_features &NETIF_F_TSO ) dev->hw_enc_features |=NETIF_F_TSO_MANGLEID ; +/* Any mangleid feature disables TSO_MANGLEID; including the latter +* in mangleid_features allows for better code in the fastpath. +*/ +if (dev->mangleid_features) + dev->mangleid_features |=NETIF_F_TSO_MANGLEID ; +It is a bit unclear why you test for anything being set in
mangleid_features
Quoted 3 lines
I would force here the bit, without any condition ?
dev->mangleid_features |= NETIF_F_TSO_MANGLEID;
Indeed the check it's not needed. I'll drop it in the next revision, thanks!
Paolo
Quoted 2 lines · Paolo Abeni
It looks like the last 3 patches did not land on the ML. I have no idea about the root cause. I'll wait a bit more and I'll resend the whole series.
FWIW I didn't get the last three e-mails to my e-mail either. It must have gotten lost somewhere along the way.