mirror of
https://github.com/soypat/lneto.git
synced 2026-08-21 15:09:05 +00:00
Tcp rst handling (#40)
* claude suggests a way forward * add timing to capture printer * add pcap.Flags * fix ICMP CRC calculation and add test * bugfix: still send data on half-close state(close-wait) * fix pcap test
This commit is contained in:
@@ -468,6 +468,158 @@ func TestWindowUpdateSWSAvoidance(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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 = 1500
|
||||
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 = 1500
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user