When MASQUERADE --random-fully rewrites the outer UDP source port of a VXLAN-encapsulated packet whose inner header has CHECKSUM_PARTIAL, the outer UDP checksum is not updated. inet_proto_csum_replace2() is a no-op for CHECKSUM_PARTIAL skb's, so the stale checksum causes remote VTEP to drop the first packet (UdpInCsumErrors) until a ~1s SYN retransmission. Add an explicit csum_replace2() call to update the outer UDP checksum when the skb is encapsulated, has CHECKSUM_PARTIAL, and is not GSO (hardware offload handles the non-encapsulated case). Test results (10 connections, single netns vxlan + veth environment): Before fix: ~1080ms avg first-connection latency, 10 SYN retransmits, UdpInCsumErrors incremented per connection After fix: ~29ms avg first-connection latency, 0 SYN retransmits, UdpInCsumErrors unchanged Fixes: faec18dbb0405 ("netfilter: nat: remove l4proto->manip_pkt") Cc: stable@vger.kernel.org Signed-off-by: lvjunyu --- net/netfilter/nf_nat_proto.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/net/netfilter/nf_nat_proto.c b/net/netfilter/nf_nat_proto.c index 64b9bac228e..35cc6e10b5b 100644 --- a/net/netfilter/nf_nat_proto.c +++ b/net/netfilter/nf_nat_proto.c @@ -57,6 +57,20 @@ __udp_manip_pkt(struct sk_buff *skb, nf_csum_update(skb, iphdroff, &hdr->check, tuple, maniptype); inet_proto_csum_replace2(&hdr->check, skb, *portptr, newport, false); + /* For CHECKSUM_PARTIAL encapsulated non-GSO packets, + * inet_proto_csum_replace2() above is a no-op because the + * port field is data, not pseudo-header. The outer uh->check + * was computed by udp_set_csum() with a temporary sport, and + * the subsequent NAT sport rewrite leaves it stale. On devices + * without hw_enc_features, the software fallback + * skb_checksum_help() honors csum_start which points at the + * inner transport header and only recomputes the inner + * checksum. The stale outer checksum goes out and the + * receiver drops the packet with UdpInCsumErrors. + */ + if (skb->ip_summed == CHECKSUM_PARTIAL && + skb->encapsulation && !skb_is_gso(skb)) + csum_replace2(&hdr->check, *portptr, newport); if (!hdr->check) hdr->check = CSUM_MANGLED_0; } -- 2.43.0