Skip to contentSkip to conversation

udp: GRO L4 improvements

PATCH Superseded Paolo Abeni 2 participants 35 messages Mar 21, 2021 – Mar 25, 2021

Subsystems (4)

v1 of 3 Superseded Latest is v3 →

lore
Share & tools
lore

Paolo Abeni

author maintainer
#0 lore raw

cover udp: GRO L4 improvements

This series improves the UDP L4 - either 'forward' or 'frag_list' - co-existence with UDP tunnel GRO, allowing the first to take place correctly even for encapsulated UDP traffic.

The first for patches are mostly bugfixes, addressing some GRO edge-cases when both tunnels and L4 are present, enabled and in use.

The next 3 patches avoid unneeded segmentation when UDP GRO traffic traverses in the receive path UDP tunnels.

Finally, some self-tests are included, covering the relevant GRO scenarios.

Even if most patches are actually bugfixes, this series is targeting net-next, as overall it makes available a new feature.

Paolo Abeni (8):
udp: fixup csum for GSO receive slow path
udp: skip fwd/list GRO for tunnel packets
udp: properly complete L4 GRO over UDP tunnel packet
udp: never accept GSO_FRAGLIST packets
vxlan: allow L4 GRO passthrou
geneve: allow UDP L4 GRO passthrou
bareudp: allow UDP L4 GRO passthrou
selftests: net: add UDP GRO forwarding self-tests

 drivers/net/bareudp.c                     |   1 +
 drivers/net/geneve.c                      |   1 +
 drivers/net/vxlan.c                       |   1 +
 include/linux/udp.h                       |  15 +-
 include/net/udp.h                         |  18 ++
 net/ipv4/udp.c                            |  22 +-
 net/ipv4/udp_offload.c                    |  27 ++-
 net/ipv6/udp.c                            |   5 +
 net/ipv6/udp_offload.c                    |   3 +-
 tools/testing/selftests/net/Makefile      |   1 +
 tools/testing/selftests/net/udpgro_fwd.sh | 251 ++++++++++++++++++++++
 11 files changed, 330 insertions(+), 15 deletions(-)
 create mode 100755 tools/testing/selftests/net/udpgro_fwd.sh
Signature · 1 line
2.26.2
author maintainer
#1 lore raw

PATCH 1/8 udp: fixup csum for GSO receive slow path

When looping back UDP GSO over UDP tunnel packets to an UDP socket, the individual packet csum is currently set to CSUM_NONE. That causes unexpected/wrong csum validation errors later in the UDP receive path.

We could possibly addressing the issue with some additional check and csum mangling in the UDP tunnel code. Since the issue affects only this UDP receive slow path, let's set a suitable csum status there.

---
 include/net/udp.h | 18 ++++++++++++++++++
 net/ipv4/udp.c    | 10 ++++++++++
 net/ipv6/udp.c    |  5 +++++
 3 files changed, 33 insertions(+)
include/net/udp.h +18 -0
index d4d064c592328..007683eb3e113 100644
@@ -515,6 +515,24 @@ static inline struct sk_buff *udp_rcv_segment(struct sock *sk,
 	return segs;
 }
 
+static inline void udp_post_segment_fix_csum(struct sk_buff *skb, int level)
+{
+	/* UDP-lite can't land here - no GRO */
+	WARN_ON_ONCE(UDP_SKB_CB(skb)->partial_cov);
+
+	/* GRO already validated the csum up to 'level', and we just
+	 * consumed one header, update the skb accordingly
+	 */
+	UDP_SKB_CB(skb)->cscov = skb->len;
+	if (level) {
+		skb->ip_summed = CHECKSUM_UNNECESSARY;
+		skb->csum_level = 0;
+	} else {
+		skb->ip_summed = CHECKSUM_NONE;
+		skb->csum_valid = 1;
+	}
+}
+
 #ifdef CONFIG_BPF_SYSCALL
 struct sk_psock;
 struct proto *udp_bpf_get_proto(struct sock *sk, struct sk_psock *psock);
net/ipv4/udp.c +10 -0
index 4a0478b17243a..ff54135c51ffa 100644
@@ -2168,6 +2168,7 @@ static int udp_queue_rcv_one_skb(struct sock *sk, struct sk_buff *skb)
 static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
 {
 	struct sk_buff *next, *segs;
+	int csum_level;
 	int ret;
 
 	if (likely(!udp_unexpected_gso(sk, skb)))
@@ -2175,9 +2176,18 @@ static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
 
 	BUILD_BUG_ON(sizeof(struct udp_skb_cb) > SKB_GSO_CB_OFFSET);
 	__skb_push(skb, -skb_mac_offset(skb));
+	csum_level = !!(skb_shinfo(skb)->gso_type &
+			(SKB_GSO_UDP_TUNNEL | SKB_GSO_UDP_TUNNEL_CSUM));
 	segs = udp_rcv_segment(sk, skb, true);
 	skb_list_walk_safe(segs, skb, next) {
 		__skb_pull(skb, skb_transport_offset(skb));
+
+		/* UDP GSO packets looped back after adding UDP encap land here with CHECKSUM none,
+		 * instead of adding another check in the tunnel fastpath, we can force valid
+		 * csums here (packets are locally generated).
+		 * Additionally fixup the UDP CB
+		 */
+		udp_post_segment_fix_csum(skb, csum_level);
 		ret = udp_queue_rcv_one_skb(sk, skb);
 		if (ret > 0)
 			ip_protocol_deliver_rcu(dev_net(skb->dev), skb, ret);
net/ipv6/udp.c +5 -0
index d25e5a9252fdb..e7d4bf3a65c72 100644
@@ -739,16 +739,21 @@ static int udpv6_queue_rcv_one_skb(struct sock *sk, struct sk_buff *skb)
 static int udpv6_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
 {
 	struct sk_buff *next, *segs;
+	int csum_level;
 	int ret;
 
 	if (likely(!udp_unexpected_gso(sk, skb)))
 		return udpv6_queue_rcv_one_skb(sk, skb);
 
 	__skb_push(skb, -skb_mac_offset(skb));
+	csum_level = !!(skb_shinfo(skb)->gso_type &
+			(SKB_GSO_UDP_TUNNEL | SKB_GSO_UDP_TUNNEL_CSUM));
 	segs = udp_rcv_segment(sk, skb, false);
 	skb_list_walk_safe(segs, skb, next) {
 		__skb_pull(skb, skb_transport_offset(skb));
 
+		/* see comments in udp_queue_rcv_skb() */
+		udp_post_segment_fix_csum(skb, csum_level);
 		ret = udpv6_queue_rcv_one_skb(sk, skb);
 		if (ret > 0)
 			ip6_protocol_deliver_rcu(dev_net(skb->dev), skb, ret,
Signature · 1 line
2.26.2
author maintainer
#11 lore raw

PATCH 2/8 udp: skip fwd/list GRO for tunnel packets

In reply to Paolo Abeni

If UDP GRO forwarding (or list) is enabled, and there are udp tunnel available in the system, we could end-up doing L4 aggregation for packets targeting the UDP tunnel.

That could inner protocol corruption, as no overaly network parameters is taken in account at aggregation time.

Just skip the fwd GRO if this packet could land in an UDP tunnel. The current check is broader than what is strictly needed, as the UDP tunnel could be e.g. on top of a different device, but is simple and the performance downside looks not relevant.

Fixes 9fd1ff5d2ac7 ("udp: Support UDP fraglist GRO/GSO.")
Fixes 36707061d6ba ("udp: allow forwarding of plain (non-fraglisted) UDP GRO packets")
---
 net/ipv4/udp_offload.c | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)
net/ipv4/udp_offload.c +11 -8
index c5b4b586570fe..25134a3548e99 100644
@@ -515,21 +515,24 @@ struct sk_buff *udp_gro_receive(struct list_head *head, struct sk_buff *skb,
 	unsigned int off = skb_gro_offset(skb);
 	int flush = 1;
 
+	/* we can do L4 aggregation only if the packet can't land in a tunnel
+	 * otherwise we could corrupt the inner stream
+	 */
 	NAPI_GRO_CB(skb)->is_flist = 0;
-	if (skb->dev->features & NETIF_F_GRO_FRAGLIST)
-		NAPI_GRO_CB(skb)->is_flist = sk ? !udp_sk(sk)->gro_enabled: 1;
+	if (!sk || !udp_sk(sk)->gro_receive) {
+		if (skb->dev->features & NETIF_F_GRO_FRAGLIST)
+			NAPI_GRO_CB(skb)->is_flist = sk ? !udp_sk(sk)->gro_enabled : 1;
 
-	if ((!sk && (skb->dev->features & NETIF_F_GRO_UDP_FWD)) ||
-	    (sk && udp_sk(sk)->gro_enabled) || NAPI_GRO_CB(skb)->is_flist) {
-		pp = call_gro_receive(udp_gro_receive_segment, head, skb);
+		if ((!sk && (skb->dev->features & NETIF_F_GRO_UDP_FWD)) ||
+		    (sk && udp_sk(sk)->gro_enabled) || NAPI_GRO_CB(skb)->is_flist)
+			pp = call_gro_receive(udp_gro_receive_segment, head, skb);
 		return pp;
 	}
 
-	if (!sk || NAPI_GRO_CB(skb)->encap_mark ||
+	if (NAPI_GRO_CB(skb)->encap_mark ||
 	    (uh->check && skb->ip_summed != CHECKSUM_PARTIAL &&
 	     NAPI_GRO_CB(skb)->csum_cnt == 0 &&
-	     !NAPI_GRO_CB(skb)->csum_valid) ||
-	    !udp_sk(sk)->gro_receive)
+	     !NAPI_GRO_CB(skb)->csum_valid))
 		goto out;
 
 	/* mark that this skb passed once through the tunnel gro layer */
Signature · 1 line
2.26.2
author maintainer
#17 lore raw

PATCH 3/8 udp: properly complete L4 GRO over UDP tunnel packet

In reply to Paolo Abeni

After the previous patch the stack can do L4 UDP aggregation on top of an UDP tunnel.

The current GRO complete code tries frag based aggregation first; in the above scenario will generate corrupted frames.

We need to try first UDP tunnel based aggregation, if the GRO packet requires that. We can use time GRO 'encap_mark' field to track the need GRO complete action. If encap_mark is set, skip the frag_list aggregation.

On tunnel encap GRO complete clear such field, so that an inner frag_list GRO complete could take action.

---
 net/ipv4/udp_offload.c | 8 +++++++-
 net/ipv6/udp_offload.c | 3 ++-
 2 files changed, 9 insertions(+), 2 deletions(-)
net/ipv4/udp_offload.c +7 -1
index 25134a3548e99..54e06b88af69a 100644
@@ -642,6 +642,11 @@ int udp_gro_complete(struct sk_buff *skb, int nhoff,
 		skb_shinfo(skb)->gso_type = uh->check ? SKB_GSO_UDP_TUNNEL_CSUM
 					: SKB_GSO_UDP_TUNNEL;
 
+		/* clear the encap mark, so that inner frag_list gro_complete
+		 * can take place
+		 */
+		NAPI_GRO_CB(skb)->encap_mark = 0;
+
 		/* Set encapsulation before calling into inner gro_complete()
 		 * functions to make them set up the inner offsets.
 		 */
@@ -665,7 +670,8 @@ INDIRECT_CALLABLE_SCOPE int udp4_gro_complete(struct sk_buff *skb, int nhoff)
 	const struct iphdr *iph = ip_hdr(skb);
 	struct udphdr *uh = (struct udphdr *)(skb->data + nhoff);
 
-	if (NAPI_GRO_CB(skb)->is_flist) {
+	/* do fraglist only if there is no outer UDP encap (or we already processed it) */
+	if (NAPI_GRO_CB(skb)->is_flist && !NAPI_GRO_CB(skb)->encap_mark) {
 		uh->len = htons(skb->len - nhoff);
 
 		skb_shinfo(skb)->gso_type |= (SKB_GSO_FRAGLIST|SKB_GSO_UDP_L4);
net/ipv6/udp_offload.c +2 -1
index faa823c242923..b3d9ed96e5ea5 100644
@@ -163,7 +163,8 @@ INDIRECT_CALLABLE_SCOPE int udp6_gro_complete(struct sk_buff *skb, int nhoff)
 	const struct ipv6hdr *ipv6h = ipv6_hdr(skb);
 	struct udphdr *uh = (struct udphdr *)(skb->data + nhoff);
 
-	if (NAPI_GRO_CB(skb)->is_flist) {
+	/* do fraglist only if there is no outer UDP encap (or we already processed it) */
+	if (NAPI_GRO_CB(skb)->is_flist && !NAPI_GRO_CB(skb)->encap_mark) {
 		uh->len = htons(skb->len - nhoff);
 
 		skb_shinfo(skb)->gso_type |= (SKB_GSO_FRAGLIST|SKB_GSO_UDP_L4);
Signature · 1 line
2.26.2
author maintainer
#21 lore raw

PATCH 4/8 udp: never accept GSO_FRAGLIST packets

In reply to Paolo Abeni

Currently the UDP protocol delivers GSO_FRAGLIST packets to the sockets without the expected segmentation.

This change addresses the issue introducing and maintaining a per socket bitmask of GSO types requiring segmentation. Enabling GSO removes SKB_GSO_UDP_L4 from such mask, while GSO_FRAGLIST packets are never accepted

Note: this also updates the 'unused' field size to really fit the otherwise existing hole. It's size become incorrect after commit bec1f6f69736 ("udp: generate gso with UDP_SEGMENT").

Fixes 9fd1ff5d2ac7 ("udp: Support UDP fraglist GRO/GSO.")
---
 include/linux/udp.h | 10 ++++++----
 net/ipv4/udp.c      | 12 +++++++++++-
 2 files changed, 17 insertions(+), 5 deletions(-)
include/linux/udp.h +6 -4
index aa84597bdc33c..6da342f15f351 100644
@@ -51,7 +51,7 @@ struct udp_sock {
 					   * different encapsulation layer set
 					   * this
 					   */
-			 gro_enabled:1;	/* Can accept GRO packets */
+			 gro_enabled:1;	/* Request GRO aggregation */
 	/*
 	 * Following member retains the information to create a UDP header
 	 * when the socket is uncorked.
@@ -68,7 +68,10 @@ struct udp_sock {
 #define UDPLITE_SEND_CC  0x2  		/* set via udplite setsockopt         */
 #define UDPLITE_RECV_CC  0x4		/* set via udplite setsocktopt        */
 	__u8		 pcflag;        /* marks socket as UDP-Lite if > 0    */
-	__u8		 unused[3];
+	__u8		 unused[1];
+	unsigned int	 unexpected_gso;/* GSO types this socket can't accept,
+					 * any of SKB_GSO_UDP_L4 or SKB_GSO_FRAGLIST
+					 */
 	/*
 	 * For encapsulation sockets.
 	 */
@@ -131,8 +134,7 @@ static inline void udp_cmsg_recv(struct msghdr *msg, struct sock *sk,
 
 static inline bool udp_unexpected_gso(struct sock *sk, struct sk_buff *skb)
 {
-	return !udp_sk(sk)->gro_enabled && skb_is_gso(skb) &&
-	       skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4;
+	return skb_is_gso(skb) && skb_shinfo(skb)->gso_type & udp_sk(sk)->unexpected_gso;
 }
 
 #define udp_portaddr_for_each_entry(__sk, list) \
net/ipv4/udp.c +11 -1
index ff54135c51ffa..1ba6d153c2f0a 100644
@@ -1600,8 +1600,13 @@ EXPORT_SYMBOL_GPL(udp_destruct_sock);
 
 int udp_init_sock(struct sock *sk)
 {
-	skb_queue_head_init(&udp_sk(sk)->reader_queue);
+	struct udp_sock *up = udp_sk(sk);
+
+	skb_queue_head_init(&up->reader_queue);
 	sk->sk_destruct = udp_destruct_sock;
+
+	/* do not accept any GSO packet by default */
+	up->unexpected_gso = SKB_GSO_FRAGLIST | SKB_GSO_UDP_L4;
 	return 0;
 }
 EXPORT_SYMBOL_GPL(udp_init_sock);
@@ -2674,8 +2679,13 @@ int udp_lib_setsockopt(struct sock *sk, int level, int optname,
 
 	case UDP_GRO:
 		lock_sock(sk);
+
+		/* when enabling GRO, accept the related GSO packet type */
+		up->unexpected_gso = SKB_GSO_FRAGLIST;
 		if (valbool)
 			udp_tunnel_encap_enable(sk->sk_socket);
+		else
+			up->unexpected_gso |= SKB_GSO_UDP_L4;
 		up->gro_enabled = valbool;
 		release_sock(sk);
 		break;
Signature · 1 line
2.26.2
author maintainer
#28 lore raw

PATCH 5/8 vxlan: allow L4 GRO passthrou

In reply to Paolo Abeni

When passing up an UDP GSO packet with L4 aggregation, there is no need to segment it at the vxlan level. We can propagate the packet untouched and let it be segmented later, if needed.

Introduce an helper to allow let the UDP socket accepting any L4 aggregation and use it in the vxlan driver.

---
 drivers/net/vxlan.c | 1 +
 include/linux/udp.h | 5 +++++
 2 files changed, 6 insertions(+)
drivers/net/vxlan.c +1 -0
index 7665817f3cb61..39ee1300cdd9d 100644
@@ -3484,6 +3484,7 @@ static struct socket *vxlan_create_sock(struct net *net, bool ipv6,
 	if (err < 0)
 		return ERR_PTR(err);
 
+	udp_allow_gso(sock->sk);
 	return sock;
 }
 
include/linux/udp.h +5 -0
index 6da342f15f351..0444f2fb6002e 100644
@@ -137,6 +137,11 @@ static inline bool udp_unexpected_gso(struct sock *sk, struct sk_buff *skb)
 	return skb_is_gso(skb) && skb_shinfo(skb)->gso_type & udp_sk(sk)->unexpected_gso;
 }
 
+static inline void udp_allow_gso(struct sock *sk)
+{
+	udp_sk(sk)->unexpected_gso = 0;
+}
+
 #define udp_portaddr_for_each_entry(__sk, list) \
 	hlist_for_each_entry(__sk, list, __sk_common.skc_portaddr_node)
 
Signature · 1 line
2.26.2
author maintainer
#29 lore raw

PATCH 6/8 geneve: allow UDP L4 GRO passthrou

In reply to Paolo Abeni

Similar to the previous commit, let even geneve
passthrou the L4 GRO packets

---
 drivers/net/geneve.c | 1 +
 1 file changed, 1 insertion(+)
drivers/net/geneve.c +1 -0
index 4ac0373326efd..5d7a2b1469f4c 100644
@@ -461,6 +461,7 @@ static struct socket *geneve_create_sock(struct net *net, bool ipv6,
 	if (err < 0)
 		return ERR_PTR(err);
 
+	udp_allow_gso(sock->sk);
 	return sock;
 }
 
Signature · 1 line
2.26.2
author maintainer
#30 lore raw

PATCH 7/8 bareudp: allow UDP L4 GRO passthrou

In reply to Paolo Abeni

Similar to the previous commit, let even geneve
passthrou the L4 GRO packets

---
 drivers/net/bareudp.c | 1 +
 1 file changed, 1 insertion(+)
drivers/net/bareudp.c +1 -0
index 7511bca9c15ed..edfad93e7b686 100644
@@ -218,6 +218,7 @@ static struct socket *bareudp_create_sock(struct net *net, __be16 port)
 	if (err < 0)
 		return ERR_PTR(err);
 
+	udp_allow_gso(sock->sk);
 	return sock;
 }
 
Signature · 1 line
2.26.2
author maintainer
#31 lore raw

PATCH 8/8 selftests: net: add UDP GRO forwarding self-tests

In reply to Paolo Abeni

create a bunch of virtual topology and verify that GRO_FRAG_LIST and GRO_FWD aggregate the ingress packets as expected, and the aggregate packets are segmented correctly when landing on a socket

Also test L4 aggregation on top of UDP tunnel (vxlan)

---
 tools/testing/selftests/net/Makefile      |   1 +
 tools/testing/selftests/net/udpgro_fwd.sh | 251 ++++++++++++++++++++++
 2 files changed, 252 insertions(+)
 create mode 100755 tools/testing/selftests/net/udpgro_fwd.sh
tools/testing/selftests/net/Makefile +1 -0
index 25f198bec0b25..2d71b283dde36 100644
@@ -23,6 +23,7 @@ TEST_PROGS += drop_monitor_tests.sh
 TEST_PROGS += vrf_route_leaking.sh
 TEST_PROGS += bareudp.sh
 TEST_PROGS += unicast_extensions.sh
+TEST_PROGS += udpgro_fwd.sh
 TEST_PROGS_EXTENDED := in_netns.sh
 TEST_GEN_FILES =  socket nettest
 TEST_GEN_FILES += psock_fanout psock_tpacket msg_zerocopy reuseport_addr_any
tools/testing/selftests/net/udpgro_fwd.sh new file +251 -0

251 lines not on this page · raw message

Signature · 1 line
2.26.2
maintainer
#2 lore raw

Re: [PATCH net-next 1/8] udp: fixup csum for GSO receive slow path

In reply to Paolo Abeni
Quoted 30 lines · Paolo Abeni

When looping back UDP GSO over UDP tunnel packets to an UDP socket, the individual packet csum is currently set to CSUM_NONE. That causes unexpected/wrong csum validation errors later in the UDP receive path.

We could possibly addressing the issue with some additional check and csum mangling in the UDP tunnel code. Since the issue affects only this UDP receive slow path, let's set a suitable csum status there.

---
 include/net/udp.h | 18 ++++++++++++++++++
 net/ipv4/udp.c    | 10 ++++++++++
 net/ipv6/udp.c    |  5 +++++
 3 files changed, 33 insertions(+)
include/net/udp.h +6 -0
index d4d064c592328..007683eb3e113 100644
@@ -515,6 +515,24 @@ static inline struct sk_buff *udp_rcv_segment(struct sock *sk,
        return segs;
 }
 
+static inline void udp_post_segment_fix_csum(struct sk_buff *skb, int level)
+{
+       /* UDP-lite can't land here - no GRO */
+       WARN_ON_ONCE(UDP_SKB_CB(skb)->partial_cov);
+
+       /* GRO already validated the csum up to 'level', and we just

include/net/udp.h static inline struct sk_buff *udp_rcv_segment(struct sock *sk, · line 533

Quoted 10 lines
include/net/udp.h +10 -0
+        * consumed one header, update the skb accordingly
+        */
+       UDP_SKB_CB(skb)->cscov = skb->len;
+       if (level) {
+               skb->ip_summed = CHECKSUM_UNNECESSARY;
+               skb->csum_level = 0;
+       } else {
+               skb->ip_summed = CHECKSUM_NONE;
+               skb->csum_valid = 1;
+       }

why does this function also update these fields for non-tunneled packets? the commit only describes an issue with tunneled packets.

Quoted 23 lines
include/net/udp.h +2 -0
+}
+
 #ifdef CONFIG_BPF_SYSCALL
 struct sk_psock;
 struct proto *udp_bpf_get_proto(struct sock *sk, struct sk_psock *psock);
net/ipv4/udp.c +3 -0
index 4a0478b17243a..ff54135c51ffa 100644
@@ -2168,6 +2168,7 @@ static int udp_queue_rcv_one_skb(struct sock *sk, struct sk_buff *skb)
 static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
 {
        struct sk_buff *next, *segs;
+       int csum_level;
        int ret;
 
        if (likely(!udp_unexpected_gso(sk, skb)))
@@ -2175,9 +2176,18 @@ static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
 
        BUILD_BUG_ON(sizeof(struct udp_skb_cb) > SKB_GSO_CB_OFFSET);
        __skb_push(skb, -skb_mac_offset(skb));
+       csum_level = !!(skb_shinfo(skb)->gso_type &
+                       (SKB_GSO_UDP_TUNNEL | SKB_GSO_UDP_TUNNEL_CSUM));

net/ipv4/udp.c static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb) · line 2190

Quoted 10 lines
net/ipv4/udp.c +7 -0
        segs = udp_rcv_segment(sk, skb, true);
        skb_list_walk_safe(segs, skb, next) {
                __skb_pull(skb, skb_transport_offset(skb));
+
+               /* UDP GSO packets looped back after adding UDP encap land here with CHECKSUM none,
+                * instead of adding another check in the tunnel fastpath, we can force valid
+                * csums here (packets are locally generated).
+                * Additionally fixup the UDP CB
+                */
+               udp_post_segment_fix_csum(skb, csum_level);

How does this code differentiates locally generated packets with udp tunnel headers from packets arriving from the wire, for which the inner checksum may be incorrect?

Quoted 32 lines
net/ipv4/udp.c +0 -0
                ret = udp_queue_rcv_one_skb(sk, skb);
                if (ret > 0)
                        ip_protocol_deliver_rcu(dev_net(skb->dev), skb, ret);
net/ipv6/udp.c +5 -1
index d25e5a9252fdb..e7d4bf3a65c72 100644
@@ -739,16 +739,21 @@ static int udpv6_queue_rcv_one_skb(struct sock *sk, struct sk_buff *skb)
 static int udpv6_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
 {
        struct sk_buff *next, *segs;
+       int csum_level;
        int ret;
 
        if (likely(!udp_unexpected_gso(sk, skb)))
                return udpv6_queue_rcv_one_skb(sk, skb);
 
        __skb_push(skb, -skb_mac_offset(skb));
+       csum_level = !!(skb_shinfo(skb)->gso_type &
+                       (SKB_GSO_UDP_TUNNEL | SKB_GSO_UDP_TUNNEL_CSUM));
        segs = udp_rcv_segment(sk, skb, false);
        skb_list_walk_safe(segs, skb, next) {
                __skb_pull(skb, skb_transport_offset(skb));
 
+               /* see comments in udp_queue_rcv_skb() */
+               udp_post_segment_fix_csum(skb, csum_level);
                ret = udpv6_queue_rcv_one_skb(sk, skb);
                if (ret > 0)
                        ip6_protocol_deliver_rcu(dev_net(skb->dev), skb, ret,
--

2.26.2

maintainer
#12 lore raw

Re: [PATCH net-next 2/8] udp: skip fwd/list GRO for tunnel packets

In reply to Paolo Abeni
Quoted 2 lines · Paolo Abeni

If UDP GRO forwarding (or list) is enabled,

Please explicitly mention the gso type SKB_GSO_FRAGLIST. I, at least, didn't immediately grasp that gro forwarding is an alias for that.

Quoted 3 lines

and there are
udp tunnel available in the system, we could end-up doing L4 aggregation for packets targeting the UDP tunnel.

Is this specific to UDP tunnels, or can this also occur with others, such as GRE? (not implying that this patchset needs to address those at the same time)

Quoted 2 lines

That could inner protocol corruption, as no overaly network parameters is taken in account at aggregation time.

nit: overaly .. is taken -> overlay .. are taken

You mean the packets on the frag list may have mtu exceeding the mtu of the tunnel? Please make the constraint more explicit.

Quoted 2 lines

Just skip the fwd GRO if this packet could land in an UDP tunnel.

Could you make more clear that this does not skip UDP GRO, only switches from fraglist-based to pure SKB_GSO_UDP_L4.

Quoted 8 lines

The current check is broader than what is strictly needed, as the UDP tunnel could be e.g. on top of a different device, but is simple and the performance downside looks not relevant.

Fixes 9fd1ff5d2ac7 ("udp: Support UDP fraglist GRO/GSO.")
Fixes 36707061d6ba ("udp: allow forwarding of plain (non-fraglisted) UDP GRO packets")
maintainer
#18 lore raw

Re: [PATCH net-next 3/8] udp: properly complete L4 GRO over UDP tunnel packet

In reply to Paolo Abeni
Quoted 38 lines · Paolo Abeni

After the previous patch the stack can do L4 UDP aggregation on top of an UDP tunnel.

The current GRO complete code tries frag based aggregation first; in the above scenario will generate corrupted frames.

We need to try first UDP tunnel based aggregation, if the GRO packet requires that. We can use time GRO 'encap_mark' field to track the need GRO complete action. If encap_mark is set, skip the frag_list aggregation.

On tunnel encap GRO complete clear such field, so that an inner frag_list GRO complete could take action.

---
 net/ipv4/udp_offload.c | 8 +++++++-
 net/ipv6/udp_offload.c | 3 ++-
 2 files changed, 9 insertions(+), 2 deletions(-)
net/ipv4/udp_offload.c +5 -0
index 25134a3548e99..54e06b88af69a 100644
@@ -642,6 +642,11 @@ int udp_gro_complete(struct sk_buff *skb, int nhoff,
                skb_shinfo(skb)->gso_type = uh->check ? SKB_GSO_UDP_TUNNEL_CSUM
                                        : SKB_GSO_UDP_TUNNEL;
 
+               /* clear the encap mark, so that inner frag_list gro_complete
+                * can take place
+                */
+               NAPI_GRO_CB(skb)->encap_mark = 0;
+
                /* Set encapsulation before calling into inner gro_complete()
                 * functions to make them set up the inner offsets.
                 */

net/ipv4/udp_offload.c INDIRECT_CALLABLE_SCOPE int udp4_gro_complete(struct sk_buff *skb, int nhoff) · line 674

Quoted 6 lines
net/ipv4/udp_offload.c +2 -1
@@ -665,7 +670,8 @@ INDIRECT_CALLABLE_SCOPE int udp4_gro_complete(struct sk_buff *skb, int nhoff)
        const struct iphdr *iph = ip_hdr(skb);
        struct udphdr *uh = (struct udphdr *)(skb->data + nhoff);
 
-       if (NAPI_GRO_CB(skb)->is_flist) {
+       /* do fraglist only if there is no outer UDP encap (or we already processed it) */
+       if (NAPI_GRO_CB(skb)->is_flist && !NAPI_GRO_CB(skb)->encap_mark) {

Sorry, I don't follow. I thought the point was to avoid fraglist if an outer udp tunnel header is present. But the above code clears the mark and allows entering the fraglist branch exactly when such a header is encountered?

Quoted 20 lines
net/ipv4/udp_offload.c +0 -0
                uh->len = htons(skb->len - nhoff);
 
                skb_shinfo(skb)->gso_type |= (SKB_GSO_FRAGLIST|SKB_GSO_UDP_L4);
net/ipv6/udp_offload.c +2 -2
index faa823c242923..b3d9ed96e5ea5 100644
@@ -163,7 +163,8 @@ INDIRECT_CALLABLE_SCOPE int udp6_gro_complete(struct sk_buff *skb, int nhoff)
        const struct ipv6hdr *ipv6h = ipv6_hdr(skb);
        struct udphdr *uh = (struct udphdr *)(skb->data + nhoff);
 
-       if (NAPI_GRO_CB(skb)->is_flist) {
+       /* do fraglist only if there is no outer UDP encap (or we already processed it) */
+       if (NAPI_GRO_CB(skb)->is_flist && !NAPI_GRO_CB(skb)->encap_mark) {
                uh->len = htons(skb->len - nhoff);
 
                skb_shinfo(skb)->gso_type |= (SKB_GSO_FRAGLIST|SKB_GSO_UDP_L4);
--

2.26.2

maintainer
#22 lore raw

Re: [PATCH net-next 4/8] udp: never accept GSO_FRAGLIST packets

In reply to Paolo Abeni
Quoted 25 lines · Paolo Abeni

Currently the UDP protocol delivers GSO_FRAGLIST packets to the sockets without the expected segmentation.

This change addresses the issue introducing and maintaining a per socket bitmask of GSO types requiring segmentation. Enabling GSO removes SKB_GSO_UDP_L4 from such mask, while GSO_FRAGLIST packets are never accepted

Note: this also updates the 'unused' field size to really fit the otherwise existing hole. It's size become incorrect after commit bec1f6f69736 ("udp: generate gso with UDP_SEGMENT").

Fixes 9fd1ff5d2ac7 ("udp: Support UDP fraglist GRO/GSO.")
---
 include/linux/udp.h | 10 ++++++----
 net/ipv4/udp.c      | 12 +++++++++++-
 2 files changed, 17 insertions(+), 5 deletions(-)
include/linux/udp.h +0 -0
index aa84597bdc33c..6da342f15f351 100644

include/linux/udp.h struct udp_sock { · line 54

Quoted 5 lines
include/linux/udp.h +1 -1
@@ -51,7 +51,7 @@ struct udp_sock {
                                           * different encapsulation layer set
                                           * this
                                           */
-                        gro_enabled:1; /* Can accept GRO packets */
+                        gro_enabled:1; /* Request GRO aggregation */

unnecessary comment change?

include/linux/udp.h struct udp_sock { · line 74

Quoted 12 lines
include/linux/udp.h +4 -1
        /*
         * Following member retains the information to create a UDP header
         * when the socket is uncorked.
@@ -68,7 +68,10 @@ struct udp_sock {
 #define UDPLITE_SEND_CC  0x2           /* set via udplite setsockopt         */
 #define UDPLITE_RECV_CC  0x4           /* set via udplite setsocktopt        */
        __u8             pcflag;        /* marks socket as UDP-Lite if > 0    */
-       __u8             unused[3];
+       __u8             unused[1];
+       unsigned int     unexpected_gso;/* GSO types this socket can't accept,
+                                        * any of SKB_GSO_UDP_L4 or SKB_GSO_FRAGLIST
+                                        */

An extra unsigned int for this seems overkill.

Current sockets that support SKB_GSO_UDP_L4 implicitly also support SKB_GSO_FRAGLIST. This patch makes explicit that the second is not supported..

include/linux/udp.h static inline void udp_cmsg_recv(struct msghdr *msg, struct sock *sk, · line 137

Quoted 10 lines
include/linux/udp.h +1 -2
        /*
         * For encapsulation sockets.
         */
@@ -131,8 +134,7 @@ static inline void udp_cmsg_recv(struct msghdr *msg, struct sock *sk,
 
 static inline bool udp_unexpected_gso(struct sock *sk, struct sk_buff *skb)
 {
-       return !udp_sk(sk)->gro_enabled && skb_is_gso(skb) &&
-              skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4;
+       return skb_is_gso(skb) && skb_shinfo(skb)->gso_type & udp_sk(sk)->unexpected_gso;

.. just update this function as follows ?

- return !udp_sk(sk)->gro_enabled && skb_is_gso(skb) &&
- skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4;
+ return skb_is_gso(skb) &&
+ (skb_shinfo(skb)->gso_type & SKB_GSO_FRAGLIST ||
!udp_sk(sk)->gro_enabled)

where the latter is shorthand for

  (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4 && !udp_sk(sk)->gro_enabled)

but the are the only two GSO types that could arrive here.

maintainer
#32 lore raw

Re: [PATCH net-next 8/8] selftests: net: add UDP GRO forwarding self-tests

In reply to Paolo Abeni
Quoted 3 lines · Paolo Abeni

create a bunch of virtual topology and verify that GRO_FRAG_LIST and GRO_FWD aggregate the ingress

what are these constants? Aliases for SKB_GSO_FRAGLIST and ?

Quoted 6 lines

packets as expected, and the aggregate packets
are segmented correctly when landing on a socket

Also test L4 aggregation on top of UDP tunnel (vxlan)

Nice comprehensive test, thanks!

Quoted 31 lines
---
 tools/testing/selftests/net/Makefile      |   1 +
 tools/testing/selftests/net/udpgro_fwd.sh | 251 ++++++++++++++++++++++
 2 files changed, 252 insertions(+)
 create mode 100755 tools/testing/selftests/net/udpgro_fwd.sh
tools/testing/selftests/net/Makefile +1 -0
index 25f198bec0b25..2d71b283dde36 100644
@@ -23,6 +23,7 @@ TEST_PROGS += drop_monitor_tests.sh
 TEST_PROGS += vrf_route_leaking.sh
 TEST_PROGS += bareudp.sh
 TEST_PROGS += unicast_extensions.sh
+TEST_PROGS += udpgro_fwd.sh
 TEST_PROGS_EXTENDED := in_netns.sh
 TEST_GEN_FILES =  socket nettest
 TEST_GEN_FILES += psock_fanout psock_tpacket msg_zerocopy reuseport_addr_any
tools/testing/selftests/net/udpgro_fwd.sh new file +7 -0
new file mode 100755
index 0000000000000..ac7ac56a27524
@@ -0,0 +1,251 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+
+readonly BASE="ns-$(mktemp -u XXXXXX)"
+readonly SRC=2
+readonly DST=1
+readonly DST_NAT=100

tools/testing/selftests/net/udpgro_fwd.sh line 17

Quoted 10 lines
tools/testing/selftests/net/udpgro_fwd.sh new file +10 -0
+readonly NS_SRC=$BASE$SRC
+readonly NS_DST=$BASE$DST
+
+# "baremetal" network used for raw UDP traffic
+readonly BM_NET_V4=192.168.1.
+readonly BM_NET_V6=2001:db8::
+
+# "overlay" network used for UDP over UDP tunnel traffic
+readonly OL_NET_V4=172.16.1.
+readonly OL_NET_V6=2002:db8::

is it okay to use a prod64 prefix for this? should this be another subnet of 2001:db8:: instead? of fd..

author maintainer
#3 lore raw

Re: [PATCH net-next 1/8] udp: fixup csum for GSO receive slow path

In reply to Willem de Bruijn
Quoted 73 lines · Willem de Bruijn
Quoted 29 lines · Paolo Abeni

When looping back UDP GSO over UDP tunnel packets to an UDP socket, the individual packet csum is currently set to CSUM_NONE. That causes unexpected/wrong csum validation errors later in the UDP receive path.

We could possibly addressing the issue with some additional check and csum mangling in the UDP tunnel code. Since the issue affects only this UDP receive slow path, let's set a suitable csum status there.

---
 include/net/udp.h | 18 ++++++++++++++++++
 net/ipv4/udp.c    | 10 ++++++++++
 net/ipv6/udp.c    |  5 +++++
 3 files changed, 33 insertions(+)
include/net/udp.h +6 -0
index d4d064c592328..007683eb3e113 100644
@@ -515,6 +515,24 @@ static inline struct sk_buff *udp_rcv_segment(struct sock *sk,
        return segs;
 }
 
+static inline void udp_post_segment_fix_csum(struct sk_buff *skb, int level)
+{
+       /* UDP-lite can't land here - no GRO */
+       WARN_ON_ONCE(UDP_SKB_CB(skb)->partial_cov);
+
+       /* GRO already validated the csum up to 'level', and we just

include/net/udp.h static inline struct sk_buff *udp_rcv_segment(struct sock *sk, · line 533

Quoted 10 lines
include/net/udp.h +10 -0
+        * consumed one header, update the skb accordingly
+        */
+       UDP_SKB_CB(skb)->cscov = skb->len;
+       if (level) {
+               skb->ip_summed = CHECKSUM_UNNECESSARY;
+               skb->csum_level = 0;
+       } else {
+               skb->ip_summed = CHECKSUM_NONE;
+               skb->csum_valid = 1;
+       }

why does this function also update these fields for non-tunneled packets? the commit only describes an issue with tunneled packets.

Quoted 29 lines
include/net/udp.h +2 -0
+}
+
 #ifdef CONFIG_BPF_SYSCALL
 struct sk_psock;
 struct proto *udp_bpf_get_proto(struct sock *sk, struct sk_psock *psock);
net/ipv4/udp.c +6 -0
index 4a0478b17243a..ff54135c51ffa 100644
@@ -2168,6 +2168,7 @@ static int udp_queue_rcv_one_skb(struct sock *sk, struct sk_buff *skb)
 static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
 {
        struct sk_buff *next, *segs;
+       int csum_level;
        int ret;
 
        if (likely(!udp_unexpected_gso(sk, skb)))
@@ -2175,9 +2176,18 @@ static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
 
        BUILD_BUG_ON(sizeof(struct udp_skb_cb) > SKB_GSO_CB_OFFSET);
        __skb_push(skb, -skb_mac_offset(skb));
+       csum_level = !!(skb_shinfo(skb)->gso_type &
+                       (SKB_GSO_UDP_TUNNEL | SKB_GSO_UDP_TUNNEL_CSUM));
        segs = udp_rcv_segment(sk, skb, true);
        skb_list_walk_safe(segs, skb, next) {
                __skb_pull(skb, skb_transport_offset(skb));
+
+               /* UDP GSO packets looped back after adding UDP encap land here with CHECKSUM none,
+                * instead of adding another check in the tunnel fastpath, we can force valid
Quoted 8 lines

net/ipv4/udp.c static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb) · line 2190

Quoted 4 lines
net/ipv4/udp.c +4 -0
+                * csums here (packets are locally generated).
+                * Additionally fixup the UDP CB
+                */
+               udp_post_segment_fix_csum(skb, csum_level);

How does this code differentiates locally generated packets with udp tunnel headers from packets arriving from the wire, for which the inner checksum may be incorrect?

First thing first, thank you for the detailed review. Digesting all the comments will take time, so please excuse for some latency.

I'll try to reply to both your question here because the replies are related.

My understanding is that UDP GRO, when processing UDP over UDP traffic with the appropriate features bit set, will validate the checksum for both the inner and the outer header - udp{4,6}_gro_receive will be traversed twice, the fist one for the outer header, the 2nd for the inner.

So when we reach here, the inner header csum could not be incorrect, and I don't do anything to differentiate locally generated GSO packets and GROed one to keep the code simpler.

The udp_post_segment_fix_csum() always set the csum info - even for non tunneled packets to avoid additional branches/make the code more complex. The csum should be valid in any scenario.

I guess I can mention the above either in a code comment and/or in the commit message.

Cheers,

Paolo

author maintainer
#13 lore raw

Re: [PATCH net-next 2/8] udp: skip fwd/list GRO for tunnel packets

In reply to Willem de Bruijn
Quoted 5 lines · Willem de Bruijn
Quoted 1 line · Paolo Abeni

If UDP GRO forwarding (or list) is enabled,

Please explicitly mention the gso type SKB_GSO_FRAGLIST. I, at least, didn't immediately grasp that gro forwarding is an alias for that.

I see the commit message was not clear at all, I'm sorry.

The above means:

gso_type & (NETIF_F_GSO_UDP_L4 | NETIF_F_GSO_FRAGLIST)

:)

Quoted 7 lines
Quoted 3 lines

and there are
udp tunnel available in the system, we could end-up doing L4 aggregation for packets targeting the UDP tunnel.

Is this specific to UDP tunnels, or can this also occur with others, such as GRE? (not implying that this patchset needs to address those at the same time)

I did not look at that before your suggestion. Thanks for pointing out.

I think the problem is specific to UDP: when processing the outer UDP header that is potentially eligible for both NETIF_F_GSO_UDP_L4 and gro_receive aggregation and that is the root cause of the problem addressed here.

Quoted 5 lines
Quoted 2 lines

Just skip the fwd GRO if this packet could land in an UDP tunnel.

Could you make more clear that this does not skip UDP GRO, only switches from fraglist-based to pure SKB_GSO_UDP_L4.

Sure, I'll try to rewrite the commit message.

Thanks!

Paolo

author maintainer
#19 lore raw

Re: [PATCH net-next 3/8] udp: properly complete L4 GRO over UDP tunnel packet

In reply to Willem de Bruijn
Quoted 41 lines · Willem de Bruijn
Quoted 40 lines · Paolo Abeni

After the previous patch the stack can do L4 UDP aggregation on top of an UDP tunnel.

The current GRO complete code tries frag based aggregation first; in the above scenario will generate corrupted frames.

We need to try first UDP tunnel based aggregation, if the GRO packet requires that. We can use time GRO 'encap_mark' field to track the need GRO complete action. If encap_mark is set, skip the frag_list aggregation.

On tunnel encap GRO complete clear such field, so that an inner frag_list GRO complete could take action.

---
 net/ipv4/udp_offload.c | 8 +++++++-
 net/ipv6/udp_offload.c | 3 ++-
 2 files changed, 9 insertions(+), 2 deletions(-)
net/ipv4/udp_offload.c +5 -0
index 25134a3548e99..54e06b88af69a 100644
@@ -642,6 +642,11 @@ int udp_gro_complete(struct sk_buff *skb, int nhoff,
                skb_shinfo(skb)->gso_type = uh->check ? SKB_GSO_UDP_TUNNEL_CSUM
                                        : SKB_GSO_UDP_TUNNEL;
 
+               /* clear the encap mark, so that inner frag_list gro_complete
+                * can take place
+                */
+               NAPI_GRO_CB(skb)->encap_mark = 0;
+
                /* Set encapsulation before calling into inner gro_complete()
                 * functions to make them set up the inner offsets.
                 */
@@ -665,7 +670,8 @@ INDIRECT_CALLABLE_SCOPE int udp4_gro_complete(struct sk_buff *skb, int nhoff)
        const struct iphdr *iph = ip_hdr(skb);
        struct udphdr *uh = (struct udphdr *)(skb->data + nhoff);
 
Quoted 8 lines

net/ipv4/udp_offload.c INDIRECT_CALLABLE_SCOPE int udp4_gro_complete(struct sk_buff *skb, int nhoff) · line 674

Quoted 3 lines
net/ipv4/udp_offload.c +2 -1
-       if (NAPI_GRO_CB(skb)->is_flist) {
+       /* do fraglist only if there is no outer UDP encap (or we already processed it) */
+       if (NAPI_GRO_CB(skb)->is_flist && !NAPI_GRO_CB(skb)->encap_mark) {

Sorry, I don't follow. I thought the point was to avoid fraglist if an outer udp tunnel header is present. But the above code clears the mark and allows entering the fraglist branch exactly when such a header is encountered?

The relevant UDP packet has gone through:

[l2/l3 GRO] -> udp_gro_receive -> udp_sk(sk)->gro_receive -> [some more GRO layers] -> udp_gro_receive (again)

The first udp_gro_receive set NAPI_GRO_CB(skb)->encap_mark, the latter udp_gro_receive set NAPI_GRO_CB(skb)->is_flist.

Then, at GRO complete time:

[l2/l3 GRO] -> udp{4,6}_gro_complete -> udp_sk(sk)->gro_complete -> [more GRO layers] -> udp{4,6}_gro_complete (again).

In the first udp{4,6}_gro_complete invocation 'encap_mark' is 1, so with this patch we do the 'udp_sk(sk)->gro_complete' path. In the second udp{4,6}_gro_complete invocation 'encap_mark' has been cleared (by udp_gro_complete), so we do the SKB_GSO_FRAGLIST completion.

In case SKB_GSO_FRAGLIST with no UDP tunnel, 'encap_mark' is 0 and we do the SKB_GSO_FRAGLIST completion.

Another alternative, possibly more readable, would be avoid clearing 'encap_mark' in udp_gro_complete() and replacing the above check with:

	if (NAPI_GRO_CB(skb)->is_flist &&
            (!NAPI_GRO_CB(skb)->encap_mark ||

(NAPI_GRO_CB(skb)->encap_mark && skb->encapsulation))) {

I opted otherwise to simplify the conditional expression.

Please let me know if the above is somewhat more clear and if you have preferecens between the two options.

Cheers,

Paolo

author maintainer
#23 lore raw

Re: [PATCH net-next 4/8] udp: never accept GSO_FRAGLIST packets

In reply to Willem de Bruijn
Quoted 20 lines · Willem de Bruijn
Quoted 19 lines · Paolo Abeni

Currently the UDP protocol delivers GSO_FRAGLIST packets to the sockets without the expected segmentation.

This change addresses the issue introducing and maintaining a per socket bitmask of GSO types requiring segmentation. Enabling GSO removes SKB_GSO_UDP_L4 from such mask, while GSO_FRAGLIST packets are never accepted

Note: this also updates the 'unused' field size to really fit the otherwise existing hole. It's size become incorrect after commit bec1f6f69736 ("udp: generate gso with UDP_SEGMENT").

Fixes 9fd1ff5d2ac7 ("udp: Support UDP fraglist GRO/GSO.")
---
 include/linux/udp.h | 10 ++++++----
 net/ipv4/udp.c      | 12 +++++++++++-
 2 files changed, 17 insertions(+), 5 deletions(-)
Quoted 12 lines

include/linux/udp.h struct udp_sock { · line 54

Quoted 10 lines
include/linux/udp.h +1 -1
index aa84597bdc33c..6da342f15f351 100644
@@ -51,7 +51,7 @@ struct udp_sock {
                                           * different encapsulation layer set
                                           * this
                                           */
-                        gro_enabled:1; /* Can accept GRO packets */
+                        gro_enabled:1; /* Request GRO aggregation */

unnecessary comment change?

Before this patch 'gro_enabled' was used in udp_unexpected_gso() to check for GSO packets acceptance, after this patch such field is not used there anymore, so does not carry explicilty the 'accept GRO packets' semantic.

Anyway I don't have strong feeling regarding changing or not this comment

Quoted 6 lines
Quoted 6 lines
include/linux/udp.h +0 -0
        /*
         * Following member retains the information to create a UDP header
         * when the socket is uncorked.
@@ -68,7 +68,10 @@ struct udp_sock {
 #define UDPLITE_SEND_CC  0x2           /* set via udplite setsockopt         */
 #define UDPLITE_RECV_CC  0x4           /* set via udplite setsocktopt        */
Quoted 8 lines

include/linux/udp.h struct udp_sock { · line 74

Quoted 6 lines
include/linux/udp.h +4 -1
        __u8             pcflag;        /* marks socket as UDP-Lite if > 0    */
-       __u8             unused[3];
+       __u8             unused[1];
+       unsigned int     unexpected_gso;/* GSO types this socket can't accept,
+                                        * any of SKB_GSO_UDP_L4 or SKB_GSO_FRAGLIST
+                                        */

An extra unsigned int for this seems overkill.

Should be more clear after the next patch.

Using an explicit 'acceptable GSO types' field makes the patch 5/8 quite simple.

After this patch the 'udp_sock' struct size remains unchanged and even the number of 'udp_sock' cachelines touched for every packet is unchanged.

I opted for an 'unsigned int' so that I could simply copy a gso_type there.

Quoted 23 lines

Current sockets that support SKB_GSO_UDP_L4 implicitly also support SKB_GSO_FRAGLIST. This patch makes explicit that the second is not supported..

include/linux/udp.h static inline void udp_cmsg_recv(struct msghdr *msg, struct sock *sk, · line 137

Quoted 10 lines
include/linux/udp.h +1 -2
        /*
         * For encapsulation sockets.
         */
@@ -131,8 +134,7 @@ static inline void udp_cmsg_recv(struct msghdr *msg, struct sock *sk,
 
 static inline bool udp_unexpected_gso(struct sock *sk, struct sk_buff *skb)
 {
-       return !udp_sk(sk)->gro_enabled && skb_is_gso(skb) &&
-              skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4;
+       return skb_is_gso(skb) && skb_shinfo(skb)->gso_type & udp_sk(sk)->unexpected_gso;

.. just update this function as follows ?

- return !udp_sk(sk)->gro_enabled && skb_is_gso(skb) &&
- skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4;

(quoted diff) +2 -0
+       return skb_is_gso(skb) &&
+                 (skb_shinfo(skb)->gso_type & SKB_GSO_FRAGLIST ||

!udp_sk(sk)->gro_enabled)

Quoted 5 lines

where the latter is shorthand for

  (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4 && !udp_sk(sk)->gro_enabled)

but the are the only two GSO types that could arrive here.

With the above patch 5/8 becomes messy ?!?

Thanks!

Paolo

author maintainer
#33 lore raw

Re: [PATCH net-next 8/8] selftests: net: add UDP GRO forwarding self-tests

In reply to Willem de Bruijn
Quoted 5 lines · Willem de Bruijn
Quoted 2 lines · Paolo Abeni

create a bunch of virtual topology and verify that GRO_FRAG_LIST and GRO_FWD aggregate the ingress

what are these constants? Aliases for SKB_GSO_FRAGLIST and ?

Well, I was inaccurate in many ways :(

I was speaking about device features, so it's really:

NETIF_F_GSO_FRAGLIST and NETIF_F_GRO_UDP_FWD

I really would love a commit message "compiler" to clarify this sort of thing before submission;)

Thanks,

Paolo

author maintainer
#34 lore raw

Re: [PATCH net-next 8/8] selftests: net: add UDP GRO forwarding self-tests

In reply to Willem de Bruijn

Hello,

Quoted 18 lines · Willem de Bruijn
Quoted 18 lines
tools/testing/selftests/net/udpgro_fwd.sh new file +12 -0
new file mode 100755
index 0000000000000..ac7ac56a27524
@@ -0,0 +1,251 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+
+readonly BASE="ns-$(mktemp -u XXXXXX)"
+readonly SRC=2
+readonly DST=1
+readonly DST_NAT=100
+readonly NS_SRC=$BASE$SRC
+readonly NS_DST=$BASE$DST
+
+# "baremetal" network used for raw UDP traffic
+readonly BM_NET_V4=192.168.1.
Quoted 8 lines

tools/testing/selftests/net/udpgro_fwd.sh line 17

Quoted 5 lines
tools/testing/selftests/net/udpgro_fwd.sh new file +5 -0
+readonly BM_NET_V6=2001:db8::
+
+# "overlay" network used for UDP over UDP tunnel traffic
+readonly OL_NET_V4=172.16.1.
+readonly OL_NET_V6=2002:db8::

is it okay to use a prod64 prefix for this? should this be another subnet of 2001:db8:: instead? of fd..

It looks like this comment slipped out of my sight... yep, I'll use 2001:db8:1:: for the overlay network in the next iteration.

Thanks!

Paolo

maintainer
#4 lore raw

Re: [PATCH net-next 1/8] udp: fixup csum for GSO receive slow path

In reply to Paolo Abeni
Quoted 84 lines · Paolo Abeni
Quoted 73 lines · Willem de Bruijn
Quoted 29 lines · Paolo Abeni

When looping back UDP GSO over UDP tunnel packets to an UDP socket, the individual packet csum is currently set to CSUM_NONE. That causes unexpected/wrong csum validation errors later in the UDP receive path.

We could possibly addressing the issue with some additional check and csum mangling in the UDP tunnel code. Since the issue affects only this UDP receive slow path, let's set a suitable csum status there.

---
 include/net/udp.h | 18 ++++++++++++++++++
 net/ipv4/udp.c    | 10 ++++++++++
 net/ipv6/udp.c    |  5 +++++
 3 files changed, 33 insertions(+)
include/net/udp.h +6 -0
index d4d064c592328..007683eb3e113 100644
@@ -515,6 +515,24 @@ static inline struct sk_buff *udp_rcv_segment(struct sock *sk,
        return segs;
 }
 
+static inline void udp_post_segment_fix_csum(struct sk_buff *skb, int level)
+{
+       /* UDP-lite can't land here - no GRO */
+       WARN_ON_ONCE(UDP_SKB_CB(skb)->partial_cov);
+
+       /* GRO already validated the csum up to 'level', and we just

include/net/udp.h static inline struct sk_buff *udp_rcv_segment(struct sock *sk, · line 533

Quoted 10 lines
include/net/udp.h +10 -0
+        * consumed one header, update the skb accordingly
+        */
+       UDP_SKB_CB(skb)->cscov = skb->len;
+       if (level) {
+               skb->ip_summed = CHECKSUM_UNNECESSARY;
+               skb->csum_level = 0;
+       } else {
+               skb->ip_summed = CHECKSUM_NONE;
+               skb->csum_valid = 1;
+       }

why does this function also update these fields for non-tunneled packets? the commit only describes an issue with tunneled packets.

Quoted 29 lines
include/net/udp.h +2 -0
+}
+
 #ifdef CONFIG_BPF_SYSCALL
 struct sk_psock;
 struct proto *udp_bpf_get_proto(struct sock *sk, struct sk_psock *psock);
net/ipv4/udp.c +6 -0
index 4a0478b17243a..ff54135c51ffa 100644
@@ -2168,6 +2168,7 @@ static int udp_queue_rcv_one_skb(struct sock *sk, struct sk_buff *skb)
 static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
 {
        struct sk_buff *next, *segs;
+       int csum_level;
        int ret;
 
        if (likely(!udp_unexpected_gso(sk, skb)))
@@ -2175,9 +2176,18 @@ static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
 
        BUILD_BUG_ON(sizeof(struct udp_skb_cb) > SKB_GSO_CB_OFFSET);
        __skb_push(skb, -skb_mac_offset(skb));
+       csum_level = !!(skb_shinfo(skb)->gso_type &
+                       (SKB_GSO_UDP_TUNNEL | SKB_GSO_UDP_TUNNEL_CSUM));
        segs = udp_rcv_segment(sk, skb, true);
        skb_list_walk_safe(segs, skb, next) {
                __skb_pull(skb, skb_transport_offset(skb));
+
+               /* UDP GSO packets looped back after adding UDP encap land here with CHECKSUM none,
+                * instead of adding another check in the tunnel fastpath, we can force valid
Quoted 8 lines

net/ipv4/udp.c static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb) · line 2190

Quoted 4 lines
net/ipv4/udp.c +4 -0
+                * csums here (packets are locally generated).
+                * Additionally fixup the UDP CB
+                */
+               udp_post_segment_fix_csum(skb, csum_level);

How does this code differentiates locally generated packets with udp tunnel headers from packets arriving from the wire, for which the inner checksum may be incorrect?

Quoted 2 lines

First thing first, thank you for the detailed review. Digesting all the comments will take time, so please excuse for some latency.

Apologies for my own delayed response. I also need to take time to understand the existing code and diffs :) And have a few questions.

Quoted 4 lines

I'll try to reply to both your question here because the replies are related.

My understanding is that UDP GRO, when processing UDP over UDP traffic

This is a UDP GSO packet egress packet that was further encapsulated with a GSO_UDP_TUNNEL on egress, then looped to the ingress path?

Then in the ingress path it has traversed the GRO layer.

Is this veth with XDP? That seems unlikely for GSO packets. But there aren't many paths that will loop a packet through napi_gro_receive or napi_gro_frags.

Quoted 4 lines

with the appropriate features bit set, will validate the checksum for both the inner and the outer header - udp{4,6}_gro_receive will be traversed twice, the fist one for the outer header, the 2nd for the inner.

GRO will validate multiple levels of checksums with CHECKSUM_COMPLETE. It can only validate the outer checksum with CHECKSUM_UNNECESSARY, I believe?

As for looped packets with CHECKSUM_PARTIAL: we definitely have found bugs in that path before. I think it's fine to set csum_valid on any packets that can unambiguously be identified as such. Hence the detailed questions above on which exact packets this code is targeting, so that there are not accidental false positives that look the same but have a different ip_summed.

Quoted 3 lines

So when we reach here, the inner header csum could not be incorrect, and I don't do anything to differentiate locally generated GSO packets and GROed one to keep the code simpler.

But the correctness of the patch depends on them being locally generated, if I'm not mistaken. Then I would make it explicit.

Quoted 6 lines

The udp_post_segment_fix_csum() always set the csum info - even for non tunneled packets to avoid additional branches/make the code more complex. The csum should be valid in any scenario.

I guess I can mention the above either in a code comment and/or in the commit message.

I don't follow this. The patch adds an else clause for non-tunneled packets. Why does it update these? It does not seem like avoiding additional branches, but adding them. So I must be missing something more involved.

Quoted 4 lines

Cheers,

Paolo

maintainer
#5 lore raw

Re: [PATCH net-next 1/8] udp: fixup csum for GSO receive slow path

Quoted 10 lines
Quoted 10 lines
Quoted 10 lines
Quoted 10 lines
net/ipv4/udp.c +1 -0
@@ -2168,6 +2168,7 @@ static int udp_queue_rcv_one_skb(struct sock *sk, struct sk_buff *skb)
 static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
 {
        struct sk_buff *next, *segs;
+       int csum_level;
        int ret;
 
        if (likely(!udp_unexpected_gso(sk, skb)))
@@ -2175,9 +2176,18 @@ static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
 
Quoted 13 lines
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
net/ipv4/udp.c +2 -0
        BUILD_BUG_ON(sizeof(struct udp_skb_cb) > SKB_GSO_CB_OFFSET);
        __skb_push(skb, -skb_mac_offset(skb));
+       csum_level = !!(skb_shinfo(skb)->gso_type &
+                       (SKB_GSO_UDP_TUNNEL | SKB_GSO_UDP_TUNNEL_CSUM));
        segs = udp_rcv_segment(sk, skb, true);
Quoted 8 lines
Quoted 8 lines

net/ipv4/udp.c static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb) · line 2189

Quoted 8 lines
net/ipv4/udp.c +6 -0
        skb_list_walk_safe(segs, skb, next) {
                __skb_pull(skb, skb_transport_offset(skb));
+
+               /* UDP GSO packets looped back after adding UDP encap land here with CHECKSUM none,
+                * instead of adding another check in the tunnel fastpath, we can force valid
+                * csums here (packets are locally generated).
+                * Additionally fixup the UDP CB
+                */

Btw, instead of this comment plus a comment in the ipv6 code that points back to this ipv4 comment, I would move the explanation to the udp_post_segment_fix_csum function itself.

maintainer
#14 lore raw

Re: [PATCH net-next 2/8] udp: skip fwd/list GRO for tunnel packets

In reply to Paolo Abeni
Quoted 7 lines
Quoted 7 lines
Quoted 3 lines

and there are
udp tunnel available in the system, we could end-up doing L4 aggregation for packets targeting the UDP tunnel.

Is this specific to UDP tunnels, or can this also occur with others, such as GRE? (not implying that this patchset needs to address those at the same time)

I suppose GRE tunnels do not advertise GSO_UDP_L4 support, so GSO packets would get segmented before entering the tunnel device.

Forwarded datagrams exceeding egress device MTU (whether tunnel or not) is a wholly separate problem.

Quoted 6 lines

I did not look at that before your suggestion. Thanks for pointing out.

I think the problem is specific to UDP: when processing the outer UDP header that is potentially eligible for both NETIF_F_GSO_UDP_L4 and gro_receive aggregation and that is the root cause of the problem addressed here.

Can you elaborate on the exact problem? The commit mentions "inner protocol corruption, as no overaly network parameters is taken in account at aggregation time."

My understanding is that these are udp gro aggregated GSO_UDP_L4 packets forwarded to a udp tunnel device. They are not encapsulated yet. Which overlay network parameters are not, but should have been, taken account at aggregation time?

Quoted 14 lines
Quoted 7 lines
Quoted 4 lines

Just skip the fwd GRO if this packet could land in an UDP tunnel.

Could you make more clear that this does not skip UDP GRO, only switches from fraglist-based to pure SKB_GSO_UDP_L4.

Sure, I'll try to rewrite the commit message.

Thanks!

Paolo

maintainer
#20 lore raw

Re: [PATCH net-next 3/8] udp: properly complete L4 GRO over UDP tunnel packet

In reply to Paolo Abeni
Quoted 76 lines · Paolo Abeni
Quoted 41 lines · Willem de Bruijn
Quoted 40 lines · Paolo Abeni

After the previous patch the stack can do L4 UDP aggregation on top of an UDP tunnel.

The current GRO complete code tries frag based aggregation first; in the above scenario will generate corrupted frames.

We need to try first UDP tunnel based aggregation, if the GRO packet requires that. We can use time GRO 'encap_mark' field to track the need GRO complete action. If encap_mark is set, skip the frag_list aggregation.

On tunnel encap GRO complete clear such field, so that an inner frag_list GRO complete could take action.

---
 net/ipv4/udp_offload.c | 8 +++++++-
 net/ipv6/udp_offload.c | 3 ++-
 2 files changed, 9 insertions(+), 2 deletions(-)
net/ipv4/udp_offload.c +5 -0
index 25134a3548e99..54e06b88af69a 100644
@@ -642,6 +642,11 @@ int udp_gro_complete(struct sk_buff *skb, int nhoff,
                skb_shinfo(skb)->gso_type = uh->check ? SKB_GSO_UDP_TUNNEL_CSUM
                                        : SKB_GSO_UDP_TUNNEL;
 
+               /* clear the encap mark, so that inner frag_list gro_complete
+                * can take place
+                */
+               NAPI_GRO_CB(skb)->encap_mark = 0;
+
                /* Set encapsulation before calling into inner gro_complete()
                 * functions to make them set up the inner offsets.
                 */
@@ -665,7 +670,8 @@ INDIRECT_CALLABLE_SCOPE int udp4_gro_complete(struct sk_buff *skb, int nhoff)
        const struct iphdr *iph = ip_hdr(skb);
        struct udphdr *uh = (struct udphdr *)(skb->data + nhoff);
 
Quoted 8 lines

net/ipv4/udp_offload.c INDIRECT_CALLABLE_SCOPE int udp4_gro_complete(struct sk_buff *skb, int nhoff) · line 674

Quoted 3 lines
net/ipv4/udp_offload.c +2 -1
-       if (NAPI_GRO_CB(skb)->is_flist) {
+       /* do fraglist only if there is no outer UDP encap (or we already processed it) */
+       if (NAPI_GRO_CB(skb)->is_flist && !NAPI_GRO_CB(skb)->encap_mark) {

Sorry, I don't follow. I thought the point was to avoid fraglist if an outer udp tunnel header is present. But the above code clears the mark and allows entering the fraglist branch exactly when such a header is encountered?

The relevant UDP packet has gone through:

[l2/l3 GRO] -> udp_gro_receive -> udp_sk(sk)->gro_receive -> [some more GRO layers] -> udp_gro_receive (again)

The first udp_gro_receive set NAPI_GRO_CB(skb)->encap_mark, the latter udp_gro_receive set NAPI_GRO_CB(skb)->is_flist.

Then, at GRO complete time:

[l2/l3 GRO] -> udp{4,6}_gro_complete -> udp_sk(sk)->gro_complete -> [more GRO layers] -> udp{4,6}_gro_complete (again).

In the first udp{4,6}_gro_complete invocation 'encap_mark' is 1, so with this patch we do the 'udp_sk(sk)->gro_complete' path. In the second udp{4,6}_gro_complete invocation 'encap_mark' has been cleared (by udp_gro_complete), so we do the SKB_GSO_FRAGLIST completion.

In case SKB_GSO_FRAGLIST with no UDP tunnel, 'encap_mark' is 0 and we do the SKB_GSO_FRAGLIST completion.

Another alternative, possibly more readable, would be avoid clearing 'encap_mark' in udp_gro_complete() and replacing the above check with:

Quoted 8 lines
        if (NAPI_GRO_CB(skb)->is_flist &&
            (!NAPI_GRO_CB(skb)->encap_mark ||
             (NAPI_GRO_CB(skb)->encap_mark && skb->encapsulation))) {

I opted otherwise to simplify the conditional expression.

Please let me know if the above is somewhat more clear and if you have preferecens between the two options.

Got it now, thanks. Yes, this patch makes sense.

When the GRO layer has built a GSO packet with both inner UDP (GSO_UDP_L4) and UDP tunnel header (GSO_UDP_TUNNEL), then on completion udp{4,6}_gro_complete will be called twice. This function will enter its is_flist branch immediately, even though that is only correct on the second call, as GSO_FRAGLIST is only relevant for the inner packet. Skip this while encap_mark == 1, identifying processing of the outer tunnel header. Clear the field to enter the path on the next round, for the inner header.

This is subject to only supporting a single layer of encap, but that is indeed the current state afaik.

maintainer
#24 lore raw

Re: [PATCH net-next 4/8] udp: never accept GSO_FRAGLIST packets

In reply to Paolo Abeni
Quoted 38 lines · Paolo Abeni
Quoted 21 lines · Willem de Bruijn
Quoted 19 lines · Paolo Abeni

Currently the UDP protocol delivers GSO_FRAGLIST packets to the sockets without the expected segmentation.

This change addresses the issue introducing and maintaining a per socket bitmask of GSO types requiring segmentation. Enabling GSO removes SKB_GSO_UDP_L4 from such mask, while GSO_FRAGLIST packets are never accepted

Note: this also updates the 'unused' field size to really fit the otherwise existing hole. It's size become incorrect after commit bec1f6f69736 ("udp: generate gso with UDP_SEGMENT").

Fixes 9fd1ff5d2ac7 ("udp: Support UDP fraglist GRO/GSO.")
---
 include/linux/udp.h | 10 ++++++----
 net/ipv4/udp.c      | 12 +++++++++++-
 2 files changed, 17 insertions(+), 5 deletions(-)
Quoted 14 lines

include/linux/udp.h struct udp_sock { · line 74

Quoted 12 lines

/*
* Following member retains the information to create a UDP header
* when the socket is uncorked.

include/linux/udp.h +4 -1
@@ -68,7 +68,10 @@ struct udp_sock {
 #define UDPLITE_SEND_CC  0x2           /* set via udplite setsockopt         */
 #define UDPLITE_RECV_CC  0x4           /* set via udplite setsocktopt        */
        __u8             pcflag;        /* marks socket as UDP-Lite if > 0    */
-       __u8             unused[3];
+       __u8             unused[1];
+       unsigned int     unexpected_gso;/* GSO types this socket can't accept,
+                                        * any of SKB_GSO_UDP_L4 or SKB_GSO_FRAGLIST
+                                        */

An extra unsigned int for this seems overkill.

Quoted 8 lines

Should be more clear after the next patch.

Using an explicit 'acceptable GSO types' field makes the patch 5/8 quite simple.

After this patch the 'udp_sock' struct size remains unchanged and even the number of 'udp_sock' cachelines touched for every packet is unchanged.

But there is opportunity cost, of course. Next time we need to add something to the struct, we will add a new cacheline.

A 32-bit field for just 2 bits, where 1 already exists does seem like overkill.

More importantly, I just think it's less obvious code than a pair of fields

accepts_udp_l4:1,
accepts_udp_fraglist:1,

Local sockets can only accept the first, as there does not exist an interface to pass along the multiple frag sizes that a frag_list based approach might have.

Sockets with encap_rcv != NULL may opt-in to being able to handle either.

I think explicit code will be more maintainable. At the cost of perhaps two branches instead of one, admittedly. But that seems premature optimization.

author maintainer
#6 lore raw

Re: [PATCH net-next 1/8] udp: fixup csum for GSO receive slow path

In reply to Willem de Bruijn
Quoted 95 lines · Willem de Bruijn
Quoted 83 lines · Paolo Abeni
Quoted 73 lines · Willem de Bruijn
Quoted 29 lines · Paolo Abeni

When looping back UDP GSO over UDP tunnel packets to an UDP socket, the individual packet csum is currently set to CSUM_NONE. That causes unexpected/wrong csum validation errors later in the UDP receive path.

We could possibly addressing the issue with some additional check and csum mangling in the UDP tunnel code. Since the issue affects only this UDP receive slow path, let's set a suitable csum status there.

---
 include/net/udp.h | 18 ++++++++++++++++++
 net/ipv4/udp.c    | 10 ++++++++++
 net/ipv6/udp.c    |  5 +++++
 3 files changed, 33 insertions(+)
include/net/udp.h +6 -0
index d4d064c592328..007683eb3e113 100644
@@ -515,6 +515,24 @@ static inline struct sk_buff *udp_rcv_segment(struct sock *sk,
        return segs;
 }
 
+static inline void udp_post_segment_fix_csum(struct sk_buff *skb, int level)
+{
+       /* UDP-lite can't land here - no GRO */
+       WARN_ON_ONCE(UDP_SKB_CB(skb)->partial_cov);
+
+       /* GRO already validated the csum up to 'level', and we just

include/net/udp.h static inline struct sk_buff *udp_rcv_segment(struct sock *sk, · line 533

Quoted 10 lines
include/net/udp.h +10 -0
+        * consumed one header, update the skb accordingly
+        */
+       UDP_SKB_CB(skb)->cscov = skb->len;
+       if (level) {
+               skb->ip_summed = CHECKSUM_UNNECESSARY;
+               skb->csum_level = 0;
+       } else {
+               skb->ip_summed = CHECKSUM_NONE;
+               skb->csum_valid = 1;
+       }

why does this function also update these fields for non-tunneled packets? the commit only describes an issue with tunneled packets.

Quoted 29 lines
include/net/udp.h +2 -0
+}
+
 #ifdef CONFIG_BPF_SYSCALL
 struct sk_psock;
 struct proto *udp_bpf_get_proto(struct sock *sk, struct sk_psock *psock);
net/ipv4/udp.c +6 -0
index 4a0478b17243a..ff54135c51ffa 100644
@@ -2168,6 +2168,7 @@ static int udp_queue_rcv_one_skb(struct sock *sk, struct sk_buff *skb)
 static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
 {
        struct sk_buff *next, *segs;
+       int csum_level;
        int ret;
 
        if (likely(!udp_unexpected_gso(sk, skb)))
@@ -2175,9 +2176,18 @@ static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
 
        BUILD_BUG_ON(sizeof(struct udp_skb_cb) > SKB_GSO_CB_OFFSET);
        __skb_push(skb, -skb_mac_offset(skb));
+       csum_level = !!(skb_shinfo(skb)->gso_type &
+                       (SKB_GSO_UDP_TUNNEL | SKB_GSO_UDP_TUNNEL_CSUM));
        segs = udp_rcv_segment(sk, skb, true);
        skb_list_walk_safe(segs, skb, next) {
                __skb_pull(skb, skb_transport_offset(skb));
+
+               /* UDP GSO packets looped back after adding UDP encap land here with CHECKSUM none,
+                * instead of adding another check in the tunnel fastpath, we can force valid
Quoted 8 lines

net/ipv4/udp.c static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb) · line 2190

Quoted 4 lines
net/ipv4/udp.c +4 -0
+                * csums here (packets are locally generated).
+                * Additionally fixup the UDP CB
+                */
+               udp_post_segment_fix_csum(skb, csum_level);

How does this code differentiates locally generated packets with udp tunnel headers from packets arriving from the wire, for which the inner checksum may be incorrect?

Quoted 2 lines

First thing first, thank you for the detailed review. Digesting all the comments will take time, so please excuse for some latency.

Apologies for my own delayed response. I also need to take time to understand the existing code and diffs :) And have a few questions.

Quoted 4 lines

I'll try to reply to both your question here because the replies are related.

My understanding is that UDP GRO, when processing UDP over UDP traffic

Quoted 8 lines

This is a UDP GSO packet egress packet that was further encapsulated with a GSO_UDP_TUNNEL on egress, then looped to the ingress path?

Then in the ingress path it has traversed the GRO layer.

Is this veth with XDP? That seems unlikely for GSO packets. But there aren't many paths that will loop a packet through napi_gro_receive or napi_gro_frags.

This patch addresses the following scenario:

sk ->vxlan -> veth -> (xdp in use, TSO disabled, GRO on) -> veth -> vxlan -> sk

What I meant here is that the issue is not visible with:

(baremetal, NETIF_F_GRO_UDP_FWD | NETIF_F_GRO_FRAGLIST enabled -> vxlan -> sk

Quoted 8 lines
Quoted 4 lines

with the appropriate features bit set, will validate the checksum for both the inner and the outer header - udp{4,6}_gro_receive will be traversed twice, the fist one for the outer header, the 2nd for the inner.

GRO will validate multiple levels of checksums with CHECKSUM_COMPLETE. It can only validate the outer checksum with CHECKSUM_UNNECESSARY, I believe?

I possibly miss some bits here ?!?

AFAICS:

udp4_gro_receive() -> skb_gro_checksum_validate_zero_check() -> __skb_gro_checksum_validate -> (if not already valid) __skb_gro_checksum_validate_complete() -> (if not CHECKSUM_COMPLETE) __skb_gro_checksum_complete()

the latter will validate the UDP checksum at the current nesting level (and set the csum-related bits in the GRO skb cb to the same status as CHECKSUM_COMPLETE)

When processing UDP over UDP tunnel packet, the gro call chain will be:

[l2/l3 GRO] -> udp4_gro_receive (validate outher header csum) -> udp_sk(sk)->gro_receive -> [other gro layers] -> udp4_gro_receive (validate inner header csum) -> ...

Quoted 6 lines

As for looped packets with CHECKSUM_PARTIAL: we definitely have found bugs in that path before. I think it's fine to set csum_valid on any packets that can unambiguously be identified as such. Hence the detailed questions above on which exact packets this code is targeting, so that there are not accidental false positives that look the same but have a different ip_summed.

I see this change is controversial. Since the addressed scenario is really a corner case, a simpler alternative would be replacing udp_post_segment_fix_csum with:

static inline void udp_post_segment_fix_cb(struct sk_buff *skb, int level)

{
	/* UDP-lite can't land here - no GRO */
	WARN_ON_ONCE(UDP_SKB_CB(skb)->partial_cov);

	/* UDP CB mirrors the GSO packet, we must re-init it */
	UDP_SKB_CB(skb)->cscov = skb->len;
}

the downside will be an additional, later, unneeded csum validation for the

sk ->vxlan -> veth -> (xdp in use, TSO disabled, GRO on) -> veth -> vxlan -> sk

scenario. WDYT?

Thanks!

Paolo

author maintainer
#15 lore raw

!

In reply to Willem de Bruijn
Quoted 7 lines · Willem de Bruijn
Quoted 6 lines

I did not look at that before your suggestion. Thanks for pointing out.

I think the problem is specific to UDP: when processing the outer UDP header that is potentially eligible for both NETIF_F_GSO_UDP_L4 and gro_receive aggregation and that is the root cause of the problem addressed here.

Quoted 8 lines

Can you elaborate on the exact problem? The commit mentions "inner protocol corruption, as no overaly network parameters is taken in account at aggregation time."

My understanding is that these are udp gro aggregated GSO_UDP_L4 packets forwarded to a udp tunnel device. They are not encapsulated yet. Which overlay network parameters are not, but should have been, taken account at aggregation time?

The scenario is as follow:

* a NIC has NETIF_F_GRO_UDP_FWD or NETIF_F_GRO_FRAGLIST enabled
* an UDP tunnel is configured/enabled in the system
* the above NIC receives some UDP-tunneled packets, targeting the mentioned tunnel
* the packets go through gro_receive and they reache 'udp_gro_receive()' while processing the outer UDP header.

without this patch, udp_gro_receive_segment() will kick in and the outer UDP header will be aggregated according to SKB_GSO_FRAGLIST or SKB_GSO_UDP_L4, even if this is really e.g. a vxlan packet.

Different vxlan ids will be ignored/aggregated to the same GSO packet. Inner headers will be ignored, too, so that e.g. TCP over vxlan push packets will be held in the GRO engine till the next flush, etc.

Please let me know if the above is more clear.

Thanks!

Paolo

author maintainer
#25 lore raw

Re: [PATCH net-next 4/8] udp: never accept GSO_FRAGLIST packets

In reply to Willem de Bruijn
Quoted 56 lines · Willem de Bruijn
Quoted 36 lines · Paolo Abeni
Quoted 20 lines · Willem de Bruijn
Quoted 19 lines · Paolo Abeni

Currently the UDP protocol delivers GSO_FRAGLIST packets to the sockets without the expected segmentation.

This change addresses the issue introducing and maintaining a per socket bitmask of GSO types requiring segmentation. Enabling GSO removes SKB_GSO_UDP_L4 from such mask, while GSO_FRAGLIST packets are never accepted

Note: this also updates the 'unused' field size to really fit the otherwise existing hole. It's size become incorrect after commit bec1f6f69736 ("udp: generate gso with UDP_SEGMENT").

Fixes 9fd1ff5d2ac7 ("udp: Support UDP fraglist GRO/GSO.")
---
 include/linux/udp.h | 10 ++++++----
 net/ipv4/udp.c      | 12 +++++++++++-
 2 files changed, 17 insertions(+), 5 deletions(-)
Quoted 14 lines

include/linux/udp.h struct udp_sock { · line 74

Quoted 12 lines

/*
* Following member retains the information to create a UDP header
* when the socket is uncorked.

include/linux/udp.h +4 -1
@@ -68,7 +68,10 @@ struct udp_sock {
 #define UDPLITE_SEND_CC  0x2           /* set via udplite setsockopt         */
 #define UDPLITE_RECV_CC  0x4           /* set via udplite setsocktopt        */
        __u8             pcflag;        /* marks socket as UDP-Lite if > 0    */
-       __u8             unused[3];
+       __u8             unused[1];
+       unsigned int     unexpected_gso;/* GSO types this socket can't accept,
+                                        * any of SKB_GSO_UDP_L4 or SKB_GSO_FRAGLIST
+                                        */

An extra unsigned int for this seems overkill.

Quoted 8 lines

Should be more clear after the next patch.

Using an explicit 'acceptable GSO types' field makes the patch 5/8 quite simple.

After this patch the 'udp_sock' struct size remains unchanged and even the number of 'udp_sock' cachelines touched for every packet is unchanged.

But there is opportunity cost, of course. Next time we need to add something to the struct, we will add a new cacheline.

A 32-bit field for just 2 bits, where 1 already exists does seem like overkill.

More importantly, I just think it's less obvious code than a pair of fields

accepts_udp_l4:1,
accepts_udp_fraglist:1,

Quoted 7 lines

Local sockets can only accept the first, as there does not exist an interface to pass along the multiple frag sizes that a frag_list based approach might have.

Sockets with encap_rcv != NULL may opt-in to being able to handle either.

I think explicit code will be more maintainable.

ok

Quoted 3 lines

At the cost of
perhaps two branches instead of one, admittedly. But that seems premature optimization.

well, if it don't hurt too much your eyes, something along the following could save udp_sock space and code branches:

    rejects_udp_l4_fraglist:2;

#define SKB_GSO_UDP_L4_SHIFT (NETIF_F_GSO_UDP_L4_BIT - NETIF_F_GSO_SHIFT)
static inline bool udp_unexpected_gso(struct sock *sk, struct sk_buff *skb)

 {
	BUILD_BUG_ON(1 << SKB_GSO_UDP_L4_SHIFT != SKB_GSO_UDP_L4);
	BUILD_BUG_ON(1 << (SKB_GSO_UDP_L4_SHIFT + 1) != SKB_GSO_FRAGLIST);
 	return skb_is_gso(skb) && skb_shinfo(skb)->gso_type &
		(udp_sk(sk)->rejects_udp_l4_fraglist << SKB_GSO_UDP_L4_SHIFT);

}

(not sure if /me runs/hides ;)

/P

maintainer
#26 lore raw

Re: [PATCH net-next 4/8] udp: never accept GSO_FRAGLIST packets

Quoted 79 lines · Paolo Abeni
Quoted 56 lines · Willem de Bruijn
Quoted 36 lines · Paolo Abeni
Quoted 20 lines · Willem de Bruijn
Quoted 19 lines · Paolo Abeni

Currently the UDP protocol delivers GSO_FRAGLIST packets to the sockets without the expected segmentation.

This change addresses the issue introducing and maintaining a per socket bitmask of GSO types requiring segmentation. Enabling GSO removes SKB_GSO_UDP_L4 from such mask, while GSO_FRAGLIST packets are never accepted

Note: this also updates the 'unused' field size to really fit the otherwise existing hole. It's size become incorrect after commit bec1f6f69736 ("udp: generate gso with UDP_SEGMENT").

Fixes 9fd1ff5d2ac7 ("udp: Support UDP fraglist GRO/GSO.")
---
 include/linux/udp.h | 10 ++++++----
 net/ipv4/udp.c      | 12 +++++++++++-
 2 files changed, 17 insertions(+), 5 deletions(-)
Quoted 14 lines

include/linux/udp.h struct udp_sock { · line 74

Quoted 12 lines

/*
* Following member retains the information to create a UDP header
* when the socket is uncorked.

include/linux/udp.h +4 -1
@@ -68,7 +68,10 @@ struct udp_sock {
 #define UDPLITE_SEND_CC  0x2           /* set via udplite setsockopt         */
 #define UDPLITE_RECV_CC  0x4           /* set via udplite setsocktopt        */
        __u8             pcflag;        /* marks socket as UDP-Lite if > 0    */
-       __u8             unused[3];
+       __u8             unused[1];
+       unsigned int     unexpected_gso;/* GSO types this socket can't accept,
+                                        * any of SKB_GSO_UDP_L4 or SKB_GSO_FRAGLIST
+                                        */

An extra unsigned int for this seems overkill.

Quoted 8 lines

Should be more clear after the next patch.

Using an explicit 'acceptable GSO types' field makes the patch 5/8 quite simple.

After this patch the 'udp_sock' struct size remains unchanged and even the number of 'udp_sock' cachelines touched for every packet is unchanged.

But there is opportunity cost, of course. Next time we need to add something to the struct, we will add a new cacheline.

A 32-bit field for just 2 bits, where 1 already exists does seem like overkill.

More importantly, I just think it's less obvious code than a pair of fields

accepts_udp_l4:1,
accepts_udp_fraglist:1,

Quoted 7 lines

Local sockets can only accept the first, as there does not exist an interface to pass along the multiple frag sizes that a frag_list based approach might have.

Sockets with encap_rcv != NULL may opt-in to being able to handle either.

I think explicit code will be more maintainable.

ok

Quoted 3 lines

At the cost of
perhaps two branches instead of one, admittedly. But that seems premature optimization.

well, if it don't hurt too much your eyes, something along the following could save udp_sock space and code branches:

    rejects_udp_l4_fraglist:2;

#define SKB_GSO_UDP_L4_SHIFT (NETIF_F_GSO_UDP_L4_BIT - NETIF_F_GSO_SHIFT)
static inline bool udp_unexpected_gso(struct sock *sk, struct sk_buff *skb)

Quoted 8 lines
 {
        BUILD_BUG_ON(1 << SKB_GSO_UDP_L4_SHIFT != SKB_GSO_UDP_L4);
        BUILD_BUG_ON(1 << (SKB_GSO_UDP_L4_SHIFT + 1) != SKB_GSO_FRAGLIST);
        return skb_is_gso(skb) && skb_shinfo(skb)->gso_type &
                (udp_sk(sk)->rejects_udp_l4_fraglist << SKB_GSO_UDP_L4_SHIFT);

}

(not sure if /me runs/hides ;)

:)

My opinion is just one, but I do find this a lot less readable and hence maintainable than

if (likely(!skb_is_gso(skb)))

     return true;
  if (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4 && !udp_sk(sk)->accept_udp_l4)
    return false;

if (skb_shinfo(skb)->gso_type & SKB_GSO_FRAGLIST &&
!udp_sk(sk)->accept_udp_fraglist)

    return false;

return true;

at no obvious benefit. The tunnel gso code is hard enough to fathom as it is.

Quoted 4 lines

/P

maintainer
#7 lore raw

Re: [PATCH net-next 1/8] udp: fixup csum for GSO receive slow path

In reply to Paolo Abeni
Quoted 39 lines
Quoted 8 lines

This is a UDP GSO packet egress packet that was further encapsulated with a GSO_UDP_TUNNEL on egress, then looped to the ingress path?

Then in the ingress path it has traversed the GRO layer.

Is this veth with XDP? That seems unlikely for GSO packets. But there aren't many paths that will loop a packet through napi_gro_receive or napi_gro_frags.

This patch addresses the following scenario:

sk ->vxlan -> veth -> (xdp in use, TSO disabled, GRO on) -> veth -> vxlan -> sk

What I meant here is that the issue is not visible with:

(baremetal, NETIF_F_GRO_UDP_FWD | NETIF_F_GRO_FRAGLIST enabled -> vxlan -> sk

Quoted 8 lines
Quoted 4 lines

with the appropriate features bit set, will validate the checksum for both the inner and the outer header - udp{4,6}_gro_receive will be traversed twice, the fist one for the outer header, the 2nd for the inner.

GRO will validate multiple levels of checksums with CHECKSUM_COMPLETE. It can only validate the outer checksum with CHECKSUM_UNNECESSARY, I believe?

I possibly miss some bits here ?!?

AFAICS:

udp4_gro_receive() -> skb_gro_checksum_validate_zero_check() -> __skb_gro_checksum_validate -> (if not already valid) __skb_gro_checksum_validate_complete() -> (if not CHECKSUM_COMPLETE) __skb_gro_checksum_complete()

the latter will validate the UDP checksum at the current nesting level (and set the csum-related bits in the GRO skb cb to the same status as CHECKSUM_COMPLETE)

Quoted 5 lines

When processing UDP over UDP tunnel packet, the gro call chain will be:

[l2/l3 GRO] -> udp4_gro_receive (validate outher header csum) -> udp_sk(sk)->gro_receive -> [other gro layers] -> udp4_gro_receive (validate inner header csum) -> ...

Agreed. But __skb_gro_checksum_validate on the first invocation will call skb_gro_incr_csum_unnecessary, so that on the second invocation csum_cnt == 0 and triggers a real checksum validation?

At least, that is my understanding. Intuitively, CHECKSUM_UNNECESSARY only validates the first checksum, so says nothing about the validity of any subsequent ones.

But it seems I'm mistaken?

__skb_gro_checksum_validate has an obvious exception for locally generated packets by excluding CHECKSUM_PARTIAL. Looped packets usually have CHECKSUM_PARTIAL set. Unfortunately, this is similar to the issue that I looked at earlier this year with looped UDP packets with MSG_MORE: those are also changed to CHECKSUM_NONE (and exposed a checksum bug: 52cbd23a119c).

Is there perhaps some other way that we can identify that these are local packets? They should trivially avoid all checksum checks.

Quoted 8 lines
Quoted 6 lines

As for looped packets with CHECKSUM_PARTIAL: we definitely have found bugs in that path before. I think it's fine to set csum_valid on any packets that can unambiguously be identified as such. Hence the detailed questions above on which exact packets this code is targeting, so that there are not accidental false positives that look the same but have a different ip_summed.

I see this change is controversial.

I have no concerns with the fix. It is just a very narrow path (veth + xdp - tso + gro ..), and to minimize risk I would try to avoid updating state of unrelated packets. That was my initial comment: I don't understand the need for the else clause.

Quoted 13 lines

Since the addressed scenario is
really a corner case, a simpler alternative would be replacing udp_post_segment_fix_csum with:

static inline void udp_post_segment_fix_cb(struct sk_buff *skb, int level)

{
        /* UDP-lite can't land here - no GRO */
        WARN_ON_ONCE(UDP_SKB_CB(skb)->partial_cov);

        /* UDP CB mirrors the GSO packet, we must re-init it */
        UDP_SKB_CB(skb)->cscov = skb->len;
}
Quoted 5 lines

the downside will be an additional, later, unneeded csum validation for the

sk ->vxlan -> veth -> (xdp in use, TSO disabled, GRO on) -> veth -> vxlan -> sk

scenario. WDYT?

No, let's definitely avoid an unneeded checksum verification.

maintainer
#16 lore raw

Re: !

In reply to Paolo Abeni
Quoted 31 lines · Paolo Abeni
Quoted 7 lines · Willem de Bruijn
Quoted 6 lines

I did not look at that before your suggestion. Thanks for pointing out.

I think the problem is specific to UDP: when processing the outer UDP header that is potentially eligible for both NETIF_F_GSO_UDP_L4 and gro_receive aggregation and that is the root cause of the problem addressed here.

Quoted 8 lines

Can you elaborate on the exact problem? The commit mentions "inner protocol corruption, as no overaly network parameters is taken in account at aggregation time."

My understanding is that these are udp gro aggregated GSO_UDP_L4 packets forwarded to a udp tunnel device. They are not encapsulated yet. Which overlay network parameters are not, but should have been, taken account at aggregation time?

The scenario is as follow:

* a NIC has NETIF_F_GRO_UDP_FWD or NETIF_F_GRO_FRAGLIST enabled
* an UDP tunnel is configured/enabled in the system
* the above NIC receives some UDP-tunneled packets, targeting the mentioned tunnel
* the packets go through gro_receive and they reache 'udp_gro_receive()' while processing the outer UDP header.

without this patch, udp_gro_receive_segment() will kick in and the outer UDP header will be aggregated according to SKB_GSO_FRAGLIST or SKB_GSO_UDP_L4, even if this is really e.g. a vxlan packet.

Quoted 5 lines

Different vxlan ids will be ignored/aggregated to the same GSO packet. Inner headers will be ignored, too, so that e.g. TCP over vxlan push packets will be held in the GRO engine till the next flush, etc.

Please let me know if the above is more clear.

Yes, thanks a lot! That's very concrete.

When processing the outer UDP tunnel header in the gro completion path, it is incorrectly identified as an inner UDP transport layer due to NAPI_GRO_CB(skb) identifying that such a layer is present (is_flist).

The issue is that the UDP GRO layer distinguishes between tunnel and transport layer too late, in udp_gro_complete, while an offending assumption of that UDP == transport layer was already made in the callers udp4_gro_complete and udp6_gro_complete.

author maintainer
#8 lore raw

Re: [PATCH net-next 1/8] udp: fixup csum for GSO receive slow path

In reply to Willem de Bruijn

Hello,

Quoted 49 lines · Willem de Bruijn
Quoted 39 lines
Quoted 8 lines

This is a UDP GSO packet egress packet that was further encapsulated with a GSO_UDP_TUNNEL on egress, then looped to the ingress path?

Then in the ingress path it has traversed the GRO layer.

Is this veth with XDP? That seems unlikely for GSO packets. But there aren't many paths that will loop a packet through napi_gro_receive or napi_gro_frags.

This patch addresses the following scenario:

sk ->vxlan -> veth -> (xdp in use, TSO disabled, GRO on) -> veth -> vxlan -> sk

What I meant here is that the issue is not visible with:

(baremetal, NETIF_F_GRO_UDP_FWD | NETIF_F_GRO_FRAGLIST enabled -> vxlan -> sk

Quoted 8 lines
Quoted 4 lines

with the appropriate features bit set, will validate the checksum for both the inner and the outer header - udp{4,6}_gro_receive will be traversed twice, the fist one for the outer header, the 2nd for the inner.

GRO will validate multiple levels of checksums with CHECKSUM_COMPLETE. It can only validate the outer checksum with CHECKSUM_UNNECESSARY, I believe?

I possibly miss some bits here ?!?

AFAICS:

udp4_gro_receive() -> skb_gro_checksum_validate_zero_check() -> __skb_gro_checksum_validate -> (if not already valid) __skb_gro_checksum_validate_complete() -> (if not CHECKSUM_COMPLETE) __skb_gro_checksum_complete()

the latter will validate the UDP checksum at the current nesting level (and set the csum-related bits in the GRO skb cb to the same status as CHECKSUM_COMPLETE)

Quoted 5 lines

When processing UDP over UDP tunnel packet, the gro call chain will be:

[l2/l3 GRO] -> udp4_gro_receive (validate outher header csum) -> udp_sk(sk)->gro_receive -> [other gro layers] -> udp4_gro_receive (validate inner header csum) -> ...

Agreed. But __skb_gro_checksum_validate on the first invocation will call skb_gro_incr_csum_unnecessary, so that on the second invocation csum_cnt == 0 and triggers a real checksum validation?

Quoted 5 lines

At least, that is my understanding. Intuitively, CHECKSUM_UNNECESSARY only validates the first checksum, so says nothing about the validity of any subsequent ones.

But it seems I'm mistaken?

AFAICS, it depends ;) From skbuff.h:

* skb->csum_level indicates the number of consecutive checksums found in
* the packet minus one that have been verified as CHECKSUM_UNNECESSARY.

if skb->csum_level > 0, the NIC validate additional headers. The intel ixgbe driver use that for vxlan RX csum offload. Such field translates into:

	NAPI_GRO_CB(skb)->csum_cnt

inside the GRO engine, and skb_gro_incr_csum_unnecessary takes care of the updating it after validation.

Quoted 17 lines

__skb_gro_checksum_validate has an obvious exception for locally generated packets by excluding CHECKSUM_PARTIAL. Looped packets usually have CHECKSUM_PARTIAL set. Unfortunately, this is similar to the issue that I looked at earlier this year with looped UDP packets with MSG_MORE: those are also changed to CHECKSUM_NONE (and exposed a checksum bug: 52cbd23a119c).

Is there perhaps some other way that we can identify that these are local packets? They should trivially avoid all checksum checks.

Quoted 7 lines
Quoted 6 lines

As for looped packets with CHECKSUM_PARTIAL: we definitely have found bugs in that path before. I think it's fine to set csum_valid on any packets that can unambiguously be identified as such. Hence the detailed questions above on which exact packets this code is targeting, so that there are not accidental false positives that look the same but have a different ip_summed.

Quoted 6 lines
Quoted 1 line

I see this change is controversial.

I have no concerns with the fix. It is just a very narrow path (veth + xdp - tso + gro ..), and to minimize risk I would try to avoid updating state of unrelated packets. That was my initial comment: I don't understand the need for the else clause.

The branch is there because I wrote this patch before the patches 5,6,7 later in this series. GSO UDP L4 over UDP tunnel packets were segmented at the UDP tunnel level, and that 'else' branch preserves the appropriate 'csum_level' value to avoid later (if/when the packet lands in a plain UDP socket) csum validation.

Quoted 13 lines
Quoted 13 lines

Since the addressed scenario is
really a corner case, a simpler alternative would be replacing udp_post_segment_fix_csum with:

static inline void udp_post_segment_fix_cb(struct sk_buff *skb, int level)

{
        /* UDP-lite can't land here - no GRO */
        WARN_ON_ONCE(UDP_SKB_CB(skb)->partial_cov);

        /* UDP CB mirrors the GSO packet, we must re-init it */
        UDP_SKB_CB(skb)->cscov = skb->len;
}
Quoted 7 lines
Quoted 5 lines

the downside will be an additional, later, unneeded csum validation for the

sk ->vxlan -> veth -> (xdp in use, TSO disabled, GRO on) -> veth -> vxlan -> sk

scenario. WDYT?

No, let's definitely avoid an unneeded checksum verification.

Ok.

My understanding is that the following should be better:

static inline void udp_post_segment_fix_csum(struct sk_buff *skb)

{
	/* UDP-lite can't land here - no GRO */
	WARN_ON_ONCE(UDP_SKB_CB(skb)->partial_cov);

	/* UDP packets generated with UDP_SEGMENT and traversing:
	 * UDP tunnel(xmit) -> veth (segmentation) -> veth (gro) -> UDP tunnel (rx)
	 * land here with CHECKSUM_NONE. Instead of adding another check
	 * in the tunnel fastpath, we can force valid csums here:
	 * packets are locally generated and the GRO engine already validated
	 * the csum.
	 * Additionally fixup the UDP CB
	 */
	UDP_SKB_CB(skb)->cscov = skb->len;
	if (skb->ip_summed == CHECKSUM_NONE && !skb->csum_valid)
		skb->csum_valid = 1;
}

I'll use the above in v2.

Thanks!

Paolo

author maintainer
#27 lore raw

Re: [PATCH net-next 4/8] udp: never accept GSO_FRAGLIST packets

In reply to Willem de Bruijn
Quoted 99 lines · Willem de Bruijn
Quoted 78 lines · Paolo Abeni
Quoted 56 lines · Willem de Bruijn
Quoted 36 lines · Paolo Abeni
Quoted 20 lines · Willem de Bruijn
Quoted 19 lines · Paolo Abeni

Currently the UDP protocol delivers GSO_FRAGLIST packets to the sockets without the expected segmentation.

This change addresses the issue introducing and maintaining a per socket bitmask of GSO types requiring segmentation. Enabling GSO removes SKB_GSO_UDP_L4 from such mask, while GSO_FRAGLIST packets are never accepted

Note: this also updates the 'unused' field size to really fit the otherwise existing hole. It's size become incorrect after commit bec1f6f69736 ("udp: generate gso with UDP_SEGMENT").

Fixes 9fd1ff5d2ac7 ("udp: Support UDP fraglist GRO/GSO.")
---
 include/linux/udp.h | 10 ++++++----
 net/ipv4/udp.c      | 12 +++++++++++-
 2 files changed, 17 insertions(+), 5 deletions(-)
Quoted 14 lines

include/linux/udp.h struct udp_sock { · line 74

Quoted 12 lines

/*
* Following member retains the information to create a UDP header
* when the socket is uncorked.

include/linux/udp.h +4 -1
@@ -68,7 +68,10 @@ struct udp_sock {
 #define UDPLITE_SEND_CC  0x2           /* set via udplite setsockopt         */
 #define UDPLITE_RECV_CC  0x4           /* set via udplite setsocktopt        */
        __u8             pcflag;        /* marks socket as UDP-Lite if > 0    */
-       __u8             unused[3];
+       __u8             unused[1];
+       unsigned int     unexpected_gso;/* GSO types this socket can't accept,
+                                        * any of SKB_GSO_UDP_L4 or SKB_GSO_FRAGLIST
+                                        */

An extra unsigned int for this seems overkill.

Quoted 8 lines

Should be more clear after the next patch.

Using an explicit 'acceptable GSO types' field makes the patch 5/8 quite simple.

After this patch the 'udp_sock' struct size remains unchanged and even the number of 'udp_sock' cachelines touched for every packet is unchanged.

But there is opportunity cost, of course. Next time we need to add something to the struct, we will add a new cacheline.

A 32-bit field for just 2 bits, where 1 already exists does seem like overkill.

More importantly, I just think it's less obvious code than a pair of fields

accepts_udp_l4:1,
accepts_udp_fraglist:1,

Quoted 7 lines

Local sockets can only accept the first, as there does not exist an interface to pass along the multiple frag sizes that a frag_list based approach might have.

Sockets with encap_rcv != NULL may opt-in to being able to handle either.

I think explicit code will be more maintainable.

ok

Quoted 3 lines

At the cost of
perhaps two branches instead of one, admittedly. But that seems premature optimization.

well, if it don't hurt too much your eyes, something along the following could save udp_sock space and code branches:

    rejects_udp_l4_fraglist:2;

#define SKB_GSO_UDP_L4_SHIFT (NETIF_F_GSO_UDP_L4_BIT - NETIF_F_GSO_SHIFT)
static inline bool udp_unexpected_gso(struct sock *sk, struct sk_buff *skb)

Quoted 8 lines
 {
        BUILD_BUG_ON(1 << SKB_GSO_UDP_L4_SHIFT != SKB_GSO_UDP_L4);
        BUILD_BUG_ON(1 << (SKB_GSO_UDP_L4_SHIFT + 1) != SKB_GSO_FRAGLIST);
        return skb_is_gso(skb) && skb_shinfo(skb)->gso_type &
                (udp_sk(sk)->rejects_udp_l4_fraglist << SKB_GSO_UDP_L4_SHIFT);

}

(not sure if /me runs/hides ;)

:)

My opinion is just one, but I do find this a lot less readable and hence maintainable than

if (likely(!skb_is_gso(skb)))

     return true;
  if (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4 && !udp_sk(sk)->accept_udp_l4)
    return false;
Quoted 7 lines

if (skb_shinfo(skb)->gso_type & SKB_GSO_FRAGLIST &&
!udp_sk(sk)->accept_udp_fraglist)

    return false;

return true;

at no obvious benefit. The tunnel gso code is hard enough to fathom as it is.

ok.

I'm only doubtful about the likely() annotation: systems with UDP tunnels likely expect receiving a majority of UDP-encaped traffic, which in turn will likely be GRO (e.g. TCP over UDP-tunnel).

In my next iteration I'll use the above, dropping the annotation.

Cheers,

Paolo

maintainer
#9 lore raw

Re: [PATCH net-next 1/8] udp: fixup csum for GSO receive slow path

In reply to Paolo Abeni
Quoted 64 lines · Paolo Abeni

Hello,

Quoted 49 lines · Willem de Bruijn
Quoted 39 lines
Quoted 8 lines

This is a UDP GSO packet egress packet that was further encapsulated with a GSO_UDP_TUNNEL on egress, then looped to the ingress path?

Then in the ingress path it has traversed the GRO layer.

Is this veth with XDP? That seems unlikely for GSO packets. But there aren't many paths that will loop a packet through napi_gro_receive or napi_gro_frags.

This patch addresses the following scenario:

sk ->vxlan -> veth -> (xdp in use, TSO disabled, GRO on) -> veth -> vxlan -> sk

What I meant here is that the issue is not visible with:

(baremetal, NETIF_F_GRO_UDP_FWD | NETIF_F_GRO_FRAGLIST enabled -> vxlan -> sk

Quoted 8 lines
Quoted 4 lines

with the appropriate features bit set, will validate the checksum for both the inner and the outer header - udp{4,6}_gro_receive will be traversed twice, the fist one for the outer header, the 2nd for the inner.

GRO will validate multiple levels of checksums with CHECKSUM_COMPLETE. It can only validate the outer checksum with CHECKSUM_UNNECESSARY, I believe?

I possibly miss some bits here ?!?

AFAICS:

udp4_gro_receive() -> skb_gro_checksum_validate_zero_check() -> __skb_gro_checksum_validate -> (if not already valid) __skb_gro_checksum_validate_complete() -> (if not CHECKSUM_COMPLETE) __skb_gro_checksum_complete()

the latter will validate the UDP checksum at the current nesting level (and set the csum-related bits in the GRO skb cb to the same status as CHECKSUM_COMPLETE)

Quoted 5 lines

When processing UDP over UDP tunnel packet, the gro call chain will be:

[l2/l3 GRO] -> udp4_gro_receive (validate outher header csum) -> udp_sk(sk)->gro_receive -> [other gro layers] -> udp4_gro_receive (validate inner header csum) -> ...

Agreed. But __skb_gro_checksum_validate on the first invocation will call skb_gro_incr_csum_unnecessary, so that on the second invocation csum_cnt == 0 and triggers a real checksum validation?

Quoted 5 lines

At least, that is my understanding. Intuitively, CHECKSUM_UNNECESSARY only validates the first checksum, so says nothing about the validity of any subsequent ones.

But it seems I'm mistaken?

AFAICS, it depends ;) From skbuff.h:

* skb->csum_level indicates the number of consecutive checksums found in
* the packet minus one that have been verified as CHECKSUM_UNNECESSARY.

Quoted 8 lines

if skb->csum_level > 0, the NIC validate additional headers. The intel ixgbe driver use that for vxlan RX csum offload. Such field translates into:

        NAPI_GRO_CB(skb)->csum_cnt

inside the GRO engine, and skb_gro_incr_csum_unnecessary takes care of the updating it after validation.

True. I glanced over those cases.

More importantly, where exactly do these looped packets get converted from CHECKSUM_PARTIAL to CHECKSUM_NONE before this patch?

Quoted 73 lines
Quoted 17 lines

__skb_gro_checksum_validate has an obvious exception for locally generated packets by excluding CHECKSUM_PARTIAL. Looped packets usually have CHECKSUM_PARTIAL set. Unfortunately, this is similar to the issue that I looked at earlier this year with looped UDP packets with MSG_MORE: those are also changed to CHECKSUM_NONE (and exposed a checksum bug: 52cbd23a119c).

Is there perhaps some other way that we can identify that these are local packets? They should trivially avoid all checksum checks.

Quoted 7 lines
Quoted 6 lines

As for looped packets with CHECKSUM_PARTIAL: we definitely have found bugs in that path before. I think it's fine to set csum_valid on any packets that can unambiguously be identified as such. Hence the detailed questions above on which exact packets this code is targeting, so that there are not accidental false positives that look the same but have a different ip_summed.

Quoted 6 lines
Quoted 1 line

I see this change is controversial.

I have no concerns with the fix. It is just a very narrow path (veth + xdp - tso + gro ..), and to minimize risk I would try to avoid updating state of unrelated packets. That was my initial comment: I don't understand the need for the else clause.

The branch is there because I wrote this patch before the patches 5,6,7 later in this series. GSO UDP L4 over UDP tunnel packets were segmented at the UDP tunnel level, and that 'else' branch preserves the appropriate 'csum_level' value to avoid later (if/when the packet lands in a plain UDP socket) csum validation.

Quoted 13 lines
Quoted 13 lines

Since the addressed scenario is
really a corner case, a simpler alternative would be replacing udp_post_segment_fix_csum with:

static inline void udp_post_segment_fix_cb(struct sk_buff *skb, int level)

{
        /* UDP-lite can't land here - no GRO */
        WARN_ON_ONCE(UDP_SKB_CB(skb)->partial_cov);

        /* UDP CB mirrors the GSO packet, we must re-init it */
        UDP_SKB_CB(skb)->cscov = skb->len;
}
Quoted 7 lines
Quoted 5 lines

the downside will be an additional, later, unneeded csum validation for the

sk ->vxlan -> veth -> (xdp in use, TSO disabled, GRO on) -> veth -> vxlan -> sk

scenario. WDYT?

No, let's definitely avoid an unneeded checksum verification.

Ok.

My understanding is that the following should be better:

static inline void udp_post_segment_fix_csum(struct sk_buff *skb)

{
        /* UDP-lite can't land here - no GRO */
        WARN_ON_ONCE(UDP_SKB_CB(skb)->partial_cov);

        /* UDP packets generated with UDP_SEGMENT and traversing:
         * UDP tunnel(xmit) -> veth (segmentation) -> veth (gro) -> UDP tunnel (rx)
         * land here with CHECKSUM_NONE. Instead of adding another check
         * in the tunnel fastpath, we can force valid csums here:
         * packets are locally generated and the GRO engine already validated
         * the csum.
         * Additionally fixup the UDP CB
         */
        UDP_SKB_CB(skb)->cscov = skb->len;
        if (skb->ip_summed == CHECKSUM_NONE && !skb->csum_valid)
                skb->csum_valid = 1;
}
Quoted 1 line

I'll use the above in v2.

Do I understand correctly that this avoids matching tunneled packets that arrive from the network with rx checksumming disabled, because __skb_gro_checksum_complete will have been called on the outer packet and have set skb->csum_valid?

Yes, this just (1) identifying the packet as being of local source and then (2) setting csum_valid sounds great to me, thanks.

author maintainer
#10 lore raw

Re: [PATCH net-next 1/8] udp: fixup csum for GSO receive slow path

Quoted 12 lines · Willem de Bruijn
Quoted 11 lines · Paolo Abeni

AFAICS, it depends ;) From skbuff.h:

* skb->csum_level indicates the number of consecutive checksums found in
* the packet minus one that have been verified as CHECKSUM_UNNECESSARY.

if skb->csum_level > 0, the NIC validate additional headers. The intel ixgbe driver use that for vxlan RX csum offload. Such field translates into:

        NAPI_GRO_CB(skb)->csum_cnt
Quoted 7 lines
Quoted 2 lines

inside the GRO engine, and skb_gro_incr_csum_unnecessary takes care of the updating it after validation.

True. I glanced over those cases.

More importantly, where exactly do these looped packets get converted from CHECKSUM_PARTIAL to CHECKSUM_NONE before this patch?

Very good question! It took a bit finding the exact place.

int __iptunnel_pull_header(struct sk_buff *skb, int hdr_len,
__be16 inner_proto, bool raw_proto, bool xnet)

{
	if (unlikely(!pskb_may_pull(skb, hdr_len)))
		return -ENOMEM;

	skb_pull_rcsum(skb, hdr_len);
        // here ^^^ via skb_pull_rcsum -> skb_postpull_rcsum() -> __skb_postpull_rcsum()

well, this is actually with _this_ patch applied: it does not change the place where the ip_summed is set.

Quoted 20 lines
Quoted 20 lines

My understanding is that the following should be better:

static inline void udp_post_segment_fix_csum(struct sk_buff *skb)

{
        /* UDP-lite can't land here - no GRO */
        WARN_ON_ONCE(UDP_SKB_CB(skb)->partial_cov);

        /* UDP packets generated with UDP_SEGMENT and traversing:
         * UDP tunnel(xmit) -> veth (segmentation) -> veth (gro) -> UDP tunnel (rx)
         * land here with CHECKSUM_NONE. Instead of adding another check
         * in the tunnel fastpath, we can force valid csums here:
         * packets are locally generated and the GRO engine already validated
         * the csum.
         * Additionally fixup the UDP CB
         */
        UDP_SKB_CB(skb)->cscov = skb->len;
        if (skb->ip_summed == CHECKSUM_NONE && !skb->csum_valid)
                skb->csum_valid = 1;
}
Quoted 6 lines
Quoted 1 line

I'll use the above in v2.

Do I understand correctly that this avoids matching tunneled packets that arrive from the network with rx checksumming disabled, because __skb_gro_checksum_complete will have been called on the outer packet and have set skb->csum_valid?

Exactly. I did the test, and perf probes showed that.

Quoted 2 lines

Yes, this just (1) identifying the packet as being of local source and then (2) setting csum_valid sounds great to me, thanks.

Will try to submit v2 soon, after some more testing.

Thanks for all the feedback!

Paolo