package tcp import ( "bytes" "fmt" "io" "math/rand" "testing" "github.com/soypat/lneto/ethernet" ) func TestHandler(t *testing.T) { const mtu = ethernet.MaxMTU const maxpackets = 3 rng := rand.New(rand.NewSource(0)) client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets) setupClientServer(t, rng, client, server) var rawbuf [mtu]byte establish(t, client, server, rawbuf[:]) sendDataFull(t, client, server, []byte("hello"), rawbuf[:]) } func TestHandler_RequeueControlRetransmitsSYN(t *testing.T) { const mtu = ethernet.MaxMTU const maxpackets = 3 rng := rand.New(rand.NewSource(1)) client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets) setupClientServer(t, rng, client, server) var rawbuf [mtu]byte n, err := client.Send(rawbuf[:]) if err != nil { t.Fatal("client sending initial SYN:", err) } else if n < sizeHeaderTCP { t.Fatalf("initial SYN size=%d, want at least %d", n, sizeHeaderTCP) } initial := mustSegment(t, rawbuf[:n], 0) if initial.Flags != FlagSYN { t.Fatalf("initial flags=%s, want SYN", initial.Flags) } if client.State() != StateSynSent { t.Fatalf("client state=%s, want SYN-SENT", client.State()) } clear(rawbuf[:]) n, err = client.Send(rawbuf[:]) if err != nil { t.Fatal("client sending before RequeueControl:", err) } else if n != 0 { t.Fatalf("Send before RequeueControl wrote %d bytes, want 0", n) } client.RequeueControl() clear(rawbuf[:]) n, err = client.Send(rawbuf[:]) if err != nil { t.Fatal("client retransmitting SYN:", err) } else if n < sizeHeaderTCP { t.Fatalf("retransmitted SYN size=%d, want at least %d", n, sizeHeaderTCP) } retransmit := mustSegment(t, rawbuf[:n], 0) if retransmit.Flags != FlagSYN { t.Fatalf("retransmit flags=%s, want SYN", retransmit.Flags) } if retransmit.SEQ != initial.SEQ { t.Fatalf("retransmit SEQ=%d, want initial SEQ=%d", retransmit.SEQ, initial.SEQ) } if err := server.Recv(rawbuf[:n]); err != nil { t.Fatal("server receiving retransmitted SYN:", err) } clear(rawbuf[:]) n, err = server.Send(rawbuf[:]) if err != nil { t.Fatal("server sending SYN-ACK:", err) } segSynAck := mustSegment(t, rawbuf[:n], 0) if segSynAck.Flags != synack { t.Fatalf("server flags=%s, want SYN-ACK", segSynAck.Flags) } if err := client.Recv(rawbuf[:n]); err != nil { t.Fatal("client receiving SYN-ACK:", err) } if client.State() != StateEstablished { t.Fatalf("client state=%s, want ESTABLISHED", client.State()) } clear(rawbuf[:]) n, err = client.Send(rawbuf[:]) // final ACK. if err != nil { t.Fatal("client sending final ACK:", err) } if ack := mustSegment(t, rawbuf[:n], 0); ack.Flags != FlagACK { t.Fatalf("client final flags=%s, want ACK", ack.Flags) } if err := server.Recv(rawbuf[:n]); err != nil { t.Fatal("server receiving final ACK:", err) } client.RequeueControl() clear(rawbuf[:]) n, err = client.Send(rawbuf[:]) if err != nil { t.Fatal("client sending after establishment:", err) } else if n != 0 { seg := mustSegment(t, rawbuf[:n], 0) t.Fatalf("Send after establishment wrote %d bytes (%s), want 0", n, seg.Flags) } } func mustSegment(t *testing.T, b []byte, payloadLen int) Segment { t.Helper() frame, err := NewFrame(b) if err != nil { t.Fatal("parse TCP frame:", err) } return frame.Segment(payloadLen) } func sendDataFull(t *testing.T, client, server *Handler, data, packetBuf []byte) { n, err := client.Write(data) if err != nil { t.Fatal("client write:", err) } else if n != len(data) { t.Fatal("expected client to write full data packet") } n, err = client.Send(packetBuf) if err != nil { t.Fatal("client sending:", err) } else if n < len(data)+sizeHeaderTCP { t.Fatal("expected client to send full data packet", n, len(data)+sizeHeaderTCP) } err = server.Recv(packetBuf[:n]) if err != nil { t.Fatal("server receiving:", err) } else if server.BufferedInput() != len(data) { t.Fatal("server did not receive full data packet", server.BufferedInput(), len(data)) } clear(packetBuf) n, err = server.Read(packetBuf) if err != nil { t.Fatal("server read:", err) } else if n != len(data) { t.Fatal("expected server to read full data packet") } else if !bytes.Equal(packetBuf[:n], data) { t.Fatal("server received unexpected data") } } func newHandler(t *testing.T, mtu, mintaxpackets int) *Handler { h := new(Handler) err := h.SetBuffers(make([]byte, mtu), make([]byte, mtu), mintaxpackets) if err != nil { t.Fatal(err) } return h } func setupClientServer(t *testing.T, rng *rand.Rand, client, server *Handler) { // Ensure buffer sizes are OK with reused buffers. err := client.SetBuffers(nil, nil, 0) if err != nil { t.Fatal(err) } err = server.SetBuffers(nil, nil, 0) if err != nil { t.Fatal(err) } err = server.OpenListen(uint16(rng.Uint32()), 0) if err != nil { t.Fatal(err) } err = client.OpenActive(uint16(rng.Uint32()), server.LocalPort(), 0) if err != nil { t.Fatal(err) } if !client.AwaitingSynSend() { t.Fatal("client in wrong state") } if !server.AwaitingSynAck() { t.Fatal("server in wrong state") } } func establish(t *testing.T, client, server *Handler, packetBuf []byte) { if client.State() != StateClosed { t.Fatal("client in wrong state") } else if server.State() != StateListen { t.Fatal("server in wrong state") } clear(packetBuf) // Commence 3-way handshake: client sends SYN, server sends SYN-ACK, client sends ACK. // Client sends SYN. n, err := client.Send(packetBuf) if err != nil { t.Fatal("client sending:", err) } else if n < sizeHeaderTCP { t.Fatal("expected client to send SYN packet") } else if client.State() != StateSynSent { t.Fatal("client did not transition to SynSent state:", client.State().String()) } err = server.Recv(packetBuf[:n]) // Server receives SYN. if err != nil { t.Fatal(err) } else if server.State() != StateSynRcvd { t.Fatal("server did not transition to SynReceived state:", server.State().String()) } clear(packetBuf) // Server sends SYNACK response to client's SYN. n, err = server.Send(packetBuf) if err != nil { t.Fatal("server sending:", err) } else if n < sizeHeaderTCP { t.Fatal("expected server to send SYNACK packet") } else if server.State() != StateSynRcvd { t.Fatal("server should remain in SynReceived state:", server.State().String()) } err = client.Recv(packetBuf[:n]) // Client receives SYNACK, is established but must send ACK. if err != nil { t.Fatal(err) } else if client.State() != StateEstablished { t.Fatal("client did not transition to Established state:", client.State().String()) } clear(packetBuf) n, err = client.Send(packetBuf) // Client sends ACK. if err != nil { t.Fatal("client sending ACK:", err) } else if n < sizeHeaderTCP { t.Fatal("expected client to send ACK packet") } else if client.State() != StateEstablished { t.Fatal("client should remain in Established state:", client.State().String()) } err = server.Recv(packetBuf[:n]) // Server receives ACK. if err != nil { t.Fatal(err) } else if server.State() != StateEstablished { t.Fatal("server did not transition to Established state on ACK receive:", server.State().String()) } } // TestHandler_MSSHonored verifies that the server respects the MSS option from // the client's SYN when sending data segments. The client uses a small packet // buffer for its SYN (advertising MSS=100), and the server should not send // segments with more than 100 bytes of payload. func TestHandler_MSSHonored(t *testing.T) { const mtu = ethernet.MaxMTU rng := rand.New(rand.NewSource(0)) client, server := newHandler(t, mtu, 3), newHandler(t, mtu, 3) setupClientServer(t, rng, client, server) // Use a 120-byte buffer for client SYN so MSS option = 120 - 20 = 100. var smallBuf [120]byte var largeBuf [mtu]byte // Client sends SYN (MSS=100 in TCP options). n, err := client.Send(smallBuf[:]) if err != nil { t.Fatal("client SYN:", err) } err = server.Recv(smallBuf[:n]) if err != nil { t.Fatal("server recv SYN:", err) } // Server sends SYN-ACK. clear(largeBuf[:]) n, err = server.Send(largeBuf[:]) if err != nil { t.Fatal("server SYN-ACK:", err) } err = client.Recv(largeBuf[:n]) if err != nil { t.Fatal("client recv SYN-ACK:", err) } // Client sends ACK. clear(largeBuf[:]) n, err = client.Send(largeBuf[:]) if err != nil { t.Fatal("client ACK:", err) } err = server.Recv(largeBuf[:n]) if err != nil { t.Fatal("server recv ACK:", err) } if server.State() != StateEstablished { t.Fatal("server not established:", server.State()) } // Write 200 bytes to server's TX buffer. data := make([]byte, 200) for i := range data { data[i] = byte(i) } nw, err := server.Write(data) if err != nil { t.Fatal("server write:", err) } else if nw != 200 { t.Fatal("server write short:", nw) } // Server sends data — should be capped at client's MSS (100). clear(largeBuf[:]) n, err = server.Send(largeBuf[:]) if err != nil { t.Fatal("server send data:", err) } else if n == 0 { t.Fatal("server sent nothing") } tfrm, err := NewFrame(largeBuf[:n]) if err != nil { t.Fatal("parse server frame:", err) } payload := tfrm.Payload() const clientMSS = 100 if len(payload) > clientMSS { t.Errorf("server sent %d bytes payload, want <= %d (client MSS)", len(payload), clientMSS) } } func clear[E any, T []E](s T) { var zero E for i := range s { s[i] = zero } } // TestTxBufferFreedOnACK tests that the TX buffer is freed when ACKs are received. // This is a regression test for https://github.com/soypat/lneto/issues/22 // where ringTx.sentoff and ringTx.sentend were not being updated when ACKs // were received, causing AvailableOutput() to return 0 indefinitely after // the initial buffer was consumed. func TestTxBufferFreedOnACK(t *testing.T) { const mtu = 256 const maxpackets = 4 const txBufSize = 128 // Small TX buffer to easily fill it rng := rand.New(rand.NewSource(42)) // Create handlers with small TX buffers to easily trigger the issue. client := new(Handler) server := new(Handler) err := client.SetBuffers(make([]byte, txBufSize), make([]byte, mtu), maxpackets) if err != nil { t.Fatal(err) } err = server.SetBuffers(make([]byte, txBufSize), make([]byte, mtu), maxpackets) if err != nil { t.Fatal(err) } // Setup and establish connection. err = server.OpenListen(uint16(rng.Uint32()), 0) if err != nil { t.Fatal(err) } err = client.OpenActive(uint16(rng.Uint32()), server.LocalPort(), 0) if err != nil { t.Fatal(err) } var rawbuf [mtu]byte establish(t, client, server, rawbuf[:]) // Record initial available space. initialAvailable := client.FreeOutput() if initialAvailable == 0 { t.Fatal("expected non-zero initial available output") } // Write data to fill a significant portion of the TX buffer. data := make([]byte, txBufSize/2) for i := range data { data[i] = byte(i) } n, err := client.Write(data) if err != nil { t.Fatal("client write:", err) } else if n != len(data) { t.Fatalf("expected to write %d bytes, wrote %d", len(data), n) } // Available space should have decreased. afterWriteAvailable := client.FreeOutput() if afterWriteAvailable >= initialAvailable { t.Fatalf("expected available to decrease after write: before=%d, after=%d", initialAvailable, afterWriteAvailable) } // Client sends DATA packet. clear(rawbuf[:]) n, err = client.Send(rawbuf[:]) if err != nil { t.Fatal("client sending data:", err) } if n < len(data)+sizeHeaderTCP { t.Fatal("expected client to send full data packet") } dataPacket := append([]byte(nil), rawbuf[:n]...) // After sending, data moves from "unsent" to "sent" - available should still be reduced // until we receive an ACK. afterSendAvailable := client.FreeOutput() // Server receives DATA. err = server.Recv(dataPacket) if err != nil { t.Fatal("server receiving data:", err) } // Server sends ACK. clear(rawbuf[:]) n, err = server.Send(rawbuf[:]) if err != nil { t.Fatal("server sending ACK:", err) } ackPacket := append([]byte(nil), rawbuf[:n]...) // Client receives ACK - this is where the bug manifests. // Without the fix, the TX buffer's sentoff/sentend are not updated, // so AvailableOutput() remains low. err = client.Recv(ackPacket) if err != nil { t.Fatal("client receiving ACK:", err) } // THE BUG: After receiving ACK, the TX buffer should be freed. // Without the fix, AvailableOutput() stays at the post-send value. afterAckAvailable := client.FreeOutput() if afterAckAvailable <= afterSendAvailable { t.Fatalf("BUG (issue #22): TX buffer not freed after receiving ACK\n"+ "AvailableOutput() after send: %d\n"+ "AvailableOutput() after ACK: %d\n"+ "Expected available space to increase after ACK is received.\n"+ "The ringTx.sentoff and ringTx.sentend fields are not being updated\n"+ "because ringTx.RecvACK() is not called when ACKs are received.", afterSendAvailable, afterAckAvailable) } // Should be back to (approximately) initial available space. if afterAckAvailable < initialAvailable-10 { // Allow small margin for overhead t.Fatalf("expected available to return close to initial: initial=%d, afterAck=%d", initialAvailable, afterAckAvailable) } } // TestWindowUpdateAfterRead verifies that after the application reads data from // a full receive buffer (Window=0), the TCP stack queues a window update ACK // so the remote peer can resume sending. This is a regression test for a // zero-window deadlock: without proactive window updates, the remote peer stays // stuck at Window=0 indefinitely after the app frees buffer space via Read(). func TestWindowUpdateAfterRead(t *testing.T) { const rxBufSize = 256 const mtu = ethernet.MaxMTU const maxpackets = 4 rng := rand.New(rand.NewSource(99)) client := new(Handler) server := new(Handler) // Server gets a small RX buffer so we can fill it easily. err := client.SetBuffers(make([]byte, mtu), make([]byte, mtu), maxpackets) if err != nil { t.Fatal(err) } err = server.SetBuffers(make([]byte, mtu), make([]byte, rxBufSize), maxpackets) if err != nil { t.Fatal(err) } err = server.OpenListen(uint16(rng.Uint32()), 0) if err != nil { t.Fatal(err) } err = client.OpenActive(uint16(rng.Uint32()), server.LocalPort(), 0) if err != nil { t.Fatal(err) } var rawbuf [mtu]byte establish(t, client, server, rawbuf[:]) // Fill the server's RX buffer completely (without reading). fillData := make([]byte, server.FreeInput()) n, err := client.Write(fillData) if err != nil { t.Fatal("client write:", err) } else if n != len(fillData) { t.Fatal("short write") } clear(rawbuf[:]) n, err = client.Send(rawbuf[:]) if err != nil { t.Fatal("client send:", err) } err = server.Recv(rawbuf[:n]) if err != nil { t.Fatal("server recv:", err) } if server.FreeInput() != 0 { t.Fatalf("expected server RX buffer full, got %d free", server.FreeInput()) } // Server sends ACK — should advertise Window=0. clear(rawbuf[:]) n, err = server.Send(rawbuf[:]) if err != nil { t.Fatal("server send ACK:", err) } if n == 0 { t.Fatal("expected server to send ACK for received data") } zeroWndFrm, _ := NewFrame(rawbuf[:n]) if wnd := zeroWndFrm.WindowSize(); wnd != 0 { t.Fatalf("expected Window=0 in ACK, got %d", wnd) } // Verify no pending segment before Read (nothing to send). clear(rawbuf[:]) n, err = server.Send(rawbuf[:]) if err != nil { t.Fatal(err) } if n != 0 { t.Fatal("expected no pending segment before Read") } // App reads ALL data from server, freeing the entire buffer. readBuf := make([]byte, rxBufSize) n, err = server.Read(readBuf) if err != nil { t.Fatal("server read:", err) } if n != len(fillData) { t.Fatalf("read %d, expected %d", n, len(fillData)) } // Server should now have a pending window update ACK. clear(rawbuf[:]) n, err = server.Send(rawbuf[:]) if err != nil { t.Fatal("server send window update:", err) } if n == 0 { t.Fatal("BUG: no window update sent after Read() freed buffer space from Window=0") } wndFrm, _ := NewFrame(rawbuf[:n]) if wnd := wndFrm.WindowSize(); wnd == 0 { t.Fatal("BUG: window update ACK still has Window=0") } t.Logf("window update sent: Window=%d (buffer free=%d)", wndFrm.WindowSize(), server.FreeInput()) } // TestWindowUpdateSWSAvoidance verifies that small reads that free less than // half the buffer do NOT trigger a window update (Silly Window Syndrome avoidance). func TestWindowUpdateSWSAvoidance(t *testing.T) { const rxBufSize = 256 const mtu = ethernet.MaxMTU const maxpackets = 4 rng := rand.New(rand.NewSource(77)) client := new(Handler) server := new(Handler) err := client.SetBuffers(make([]byte, mtu), make([]byte, mtu), maxpackets) if err != nil { t.Fatal(err) } err = server.SetBuffers(make([]byte, mtu), make([]byte, rxBufSize), maxpackets) if err != nil { t.Fatal(err) } err = server.OpenListen(uint16(rng.Uint32()), 0) if err != nil { t.Fatal(err) } err = client.OpenActive(uint16(rng.Uint32()), server.LocalPort(), 0) if err != nil { t.Fatal(err) } var rawbuf [mtu]byte establish(t, client, server, rawbuf[:]) // Fill most of the server's RX buffer (leave a tiny amount free). fillSize := server.FreeInput() - 10 fillData := make([]byte, fillSize) for i := range fillData { fillData[i] = byte(i) } n, err := client.Write(fillData) if err != nil { t.Fatal("client write:", err) } else if n != len(fillData) { t.Fatal("short write") } clear(rawbuf[:]) n, err = client.Send(rawbuf[:]) if err != nil { t.Fatal("client send:", err) } err = server.Recv(rawbuf[:n]) if err != nil { t.Fatal("server recv:", err) } // Server sends ACK with small window. clear(rawbuf[:]) n, err = server.Send(rawbuf[:]) if err != nil { t.Fatal(err) } if n == 0 { t.Fatal("expected ACK") } // Client receives the ACK so its send window is updated. err = client.Recv(rawbuf[:n]) if err != nil { t.Fatal(err) } // App reads a small amount (less than half the buffer). smallRead := make([]byte, rxBufSize/4) n, err = server.Read(smallRead) if err != nil { t.Fatal("server read:", err) } if n == 0 { t.Fatal("expected to read data") } // Because freed space < bufSize/2, no window update should be queued. clear(rawbuf[:]) n, err = server.Send(rawbuf[:]) if err != nil { t.Fatal(err) } if n != 0 { t.Logf("NOTE: window update sent after small read (freed %d of %d buffer)", len(smallRead), rxBufSize) // This is acceptable if the threshold is met, but for SWS avoidance // we expect no update when the freed increment is < bufSize/2. freeAfterRead := Size(server.FreeInput()) if freeAfterRead < Size(rxBufSize/2) { t.Fatalf("SWS violation: window update sent when free=%d < bufSize/2=%d", freeAfterRead, rxBufSize/2) } } } // TestWriteAfterRemoteFIN verifies that when a remote peer sends FIN (entering // CLOSE_WAIT on our side), we can still write and send data before closing. // This is a regression test for a panic in sentlist.AddPacket caused by // PendingSegment returning DATALEN=0 while Handler.Send calls MakePacket with // available > 0, creating degenerate zero-data packets in the sent queue. // // The sequence that triggers the panic: // 1. Connection established // 2. Remote sends FIN,ACK → local enters CLOSE_WAIT // 3. Application writes data to TX buffer // 4. Handler.Send() is called: PendingSegment sets PSH because payloadLen>0, // then zeroes payloadLen because !established → DATALEN=0 but ok=true // 5. MakePacket called with zero-length buffer → creates {off:0,end:0} entry // 6. Handler.Send() called again → same thing → AddPacket panics because // off=0 but lastPkt.end=0 != bufsize func TestWriteAfterRemoteFIN(t *testing.T) { const mtu = ethernet.MaxMTU const maxpackets = 3 rng := rand.New(rand.NewSource(11)) client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets) setupClientServer(t, rng, client, server) var rawbuf [mtu]byte establish(t, client, server, rawbuf[:]) if server.State() != StateEstablished { t.Fatal("server not established:", server.State()) } // Client initiates close (sends FIN). err := client.Close() if err != nil { t.Fatal("client close:", err) } clear(rawbuf[:]) n, err := client.Send(rawbuf[:]) if err != nil { t.Fatal("client sending FIN:", err) } if n < sizeHeaderTCP { t.Fatal("expected FIN packet") } if client.State() != StateFinWait1 { t.Fatal("client not in FIN_WAIT_1:", client.State()) } // Server receives FIN → enters CLOSE_WAIT. err = server.Recv(rawbuf[:n]) if err != nil { t.Fatal("server receiving FIN:", err) } if server.State() != StateCloseWait { t.Fatal("server not in CLOSE_WAIT:", server.State()) } // Application writes data (like an HTTP 404 response). responseData := []byte("HTTP/1.1 404 Not Found\r\n\r\n") nw, err := server.Write(responseData) if err != nil { t.Fatal("server write:", err) } if nw != len(responseData) { t.Fatal("short write:", nw) } // Server sends response — this should include the data, not panic. // The bug causes a panic on the second Send() call because the first // creates a degenerate zero-data packet in the sentlist. clear(rawbuf[:]) n, err = server.Send(rawbuf[:]) if err != nil { t.Fatal("server send 1:", err) } clear(rawbuf[:]) n, err = server.Send(rawbuf[:]) if err != nil { t.Fatal("server send 2:", err) } } // TestRSTinSynReceived verifies that a RST received during the SYN-RECEIVED // state correctly reverts the connection to LISTEN per RFC 9293 §3.5.3. // This is a regression test for a bug where RST segments in non-synchronized // states were blocked by errRequireSequential, causing connection pool leaks. func TestRSTinSynReceived(t *testing.T) { const mtu = ethernet.MaxMTU const maxpackets = 3 rng := rand.New(rand.NewSource(2)) client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets) setupClientServer(t, rng, client, server) var rawbuf [mtu]byte // Client sends SYN. clear(rawbuf[:]) n, err := client.Send(rawbuf[:]) if err != nil { t.Fatal("client sending SYN:", err) } if client.State() != StateSynSent { t.Fatal("client not in SynSent:", client.State()) } // Server receives SYN → transitions to SYN-RECEIVED. err = server.Recv(rawbuf[:n]) if err != nil { t.Fatal("server receiving SYN:", err) } if server.State() != StateSynRcvd { t.Fatal("server not in SynRcvd:", server.State()) } // Server sends SYN,ACK. clear(rawbuf[:]) n, err = server.Send(rawbuf[:]) if err != nil { t.Fatal("server sending SYN,ACK:", err) } if n < sizeHeaderTCP { t.Fatal("expected SYN,ACK packet") } synackFrm, _ := NewFrame(rawbuf[:n]) synackSeg := synackFrm.Segment(0) // Construct RST packet from client perspective (as if the remote peer // rejected the connection). SEQ = ACK from SYN,ACK, no ACK flag, no payload. clear(rawbuf[:]) rstFrm, err := NewFrame(rawbuf[:]) if err != nil { t.Fatal("new frame:", err) } rstSeg := Segment{ SEQ: synackSeg.ACK, // SEQ = server's ACK value = in window. Flags: FlagRST, } rstFrm.SetSourcePort(client.localPort) rstFrm.SetDestinationPort(server.localPort) rstFrm.SetSegment(rstSeg, 5) rstFrm.SetUrgentPtr(0) // Server receives RST → should revert to LISTEN per RFC 9293 §3.5.3. err = server.Recv(rawbuf[:sizeHeaderTCP]) if !IsDroppedErr(err) { t.Fatal("expected drop segment error from RST recv, got:", err) } if server.State() != StateListen { t.Fatalf("expected server LISTEN after RST in SYN-RECEIVED, got %s", server.State()) } if server.scb.HasPending() { t.Fatal("server should have no pending segments after RST") } } // TestBufferNotClearedOnPassiveClose tests that data remains readable after // the TCP connection is closed by the remote peer. This is a regression test // for a bug where the receive buffer was cleared when the connection transitioned // to CLOSED state, causing data loss. // // The sequence is: // 1. Server sends DATA + initiates close (FIN) // 2. Client receives data, enters CLOSE_WAIT // 3. Client sends ACK, then FIN+ACK (enters LAST_ACK) // 4. Server sends final ACK // 5. Client receives ACK in LAST_ACK -> state becomes CLOSED // 6. At this point, client.Read() should still return the buffered data // // The bug was that reset() cleared bufRx when state became CLOSED. func TestBufferNotClearedOnPassiveClose(t *testing.T) { const mtu = ethernet.MaxMTU const maxpackets = 3 rng := rand.New(rand.NewSource(1)) client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets) setupClientServer(t, rng, client, server) var rawbuf [mtu]byte establish(t, client, server, rawbuf[:]) // Server writes data to be sent. data := []byte("hello world - this data should survive close") n, err := server.Write(data) if err != nil { t.Fatal("server write:", err) } else if n != len(data) { t.Fatal("expected server to write full data") } // Server sends DATA packet. clear(rawbuf[:]) n, err = server.Send(rawbuf[:]) if err != nil { t.Fatal("server sending data:", err) } else if n < len(data)+sizeHeaderTCP { t.Fatal("expected server to send full data packet") } dataPacket := append([]byte(nil), rawbuf[:n]...) // Save for later use. // Client receives DATA. err = client.Recv(dataPacket) if err != nil { t.Fatal("client receiving data:", err) } if client.BufferedInput() != len(data) { t.Fatalf("client did not buffer data: got %d, want %d", client.BufferedInput(), len(data)) } // Server initiates close (will send FIN on next Send). err = server.Close() if err != nil { t.Fatal("server close:", err) } // Server sends FIN (enters FIN_WAIT_1). clear(rawbuf[:]) n, err = server.Send(rawbuf[:]) if err != nil { t.Fatal("server sending FIN:", err) } if server.State() != StateFinWait1 { t.Fatalf("expected server in FIN_WAIT_1, got %s", server.State()) } finPacket := append([]byte(nil), rawbuf[:n]...) // Client receives FIN (enters CLOSE_WAIT). err = client.Recv(finPacket) if err != nil { t.Fatal("client receiving FIN:", err) } if client.State() != StateCloseWait { t.Fatalf("expected client in CLOSE_WAIT, got %s", client.State()) } // Client sends ACK for FIN. clear(rawbuf[:]) n, err = client.Send(rawbuf[:]) if err != nil { t.Fatal("client sending ACK:", err) } ackPacket := append([]byte(nil), rawbuf[:n]...) // Server receives ACK (enters FIN_WAIT_2). err = server.Recv(ackPacket) if err != nil { t.Fatal("server receiving ACK:", err) } if server.State() != StateFinWait2 { t.Fatalf("expected server in FIN_WAIT_2, got %s", server.State()) } // Client initiates its own close (will send FIN on next Send). err = client.Close() if err != nil { t.Fatal("client close:", err) } // Client sends FIN (enters LAST_ACK). clear(rawbuf[:]) n, err = client.Send(rawbuf[:]) if err != nil { t.Fatal("client sending FIN:", err) } if client.State() != StateLastAck { t.Fatalf("expected client in LAST_ACK, got %s", client.State()) } clientFinPacket := append([]byte(nil), rawbuf[:n]...) // Server receives client's FIN (enters TIME_WAIT). err = server.Recv(clientFinPacket) if err != nil { t.Fatal("server receiving client FIN:", err) } if server.State() != StateTimeWait { t.Fatalf("expected server in TIME_WAIT, got %s", server.State()) } // Server sends final ACK. clear(rawbuf[:]) n, err = server.Send(rawbuf[:]) if err != nil { t.Fatal("server sending final ACK:", err) } finalAckPacket := append([]byte(nil), rawbuf[:n]...) if client.BufferedInput() == 0 { t.Fatal("emptied buffer") } // Client receives final ACK (should enter CLOSED). // This is where the bug manifests: the buffer gets cleared. err = client.Recv(finalAckPacket) // Note: client.Recv returns net.ErrClosed when state becomes CLOSED, that's expected. if err != nil && err.Error() != "use of closed network connection" { t.Fatal("client receiving final ACK:", err) } if client.State() != StateClosed { t.Fatalf("expected client in CLOSED, got %s", client.State()) } // THE BUG: At this point, the data should still be readable, but the // buffer was cleared by reset() when state transitioned to CLOSED. // // This test will FAIL until the bug is fixed. readBuf := make([]byte, mtu) n, err = client.Read(readBuf) if err != nil && n == 0 { t.Fatalf("BUG: Could not read buffered data after connection closed: %v\n"+ "Expected to read %d bytes of data that was received before the connection closed.\n"+ "The receive buffer was incorrectly cleared when the connection transitioned to CLOSED state.", err, len(data)) } if n != len(data) { t.Fatalf("read wrong amount: got %d, want %d", n, len(data)) } if !bytes.Equal(readBuf[:n], data) { t.Fatalf("read wrong data: got %q, want %q", readBuf[:n], data) } } // TestChallengeACKWithBufferedData verifies that a challenge ACK triggered by // an out-of-order segment does not corrupt the TX sentlist when there is // buffered data waiting to be sent. // // This is a regression test for a panic in sentlist.AddPacket: // // "new sent packet offset must match last sent packet end" // // The sequence that triggers the panic: // 1. Connection established, both sides ESTABLISHED // 2. Application writes data to TX buffer // 3. Out-of-order segment arrives → challengeAck flag set // 4. Handler.Send() called: PendingSegment returns challenge ACK (DATALEN=0) // but available > 0, so MakePacket is called with zero-length buffer → // creates degenerate {off:0,end:0,size:0} entry in sentlist // 5. Handler.Send() called again → AddPacket panics because off=0 but // lastPkt.end=0 != bufsize func TestChallengeACKWithBufferedData(t *testing.T) { const mtu = ethernet.MaxMTU const maxpackets = 3 rng := rand.New(rand.NewSource(42)) client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets) setupClientServer(t, rng, client, server) var rawbuf [mtu]byte establish(t, client, server, rawbuf[:]) if server.State() != StateEstablished { t.Fatal("server not established:", server.State()) } // Buffer data on the server side for transmission. responseData := []byte("HTTP/1.1 200 OK\r\n\r\nhello") nw, err := server.Write(responseData) if err != nil { t.Fatal("server write:", err) } if nw != len(responseData) { t.Fatal("short write:", nw) } // Craft an out-of-order segment from the client to trigger a challenge ACK. // Use a real client packet as template: client sends a normal ACK, then we // corrupt the SEQ field to be 3 bytes ahead of what the server expects. clear(rawbuf[:]) n, err := client.Send(rawbuf[:]) if n >= sizeHeaderTCP { // There was a pending ACK from establishment. Deliver it first so server // state is clean, then craft the bad segment. _ = server.Recv(rawbuf[:n]) } // Build an out-of-order segment: valid ports, valid ACK, but SEQ is wrong. clear(rawbuf[:]) oooFrame, _ := NewFrame(rawbuf[:sizeHeaderTCP]) oooFrame.SetSourcePort(client.LocalPort()) oooFrame.SetDestinationPort(server.LocalPort()) oooFrame.SetSeq(server.scb.rcv.NXT + 3) // 3 bytes ahead of expected. oooFrame.SetAck(server.scb.snd.UNA) oooFrame.SetSegment(Segment{ SEQ: server.scb.rcv.NXT + 3, ACK: server.scb.snd.UNA, Flags: FlagACK, WND: 1024, }, 5) // Server receives the out-of-order segment. This sets challengeAck=true // and returns an error (errRequireSequential), which is expected. err = server.Recv(rawbuf[:sizeHeaderTCP]) if err == nil { t.Fatal("expected error from out-of-order segment") } if !server.scb.pendingChallengeAck() { t.Fatal("challengeAck flag not set after out-of-order segment") } if server.State() != StateEstablished { t.Fatal("server should remain ESTABLISHED, got:", server.State()) } // First Send: should emit the challenge ACK without panicking. // The bug causes MakePacket to be called with zero-length buffer here. clear(rawbuf[:]) n, err = server.Send(rawbuf[:]) if err != nil { t.Fatal("server send 1 (challenge ACK):", err) } if n < sizeHeaderTCP { t.Fatal("expected challenge ACK packet") } // Second Send: should send the buffered data without panicking. // The bug panics here in AddPacket due to the degenerate sentlist entry. clear(rawbuf[:]) n, err = server.Send(rawbuf[:]) if err != nil { t.Fatal("server send 2 (data):", err) } if n <= sizeHeaderTCP { t.Fatal("expected data packet, got header-only") } } func TestHandler_RetransmitAfter3DupACKs(t *testing.T) { const ( mtu = 1500 maxpackets = 3 ) rng := rand.New(rand.NewSource(42)) client := newHandler(t, mtu, maxpackets) server := newHandler(t, mtu, maxpackets) setupClientServer(t, rng, client, server) var pkt [mtu]byte establish(t, client, server, pkt[:]) // Client sends some data in flight. payload := []byte("0123456789") written, err := client.Write(payload) if err != nil || written != len(payload) { t.Fatalf("client.Write failed: %v len=%d", err, written) } n, err := client.Send(pkt[:]) if err != nil { t.Fatalf("client.Send initial data: %v", err) } if n <= sizeHeaderTCP { t.Fatalf("expected non-empty data packet; got %d", n) } // Server does NOT receive the intended packet, but rather the retransmission later on. // no server.Recv(pkt[:n]) -> Packet loss. // Simulate 3 duplicate ACKs (ACK == UNA, no progress). dup := server.scb.MakeDupACK() if !client.scb.IncomingIsDupACK(dup.ACK) { t.Fatal("MakeRetransmitDupACK return should be considered a duplicate ACK by remote") } for i := range 3 { fb, _ := NewFrame(pkt[:]) fb.SetSourcePort(server.LocalPort()) fb.SetDestinationPort(client.LocalPort()) fb.SetSegment(dup, 5) if err := client.Recv(pkt[:sizeHeaderTCP]); err != nil { t.Fatalf("client.Recv dupACK #%d failed: %v", i+1, err) } } if client.scb.dupack != 3 { t.Fatalf("expected dupack=3; got %d", client.scb.dupack) } if !client.scb.HasPendingRetransmit() { t.Fatal("expected HasPendingRetransmit() true after 3 dupACKs") } oldUNA := client.scb.snd.UNA n, err = client.Send(pkt[:]) if err != nil { t.Fatalf("client.Send retransmit failed: %v", err) } if n <= sizeHeaderTCP { t.Fatalf("expected retransmit segment (>=20 bytes); got %d", n) } else if client.scb.HasPendingRetransmit() { t.Fatal("expected client to satisfy pending retransmit after single Send call") } retransmitFrame, _ := NewFrame(pkt[:n]) rtSeg := retransmitFrame.Segment(0) if rtSeg.SEQ != oldUNA { t.Fatalf("retransmit SEQ = %d; expected UNA=%d", rtSeg.SEQ, oldUNA) } if !rtSeg.Flags.HasAny(FlagACK) { t.Fatalf("retransmit missing ACK flag: %#v", rtSeg.Flags) } if client.scb.nRetransmit != 1 { t.Fatalf("expected scb.nRetransmit = 1; got %d", client.scb.nRetransmit) } // Ensure remote side can receive the retransmit frame. if err := server.Recv(pkt[:n]); err != nil { t.Fatalf("server.Recv retransmit packet failed: %v", err) } } func TestHandler_RetransmitAfterMultipleLossesBothDirections(t *testing.T) { const ( mtu = 1500 maxpackets = 3 loops = 3 ) rng := rand.New(rand.NewSource(1)) client := newHandler(t, mtu, maxpackets) server := newHandler(t, mtu, maxpackets) setupClientServer(t, rng, client, server) var pkt [mtu]byte establish(t, client, server, pkt[:]) sendWithLoss := func(sender, receiver *Handler, pay []byte) { n, err := sender.Write(pay) if err != nil || n != len(pay) { t.Fatalf("write failed: %v len=%d", err, n) } n, err = sender.Send(pkt[:]) if err != nil { t.Fatalf("Send initial data: %v", err) } else if n <= sizeHeaderTCP { t.Fatalf("expected non-empty data packet; got %d", n) } else if sender.BufferedUnsent() > 0 { t.Fatal("buffer too small to send all data") } // Drop packet (simulate loss): NO receiver.Recv(pkt[:n]). // Three dupACKs from receiver side (its rcv state has not advanced). dup := receiver.scb.MakeDupACK() if !sender.scb.IncomingIsDupACK(dup.ACK) { t.Fatal("dup ACK not recognized as dupack by sender") } for i := range 3 { clear(pkt[:]) fb, _ := NewFrame(pkt[:]) fb.SetSourcePort(receiver.LocalPort()) fb.SetDestinationPort(sender.LocalPort()) fb.SetSegment(dup, 5) if !sender.scb.IncomingIsDupACK(dup.ACK) { t.Fatal("expected incoming segment to be dupack") } if err := sender.Recv(pkt[:sizeHeaderTCP]); err != nil { t.Fatalf("sender.Recv dupACK #%d failed: %v", i+1, err) } } t.Log("dupack", sender.scb.dupack) if sender.scb.dupack != 3 { t.Fatalf("expected dupack=3; got=%d", sender.scb.dupack) } else if !sender.scb.HasPendingRetransmit() { t.Fatal("expected pending retransmit after 3 dupacks") } // Now expect retransmission packet. oldUNA := sender.scb.snd.UNA clear(pkt[:]) n, err = sender.Send(pkt[:]) if err != nil { t.Fatalf("sender.Send retransmit failed: %v", err) } else if n <= sizeHeaderTCP { t.Fatalf("expected retransmit packet; got %d", n) } else if sender.scb.HasPendingRetransmit() { t.Error("after one retransmit should be satisfied") } retrFrm, _ := NewFrame(pkt[:n]) seg := retrFrm.Segment(0) if seg.SEQ != oldUNA { t.Fatalf("retransmit SEQ=%d; want=%d", seg.SEQ, oldUNA) } // Receiver consumes retransmit if err := receiver.Recv(pkt[:n]); err != nil { t.Fatalf("receiver.Recv retransmit failed: %v", err) } if receiver.scb.dupack > 0 { t.Fatal("receiver has dupack", receiver.scb.dupack) } // Receiver ACKs, so sender progresses and dupack should reset. clear(pkt[:]) n, err = receiver.Send(pkt[:]) if err != nil { t.Fatalf("receiver.Send ACK after retransmit: %v", err) } if n > 0 { if err := sender.Recv(pkt[:n]); err != nil { t.Fatalf("sender.Recv ACK after retransmit: %v", err) } } if sender.scb.dupack != 0 { t.Fatalf("expected sender.dupack reset, got %d", sender.scb.dupack) } } // Do several losses in client->server direction for i := range loops { payload := fmt.Appendf(nil, "C->S loss %d", i) sendWithLoss(client, server, payload) sendWithLoss(client, server, payload) sendWithLoss(server, client, payload) sendWithLoss(client, server, payload) sendWithLoss(server, client, payload) sendWithLoss(server, client, payload) } } // driveToFinWait2 performs an active close from client: Close() → FIN, the // server ACKs it (no FIN of its own), leaving client in FIN-WAIT-2. func driveToFinWait2(t *testing.T, client, server *Handler, buf []byte) { t.Helper() if err := client.Close(); err != nil { t.Fatal("client close:", err) } clear(buf) n, err := client.Send(buf) if err != nil { t.Fatal("client send FIN:", err) } if client.State() != StateFinWait1 { t.Fatal("client not FIN-WAIT-1:", client.State()) } if err := server.Recv(buf[:n]); err != nil { t.Fatal("server recv FIN:", err) } clear(buf) n, err = server.Send(buf) // pure ACK of the FIN. if err != nil { t.Fatal("server send ACK:", err) } if err := client.Recv(buf[:n]); err != nil { t.Fatal("client recv ACK:", err) } if client.State() != StateFinWait2 { t.Fatal("client not FIN-WAIT-2:", client.State()) } } // serverSendData writes data on the server and emits it as one packet, returning // the packet length in buf. func serverSendData(t *testing.T, server *Handler, data, buf []byte) int { t.Helper() if _, err := server.Write(data); err != nil { t.Fatal("server write:", err) } clear(buf) n, err := server.Send(buf) if err != nil { t.Fatal("server send data:", err) } if n <= sizeHeaderTCP { t.Fatal("server emitted no data segment") } return n } // TestFinWait2_FullClose_RST: when the application is done in BOTH directions — // read side shut down ([Handler.ShutdownRead]) AND our FIN sent (Close) — inbound // data in FIN-WAIT-2 has no consumer. The Handler must reply RST (not silently // ACK-and-drop, which leaves the peer waiting) and tear down the local // connection. Regression for soypat/lneto#50, reworked to gate on the read // shutdown so RFC half-close is preserved (see TestFinWait2_HalfClose_DataReadable). func TestFinWait2_FullClose_RST(t *testing.T) { const mtu = ethernet.MaxMTU rng := rand.New(rand.NewSource(50)) client, server := newHandler(t, mtu, 3), newHandler(t, mtu, 3) setupClientServer(t, rng, client, server) var buf [mtu]byte establish(t, client, server, buf[:]) client.ShutdownRead() // application is done reading. driveToFinWait2(t, client, server, buf[:]) // Peer pipelines a request on the fully-closed connection. n := serverSendData(t, server, []byte("GET / HTTP/1.1\r\nHost: x\r\n\r\n"), buf[:]) err := client.Recv(buf[:n]) if err != nil && !IsDroppedErr(err) { t.Fatal("client recv data:", err) } // Response must be RST. clear(buf[:]) n, err = client.Send(buf[:]) if err != nil { t.Fatal("client send response:", err) } if n < sizeHeaderTCP { t.Fatal("no response emitted to data in fully-closed FIN-WAIT-2") } resp, _ := NewFrame(buf[:n]) if !resp.Segment(0).Flags.HasAny(FlagRST) { t.Fatalf("data after full close must elicit RST; got flags=%s", resp.Segment(0).Flags) } // Post-RST teardown: the local connection must be gone, not stuck in FIN-WAIT-2. if client.State() != StateClosed { t.Fatalf("connection not torn down after RST: state=%s (want CLOSED)", client.State()) } } // TestFinWait2_HalfClose_DataReadable guards the RFC half-close model: when only // Close() was called (write side done) but the read side is still open, data // arriving in FIN-WAIT-2 must be ACKed and delivered to the application — never // reset. This is the case the original #50 fix wrongly broke. func TestFinWait2_HalfClose_DataReadable(t *testing.T) { const mtu = ethernet.MaxMTU rng := rand.New(rand.NewSource(51)) client, server := newHandler(t, mtu, 3), newHandler(t, mtu, 3) setupClientServer(t, rng, client, server) var buf [mtu]byte establish(t, client, server, buf[:]) // NOTE: no ShutdownRead — app closed the write half only, still reading. driveToFinWait2(t, client, server, buf[:]) data := []byte("late peer data") n := serverSendData(t, server, data, buf[:]) if err := client.Recv(buf[:n]); err != nil { t.Fatalf("half-close: data in FIN-WAIT-2 must be accepted, not dropped: %v", err) } if client.State() != StateFinWait2 { t.Fatalf("state changed on half-close data: got %s want FIN-WAIT-2", client.State()) } // Data must be readable by the application. var rd [64]byte rn, err := client.Read(rd[:]) if err != nil { t.Fatal("client read:", err) } if string(rd[:rn]) != string(data) { t.Fatalf("read %q; want %q", rd[:rn], data) } // The response must never be a RST. clear(buf[:]) n, err = client.Send(buf[:]) if err != nil { t.Fatal("client send:", err) } if n >= sizeHeaderTCP { if s, _ := NewFrame(buf[:n]); s.Segment(0).Flags.HasAny(FlagRST) { t.Fatal("half-close reader must not RST inbound data") } } } // TestRetransmit_CumulativeACK_NoSpurious reproduces soypat/lneto#57. // lneto streams 4 segments (TX queue=4); the first is "lost". A Linux-style // remote buffers the rest out of order and dup-ACKs the hole. After 3 dup ACKs // lneto fast-retransmits the lost segment; the remote then cumulatively ACKs // ALL data it received. On the buggy design (snd.NXT rewound to UNA on // retransmit) lneto treated that ACK as acknowledging unsent data, dropped it, // and emitted spurious retransmissions of already-acked segments. // Correct behaviour: the cumulative ACK is accepted and nothing further is sent. // // Note: an lneto Handler cannot act as the server here (it rejects out-of-order // segments per errRequireSequential), so the remote's ACKs are hand-crafted from // captured sequence numbers, using only public API / observed packets. func TestRetransmit_CumulativeACK_NoSpurious(t *testing.T) { const mtu = 1500 const maxpackets = 4 // TX packet queue size 4, per issue. rng := rand.New(rand.NewSource(57)) client := newHandler(t, mtu, maxpackets) server := newHandler(t, mtu, maxpackets) setupClientServer(t, rng, client, server) var pkt [mtu]byte establish(t, client, server, pkt[:]) // Emit one small in-flight data segment and return it as observed on the wire. emit := func(payload string) Segment { clear(pkt[:]) if _, err := client.Write([]byte(payload)); err != nil { t.Fatal("client write:", err) } n, err := client.Send(pkt[:]) if err != nil { t.Fatal("client send data:", err) } if n <= sizeHeaderTCP { t.Fatal("expected data segment, got header-only") } f, _ := NewFrame(pkt[:n]) return f.Segment(n - sizeHeaderTCP) } // 3 segments in flight (queue=4 leaves a slot for the retransmit). seg1 is // "lost"; seg2/seg3 reach the remote. seg1 := emit("aaaa") emit("bbbb") seg3 := emit("cccc") remoteSeq := seg1.ACK // remote's seq (sends no data) == client.rcv.NXT. hole := seg1.SEQ // snd.UNA: the lost (first unacked) segment. cumAck := seg3.SEQ + Value(seg3.DATALEN) // all data sent == client snd.NXT. recvACK := func(seqv, ackv Value) error { clear(pkt[:]) f, _ := NewFrame(pkt[:]) f.SetSourcePort(server.LocalPort()) f.SetDestinationPort(client.LocalPort()) f.SetSegment(Segment{SEQ: seqv, ACK: ackv, Flags: FlagACK, WND: 64000}, 5) return client.Recv(pkt[:sizeHeaderTCP]) } // Remote dup-ACKs the hole 3 times → fast-retransmit trigger. for i := range 3 { if err := recvACK(remoteSeq, hole); err != nil { t.Fatalf("client.Recv dupACK #%d: %v", i+1, err) } } // lneto fast-retransmits the lost segment. clear(pkt[:]) n, err := client.Send(pkt[:]) if err != nil { t.Fatal("client send fast-retransmit:", err) } if n <= sizeHeaderTCP { t.Fatal("expected fast-retransmit data segment") } if rt, _ := NewFrame(pkt[:n]); rt.Segment(0).SEQ != hole { t.Fatalf("fast retransmit SEQ=%d; want hole=%d", rt.Segment(0).SEQ, hole) } // Remote got the retransmit and cumulatively ACKs ALL data. if err := recvACK(remoteSeq, cumAck); err != nil { t.Fatalf("cumulative ACK (covers all sent data) must be accepted, not dropped (issue #57): %v", err) } // No spurious retransmission: everything acked, nothing left to send. clear(pkt[:]) n, err = client.Send(pkt[:]) if err != nil { t.Fatal("client send after cumulative ACK:", err) } if n > sizeHeaderTCP { s, _ := NewFrame(pkt[:n]) seg := s.Segment(n - sizeHeaderTCP) t.Fatalf("spurious retransmission after cumulative ACK: SEQ=%d len=%d (issue #57)", seg.SEQ, seg.DATALEN) } } // emitClientData writes payload to the client and emits it as one data packet, // returning a copy of the wire bytes (the caller controls delivery order). func emitClientData(t *testing.T, client *Handler, buf []byte, payload string) []byte { t.Helper() if _, err := client.Write([]byte(payload)); err != nil { t.Fatal("client write:", err) } clear(buf) n, err := client.Send(buf) if err != nil { t.Fatal("client send:", err) } if n <= sizeHeaderTCP { t.Fatal("expected a data segment, got header-only") } return append([]byte(nil), buf[:n]...) } // TestHandler_OutOfOrderReassembly drives the full out-of-order path: a later // segment delivered before the gap-filling one is staged, then delivered // contiguously once the gap arrives, without go-back-N. func TestHandler_OutOfOrderReassembly(t *testing.T) { const mtu = ethernet.MaxMTU const maxpackets = 4 rng := rand.New(rand.NewSource(99)) client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets) setupClientServer(t, rng, client, server) var buf [mtu]byte establish(t, client, server, buf[:]) pkt1 := emitClientData(t, client, buf[:], "AAAA") // seq S, covers S..S+4. pkt2 := emitClientData(t, client, buf[:], "BBBB") // seq S+4, covers S+4..S+8. full := server.SizeInput() // Deliver the second segment first: accepted, buffered, not yet readable. if err := server.Recv(pkt2); err != nil { t.Fatalf("out-of-order segment must be accepted, got: %v", err) } if server.BufferedInput() != 0 { t.Fatalf("OOO data must not be readable yet, buffered=%d", server.BufferedInput()) } // Advertised window shrinks by the held bytes so the peer cannot overrun. if got := server.FreeInput(); got != full-4 { t.Fatalf("FreeInput=%d, want %d (window reduced by held OOO bytes)", got, full-4) } // Deliver the gap-filling first segment: both become contiguous. if err := server.Recv(pkt1); err != nil { t.Fatalf("gap-filling segment: %v", err) } if server.BufferedInput() != 8 { t.Fatalf("buffered=%d, want 8 after gap fill", server.BufferedInput()) } if got := server.FreeInput(); got != full-8 { t.Fatalf("FreeInput=%d, want %d after delivery", got, full-8) } var rd [16]byte n, err := server.Read(rd[:]) if err != nil { t.Fatal("server read:", err) } if string(rd[:n]) != "AAAABBBB" { t.Fatalf("reassembled %q, want AAAABBBB", rd[:n]) } } // TestHandler_OutOfOrderDiscardedAfterShutdownRead verifies staged segments are // dropped, not delivered, when the read side is shut down before the gap fills. func TestHandler_OutOfOrderDiscardedAfterShutdownRead(t *testing.T) { const mtu = ethernet.MaxMTU const maxpackets = 4 rng := rand.New(rand.NewSource(100)) client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets) setupClientServer(t, rng, client, server) var buf [mtu]byte establish(t, client, server, buf[:]) pkt1 := emitClientData(t, client, buf[:], "AAAA") pkt2 := emitClientData(t, client, buf[:], "BBBB") if err := server.Recv(pkt2); err != nil { // buffer out of order. t.Fatalf("OOO segment: %v", err) } server.ShutdownRead() // application done reading; staged data must be dropped. if err := server.Recv(pkt1); err != nil && !IsDroppedErr(err) { t.Fatalf("gap-filling segment after shutdown: %v", err) } var rd [16]byte n, err := server.Read(rd[:]) if n != 0 || err != io.EOF { t.Fatalf("read after ShutdownRead = %d,%v want 0,EOF", n, err) } if server.BufferedInput() != 0 { t.Fatalf("discard mode must hold no data, buffered=%d", server.BufferedInput()) } }