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