overlay/tio: derive WriteGSO geometry from non-empty fragments

GSOSize came from len(pays[0]) while the iovec build skips empty
fragments, so a leading empty fragment emitted a TSO/USO header with
gso_size == 0 -- virtio_net_hdr_to_skb rejects that with EINVAL and the
whole superpacket is lost. Compute gso_size from the first non-empty
fragment and use the non-empty count to decide superpacket vs plain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
JackDoan
2026-07-27 15:38:21 -05:00
parent 5c0a5ee4be
commit 6c0305c7ae
2 changed files with 59 additions and 2 deletions
+16 -2
View File
@@ -334,14 +334,28 @@ func (r *Offload) WriteGSO(hdr []byte, transportHdr []byte, pays [][]byte, proto
default:
csumOff = 16
}
// GSO geometry comes from the non-empty fragments only: the iovec loop
// below skips empties, so gso_size must never be derived from one. A
// leading empty fragment would otherwise stamp a superpacket header
// with gso_size == 0, which the kernel rejects with EINVAL.
segSize, segCount := 0, 0
for _, p := range pays {
if len(p) == 0 {
continue
}
if segCount == 0 {
segSize = len(p)
}
segCount++
}
vhdr := virtio.Hdr{
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
HdrLen: uint16(len(hdr) + len(transportHdr)),
GSOSize: uint16(len(pays[0])),
GSOSize: uint16(segSize),
CsumStart: uint16(len(hdr)),
CsumOffset: csumOff,
}
if len(pays) > 1 {
if segCount > 1 {
ipVer := hdr[0] >> 4
switch {
case proto == GSOProtoUDP && (ipVer == 4 || ipVer == 6):