mirror of
https://github.com/slackhq/nebula.git
synced 2026-08-16 17:37:04 +02:00
udp: extract and test the GRO RX splitting
This commit is contained in:
+16
-12
@@ -332,24 +332,28 @@ func (u *StdConn) ListenOut(r EncReader, flush func()) error {
|
|||||||
segSize, outerECN = parseRecvCmsg(&msgs[i].Hdr, u.groSupported, u.ecnRecvSupported)
|
segSize, outerECN = parseRecvCmsg(&msgs[i].Hdr, u.groSupported, u.ecnRecvSupported)
|
||||||
}
|
}
|
||||||
|
|
||||||
if segSize <= 0 || segSize >= len(payload) {
|
deliverSegments(r, from, payload, segSize, RxMeta{OuterECN: outerECN})
|
||||||
r(from, payload, RxMeta{OuterECN: outerECN})
|
|
||||||
} else {
|
|
||||||
for off := 0; off < len(payload); off += segSize {
|
|
||||||
end := off + segSize
|
|
||||||
if end > len(payload) {
|
|
||||||
end = len(payload)
|
|
||||||
}
|
|
||||||
seg := payload[off:end]
|
|
||||||
r(from, seg, RxMeta{OuterECN: outerECN})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
flush()
|
flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deliverSegments hands a received superdatagram to r, splitting it back into pre-coalesce packets
|
||||||
|
func deliverSegments(r EncReader, from netip.AddrPort, payload []byte, segSize int, meta RxMeta) {
|
||||||
|
if segSize <= 0 || segSize >= len(payload) { //avoid bogus values
|
||||||
|
r(from, payload, meta)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for off := 0; off < len(payload); off += segSize {
|
||||||
|
end := off + segSize
|
||||||
|
if end > len(payload) {
|
||||||
|
end = len(payload)
|
||||||
|
}
|
||||||
|
r(from, payload[off:end], meta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// parseRecvCmsg walks the per-slot ancillary buffer once and extracts up to
|
// parseRecvCmsg walks the per-slot ancillary buffer once and extracts up to
|
||||||
// two values of interest in a single pass: the UDP_GRO gso_size (when
|
// two values of interest in a single pass: the UDP_GRO gso_size (when
|
||||||
// wantGRO is true) and the outer IP-level ECN codepoint stamped on the
|
// wantGRO is true) and the outer IP-level ECN codepoint stamped on the
|
||||||
|
|||||||
@@ -324,3 +324,76 @@ func TestParseRecvCmsgCorruptLenNoPanic(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestDeliverSegments pins the GRO RX splitting: a kernel-coalesced buffer
|
||||||
|
// must come back out as the exact pre-coalesce packets -- every boundary
|
||||||
|
// error here shreds encrypted packets and every decrypt downstream fails.
|
||||||
|
func TestDeliverSegments(t *testing.T) {
|
||||||
|
from := netip.MustParseAddrPort("192.0.2.1:4242")
|
||||||
|
pay := func(n int) []byte {
|
||||||
|
b := make([]byte, n)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = byte(i)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
payload []byte
|
||||||
|
segSize int
|
||||||
|
wantLens []int
|
||||||
|
}{
|
||||||
|
{"no-gro", pay(1400), 0, []int{1400}},
|
||||||
|
{"negative-segsize", pay(1400), -5, []int{1400}},
|
||||||
|
{"segsize-equals-payload", pay(1400), 1400, []int{1400}},
|
||||||
|
{"segsize-past-payload", pay(1400), 2000, []int{1400}},
|
||||||
|
{"even-split", pay(4200), 1400, []int{1400, 1400, 1400}},
|
||||||
|
{"short-tail", pay(3000), 1400, []int{1400, 1400, 200}},
|
||||||
|
{"single-byte-tail", pay(2801), 1400, []int{1400, 1400, 1}},
|
||||||
|
{"segsize-one", pay(3), 1, []int{1, 1, 1}},
|
||||||
|
{"empty-payload", pay(0), 1400, []int{0}},
|
||||||
|
{"max-coalesce", pay(65500), 1372, nil}, // lens derived below
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
wantLens := c.wantLens
|
||||||
|
if wantLens == nil {
|
||||||
|
for rem := len(c.payload); rem > 0; rem -= c.segSize {
|
||||||
|
wantLens = append(wantLens, min(c.segSize, rem))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var got [][]byte
|
||||||
|
meta := RxMeta{OuterECN: 0x2}
|
||||||
|
deliverSegments(func(a netip.AddrPort, seg []byte, m RxMeta) {
|
||||||
|
if a != from {
|
||||||
|
t.Errorf("from = %v, want %v", a, from)
|
||||||
|
}
|
||||||
|
if m != meta {
|
||||||
|
t.Errorf("meta = %+v, want %+v", m, meta)
|
||||||
|
}
|
||||||
|
got = append(got, seg)
|
||||||
|
}, from, c.payload, c.segSize, meta)
|
||||||
|
|
||||||
|
if len(got) != len(wantLens) {
|
||||||
|
t.Fatalf("delivered %d segments, want %d", len(got), len(wantLens))
|
||||||
|
}
|
||||||
|
// Segments must tile the payload in order with no gap, overlap,
|
||||||
|
// or copy: each must alias the payload at the right offset.
|
||||||
|
off := 0
|
||||||
|
for i, seg := range got {
|
||||||
|
if len(seg) != wantLens[i] {
|
||||||
|
t.Fatalf("segment %d len=%d want %d", i, len(seg), wantLens[i])
|
||||||
|
}
|
||||||
|
if len(seg) > 0 && &seg[0] != &c.payload[off] {
|
||||||
|
t.Errorf("segment %d does not alias payload at offset %d", i, off)
|
||||||
|
}
|
||||||
|
off += len(seg)
|
||||||
|
}
|
||||||
|
if off != len(c.payload) {
|
||||||
|
t.Errorf("segments cover %d bytes, payload has %d", off, len(c.payload))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user