diff --git a/overlay/batch/tcp_coalesce.go b/overlay/batch/tcp_coalesce.go index 604a802e..632d34b3 100644 --- a/overlay/batch/tcp_coalesce.go +++ b/overlay/batch/tcp_coalesce.go @@ -344,6 +344,11 @@ func (c *TCPCoalescer) appendPayload(s *coalesceSlot, pkt []byte, info parsedTCP s.numSeg++ s.totalPay += info.payLen s.nextSeq = info.seq + uint32(info.payLen) + if info.flags&0x08 != 0 { + // Propagate PSH into the seed header so kernel TSO sets it on the + // last segment. Without this the sender's push signal is dropped. + s.hdrBuf[s.ipHdrLen+13] |= 0x08 + } if info.payLen < s.gsoSize || info.flags&0x08 != 0 { s.psh = true } diff --git a/overlay/batch/tcp_coalesce_test.go b/overlay/batch/tcp_coalesce_test.go index 8bf47900..188b55a6 100644 --- a/overlay/batch/tcp_coalesce_test.go +++ b/overlay/batch/tcp_coalesce_test.go @@ -343,6 +343,39 @@ func TestCoalescerPSHFinalizesChain(t *testing.T) { } } +// TestCoalescerPropagatesPSHFromAppended ensures that when an appended +// segment carries PSH (or is short, sealing the chain), the PSH bit ends +// up in the emitted superpacket's TCP flags. The kernel TSO path keeps +// PSH only on the last segment iff the input header has it set; if the +// coalescer drops it the sender's push signal never reaches the receiver. +func TestCoalescerPropagatesPSHFromAppended(t *testing.T) { + w := &fakeTunWriter{gsoEnabled: true} + c := NewTCPCoalescer(w) + pay := make([]byte, 1200) + // Seed has no PSH; second segment carries PSH and seals the chain. + if err := c.Commit(buildTCPv4(1000, tcpAck, pay)); err != nil { + t.Fatal(err) + } + if err := c.Commit(buildTCPv4(2200, tcpAckPsh, pay)); err != nil { + t.Fatal(err) + } + if err := c.Flush(0); err != nil { + t.Fatal(err) + } + if len(w.gsoWrites) != 1 { + t.Fatalf("want 1 gso write got %d", len(w.gsoWrites)) + } + g := w.gsoWrites[0] + const ipHdrLen = 20 + flags := g.hdr[ipHdrLen+13] + if flags&tcpPsh == 0 { + t.Fatalf("PSH lost from coalesced superpacket: flags=0x%02x", flags) + } + if flags&tcpAck == 0 { + t.Fatalf("ACK missing from coalesced superpacket: flags=0x%02x", flags) + } +} + func TestCoalescerRejectsDifferentFlow(t *testing.T) { w := &fakeTunWriter{gsoEnabled: true} c := NewTCPCoalescer(w)