Frag-list GRO validates the UDP checksum of every datagram before linking
it into an aggregate. Completion records that verdict by turning a
CHECKSUM_NONE head into CHECKSUM_UNNECESSARY, but leaves a
CHECKSUM_COMPLETE head untouched. Its checksum then covers only the first
datagram, while uh->len spans the whole aggregate.

Any later validation of such an aggregate fails. udp4_csum_init and
udp6_csum_init compute the complete checksum of an aggregate no longer
than CHECKSUM_BREAK and drop it as a checksum error. For a larger
aggregate, conntrack's nf_checksum in PRE_ROUTING falls back to a
software checksum of the whole skb, which fails, marks the packet invalid
and sets csum_complete_sw, so UDP drops the aggregate too.

Record the verdict for a CHECKSUM_COMPLETE head as for a device which
validated the checksums itself, so that completion yields the same
CHECKSUM_UNNECESSARY state in every receive checksum mode.

With rx-gro-list enabled on an mlx5 port, which reports CHECKSUM_COMPLETE,
a 100 Mbit/s flow of 30-byte UDP datagrams to a local socket raised
UdpInCsumErrors by 3880 in five seconds and lost exactly twice as many
datagrams, one pair per dropped aggregate. With rx-gro-list disabled, the
counter stayed at zero.

TCP frag-list completion follows the same pattern and is not changed here.

Sashiko reported the issue while reviewing an ovpn series which uses
frag-list GRO.

Fixes: 9fd1ff5d2ac7 ("udp: Support UDP fraglist GRO/GSO.")
Signed-off-by: Ralf Lici <[email protected]>
---
New in v2.

 net/ipv4/udp_offload.c | 8 ++++++++
 net/ipv6/udp_offload.c | 8 ++++++++
 2 files changed, 16 insertions(+)

diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c
index cf07c3c6611a..572a65cd8977 100644
--- a/net/ipv4/udp_offload.c
+++ b/net/ipv4/udp_offload.c
@@ -965,6 +965,14 @@ INDIRECT_CALLABLE_SCOPE int udp4_gro_complete(struct 
sk_buff *skb, int nhoff)
                skb_shinfo(skb)->gso_type |= (SKB_GSO_FRAGLIST|SKB_GSO_UDP_L4);
                skb_shinfo(skb)->gso_segs = NAPI_GRO_CB(skb)->count;
 
+               /* GRO validated every datagram, but a CHECKSUM_COMPLETE value
+                * covers only the first one: keep the verdict instead, as for
+                * a device which validated the checksums itself
+                */
+               if (skb->ip_summed == CHECKSUM_COMPLETE) {
+                       skb->ip_summed = CHECKSUM_UNNECESSARY;
+                       skb->csum_level = 0;
+               }
                __skb_incr_checksum_unnecessary(skb);
 
                return 0;
diff --git a/net/ipv6/udp_offload.c b/net/ipv6/udp_offload.c
index 7370bcb80332..0b7ced2b5394 100644
--- a/net/ipv6/udp_offload.c
+++ b/net/ipv6/udp_offload.c
@@ -176,6 +176,14 @@ int udp6_gro_complete(struct sk_buff *skb, int nhoff)
                skb_shinfo(skb)->gso_type |= (SKB_GSO_FRAGLIST|SKB_GSO_UDP_L4);
                skb_shinfo(skb)->gso_segs = NAPI_GRO_CB(skb)->count;
 
+               /* GRO validated every datagram, but a CHECKSUM_COMPLETE value
+                * covers only the first one: keep the verdict instead, as for
+                * a device which validated the checksums itself
+                */
+               if (skb->ip_summed == CHECKSUM_COMPLETE) {
+                       skb->ip_summed = CHECKSUM_UNNECESSARY;
+                       skb->csum_level = 0;
+               }
                __skb_incr_checksum_unnecessary(skb);
 
                return 0;
-- 
2.56.0



_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

Reply via email to