mirror of
https://github.com/slackhq/nebula.git
synced 2026-08-16 16:57:02 +02:00
fable fixes
This commit is contained in:
@@ -1437,3 +1437,179 @@ func TestCoalescerAtomicRandomIDsCoalesce(t *testing.T) {
|
||||
t.Fatalf("DF=1 chain with arbitrary IDs must coalesce: gso=%d", len(w.gsoWrites))
|
||||
}
|
||||
}
|
||||
|
||||
// buildTCPv4TS is buildTCPv4 with a TCP timestamp option in the standard
|
||||
// Linux layout (NOP,NOP,TS — a 32-byte TCP header).
|
||||
func buildTCPv4TS(seq uint32, flags byte, tsVal, tsEcr uint32, payload []byte) []byte {
|
||||
const ipHdrLen = 20
|
||||
const tcpHdrLen = 32
|
||||
total := ipHdrLen + tcpHdrLen + len(payload)
|
||||
pkt := make([]byte, total)
|
||||
|
||||
pkt[0] = 0x45
|
||||
pkt[1] = 0x00
|
||||
binary.BigEndian.PutUint16(pkt[2:4], uint16(total))
|
||||
binary.BigEndian.PutUint16(pkt[4:6], 0)
|
||||
binary.BigEndian.PutUint16(pkt[6:8], 0x4000)
|
||||
pkt[8] = 64
|
||||
pkt[9] = ipProtoTCP
|
||||
copy(pkt[12:16], []byte{10, 0, 0, 1})
|
||||
copy(pkt[16:20], []byte{10, 0, 0, 2})
|
||||
|
||||
binary.BigEndian.PutUint16(pkt[20:22], 1000)
|
||||
binary.BigEndian.PutUint16(pkt[22:24], 2000)
|
||||
binary.BigEndian.PutUint32(pkt[24:28], seq)
|
||||
binary.BigEndian.PutUint32(pkt[28:32], 12345)
|
||||
pkt[32] = 0x80 // doff=8: 32-byte TCP header
|
||||
pkt[33] = flags
|
||||
binary.BigEndian.PutUint16(pkt[34:36], 0xffff)
|
||||
pkt[40] = 0x01 // NOP
|
||||
pkt[41] = 0x01 // NOP
|
||||
pkt[42] = 0x08 // TS kind
|
||||
pkt[43] = 10 // TS length
|
||||
binary.BigEndian.PutUint32(pkt[44:48], tsVal)
|
||||
binary.BigEndian.PutUint32(pkt[48:52], tsEcr)
|
||||
|
||||
copy(pkt[52:], payload)
|
||||
return pkt
|
||||
}
|
||||
|
||||
func TestParseTCPOptions(t *testing.T) {
|
||||
ts := func(val, ecr uint32) []byte {
|
||||
b := make([]byte, 10)
|
||||
b[0], b[1] = 0x08, 10
|
||||
binary.BigEndian.PutUint32(b[2:6], val)
|
||||
binary.BigEndian.PutUint32(b[6:10], ecr)
|
||||
return b
|
||||
}
|
||||
cases := []struct {
|
||||
name string
|
||||
opts []byte
|
||||
wantVal uint32
|
||||
wantEcr uint32
|
||||
wantOK bool
|
||||
}{
|
||||
{"empty", nil, 0, 0, false},
|
||||
{"bare TS filling the block exactly", ts(100, 200), 100, 200, true},
|
||||
{"standard linux NOP,NOP,TS", append([]byte{1, 1}, ts(7, 9)...), 7, 9, true},
|
||||
{"unknown option then TS", append([]byte{254, 4, 0, 0}, ts(3, 4)...), 3, 4, true},
|
||||
{"EOL terminates before garbage", append([]byte{0, 0}, ts(1, 2)...), 0, 0, false},
|
||||
{"zero-length option must not hang", []byte{254, 0, 8, 10, 0, 0, 0, 1, 0, 0, 0, 2}, 0, 0, false},
|
||||
{"TS with wrong length", []byte{8, 4, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}, 0, 0, false},
|
||||
{"truncated TS", append([]byte{1, 1, 1}, ts(5, 6)[:9]...), 0, 0, false},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
val, ecr, ok := parseTCPOptions(tc.opts)
|
||||
if val != tc.wantVal || ecr != tc.wantEcr || ok != tc.wantOK {
|
||||
t.Fatalf("parseTCPOptions(%v) = (%d, %d, %v), want (%d, %d, %v)",
|
||||
tc.opts, val, ecr, ok, tc.wantVal, tc.wantEcr, tc.wantOK)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCompareCoalesceSlotsAntisymmetric pins the comparator contract for the
|
||||
// retransmit shape: a lower seq with a newer TSval (retransmit) versus a
|
||||
// higher seq with an older TSval (delayed original). The TSval must win in
|
||||
// BOTH directions — an asymmetric comparator gives SortStableFunc an
|
||||
// inconsistent order and unspecified output.
|
||||
func TestCompareCoalesceSlotsAntisymmetric(t *testing.T) {
|
||||
mk := func(seq, tsVal uint32, hasTS bool) *coalesceSlot {
|
||||
return &coalesceSlot{nextSeq: seq, tsVal: tsVal, hasTS: hasTS}
|
||||
}
|
||||
original := mk(5000, 100, true) // sent first, delayed in flight
|
||||
retransmit := mk(1000, 105, true) // sent later, lower seq
|
||||
|
||||
if got := compareCoalesceSlots(original, retransmit); got != -1 {
|
||||
t.Fatalf("compare(original, retransmit) = %d, want -1 (older TSval first)", got)
|
||||
}
|
||||
if got := compareCoalesceSlots(retransmit, original); got != 1 {
|
||||
t.Fatalf("compare(retransmit, original) = %d, want 1", got)
|
||||
}
|
||||
|
||||
// Equal TSvals (a burst within one tick) fall back to seq order,
|
||||
// still antisymmetrically.
|
||||
a, b := mk(1000, 50, true), mk(2000, 50, true)
|
||||
if compareCoalesceSlots(a, b) != -1 || compareCoalesceSlots(b, a) != 1 {
|
||||
t.Fatal("equal-TSval slots must order by seq in both directions")
|
||||
}
|
||||
|
||||
// Timestamp-less flows keep pure seq order.
|
||||
c, d := mk(2000, 0, false), mk(1000, 99, true)
|
||||
if compareCoalesceSlots(c, d) != 1 || compareCoalesceSlots(d, c) != -1 {
|
||||
t.Fatal("mixed/absent timestamps must fall back to seq in both directions")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCoalescerRetransmitEmitsAfterDelayedOriginal: a retransmit (lower seq,
|
||||
// newer TSval) and a delayed original (higher seq, older TSval) land in one
|
||||
// flush window. Seq-only sorting would emit the retransmit first; the
|
||||
// receiver would advance ts_recent past the original's TSval and PAWS would
|
||||
// drop the original. TSval-first ordering must emit the original first.
|
||||
func TestCoalescerRetransmitEmitsAfterDelayedOriginal(t *testing.T) {
|
||||
w := &fakeTunWriter{gsoEnabled: true}
|
||||
c := newTestTCPCoalescer(t, w)
|
||||
pay := make([]byte, 100)
|
||||
|
||||
original := buildTCPv4TS(5000, tcpAck, 100, 1, pay)
|
||||
retransmit := buildTCPv4TS(1000, tcpAck, 105, 1, pay)
|
||||
|
||||
if err := c.Commit(original); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := c.Commit(retransmit); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := c.Flush(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(w.writes) != 2 {
|
||||
t.Fatalf("want 2 plain writes (non-contiguous single-segment slots), got %d writes, %d gso", len(w.writes), len(w.gsoWrites))
|
||||
}
|
||||
firstSeq := binary.BigEndian.Uint32(w.writes[0][24:28])
|
||||
secondSeq := binary.BigEndian.Uint32(w.writes[1][24:28])
|
||||
if firstSeq != 5000 || secondSeq != 1000 {
|
||||
t.Fatalf("emission order (%d, %d), want (5000, 1000): retransmit must not overtake the older-TSval original", firstSeq, secondSeq)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCoalescerACKDoesNotSplitSortRun: an interleaved pure ACK must not stop
|
||||
// wire-reordered same-flow data on either side of it from sorting adjacent
|
||||
// and merging — the contract explicitly allows data to overtake a bare ACK.
|
||||
// Arrival is D2, ACK, D1; the two data slots must still merge into one
|
||||
// superpacket, with the ACK emitted after (its seq is the peer's snd_nxt,
|
||||
// which orders it behind the data it followed).
|
||||
func TestCoalescerACKDoesNotSplitSortRun(t *testing.T) {
|
||||
w := &fakeTunWriter{gsoEnabled: true}
|
||||
c := newTestTCPCoalescer(t, w)
|
||||
pay := make([]byte, 1200)
|
||||
|
||||
d2 := buildTCPv4(2200, tcpAck, pay)
|
||||
ack := buildTCPv4(3400, tcpAck, nil)
|
||||
d1 := buildTCPv4(1000, tcpAck, pay)
|
||||
|
||||
for _, pkt := range [][]byte{d2, ack, d1} {
|
||||
if err := c.Commit(pkt); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := c.Flush(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(w.gsoWrites) != 1 {
|
||||
t.Fatalf("want the two data slots merged into 1 gso write across the ACK, got %d gso + %d plain", len(w.gsoWrites), len(w.writes))
|
||||
}
|
||||
if got := w.gsoWrites[0].payLen(); got != 2400 {
|
||||
t.Fatalf("merged payload = %d, want 2400", got)
|
||||
}
|
||||
if len(w.writes) != 1 {
|
||||
t.Fatalf("want the ACK as 1 plain write, got %d", len(w.writes))
|
||||
}
|
||||
if got := binary.BigEndian.Uint32(w.writes[0][24:28]); got != 3400 {
|
||||
t.Fatalf("plain write seq = %d, want the ACK (3400)", got)
|
||||
}
|
||||
if len(w.order) != 2 || w.order[0] != "gso" || w.order[1] != "write" {
|
||||
t.Fatalf("emission order = %v, want [gso write]", w.order)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user