udp: test the partial-sendmmsg rewind

The rewind (resume after the kernel accepts fewer entries than
submitted) was the hairiest untested logic in the write path; a bug
there silently duplicates or loses packets under backpressure. Give
batchWriter an injectable sendFn and drive WriteBatch through scripted
partial-acceptance sequences over a mixed GSO-run/plain batch, decoding
what "reached the wire" straight from the prepared iovecs rather than
the entryEnd bookkeeping under test. Also pins the zero-progress abort
and the EIO runtime GSO-disable replay.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
JackDoan
2026-07-27 15:59:49 -05:00
parent 3fe2cb970e
commit 66cb98e13a
2 changed files with 160 additions and 1 deletions
+7 -1
View File
@@ -55,10 +55,16 @@ type batchWriter struct {
// entryEnd[e] is the bufs index *after* the last packet packed into
// mmsghdr entry e. Used to rewind `i` on partial sendmmsg success.
entryEnd []int
// sendFn issues the sendmmsg for the first n prepared entries. Points
// at the real syscall in production; tests inject partial-success and
// error scripts to exercise the rewind logic without a socket.
sendFn func(n int) (int, error)
}
func newBatchWriter(fd int, isV4 bool, l *slog.Logger) *batchWriter {
w := &batchWriter{fd: fd, isV4: isV4, l: l}
w.sendFn = w.sendmmsg
w.prepareWriteMessages(MaxWriteBatch)
w.prepareGSO()
return w
@@ -294,7 +300,7 @@ sendChunks:
return written, fmt.Errorf("sendmmsg: no progress")
}
sent, serr := w.sendmmsg(entry)
sent, serr := w.sendFn(entry)
if serr != nil && sent <= 0 {
// sent<=0 means message 0 itself failed. If that entry was a GSO
// superpacket and the errno is the kernel's "device can't do this"