mirror of
https://github.com/soypat/lneto.git
synced 2026-08-10 01:43:41 +00:00
faae7ab076
* fix for #58 * add fixes for both retransmit and fuzz failure found * fix infinite challenge ack due to RST+SYN+FIN corrupt packet in SYNRCV state * fuzz: previous recovery ack path led to infinite transmit * fuzz: calculate CRCs when fuzzing to reduce search space to non-CRC error cases * add local fuzz corpus to tests * add more cases to fuzz corpus
958 lines
34 KiB
Go
958 lines
34 KiB
Go
package tcp_test
|
|
|
|
import (
|
|
"math/rand"
|
|
"testing"
|
|
|
|
"github.com/soypat/lneto/tcp"
|
|
)
|
|
|
|
const (
|
|
SYNACK = tcp.FlagSYN | tcp.FlagACK
|
|
FINACK = tcp.FlagFIN | tcp.FlagACK
|
|
PSHACK = tcp.FlagPSH | tcp.FlagACK
|
|
)
|
|
|
|
// TestExchangeTest_PassiveClose_FINACKRegression is a regression test for the bug where
|
|
// Close() in CLOSE-WAIT state set pending = [FlagFIN, FlagACK] (separate slots) instead
|
|
// of pending = [FlagFIN|FlagACK, 0]. This caused PendingSegment() to return only FIN
|
|
// without ACK since it only reads pending[0].
|
|
func TestExchangeTest_PassiveClose_FINACKRegression(t *testing.T) {
|
|
const issA, issB, windowA, windowB = 100, 300, 1000, 1000
|
|
test := tcp.ExchangeTest{
|
|
ISSA: issA,
|
|
ISSB: issB,
|
|
WindowA: windowA,
|
|
WindowB: windowB,
|
|
InitStateA: tcp.StateEstablished,
|
|
InitStateB: tcp.StateEstablished,
|
|
Steps: []tcp.SegmentStep{
|
|
0: { // A sends FIN|ACK to B. B goes to CLOSE-WAIT.
|
|
Seg: tcp.Segment{SEQ: issA, ACK: issB, Flags: FINACK, WND: windowA},
|
|
Action: tcp.StepASends,
|
|
AState: tcp.StateFinWait1,
|
|
BState: tcp.StateCloseWait,
|
|
BPending: &tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: tcp.FlagACK, WND: windowB},
|
|
},
|
|
1: { // B sends ACK to A (RFC 9293 Figure 12 step 3). B stays in CLOSE-WAIT.
|
|
Seg: tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: tcp.FlagACK, WND: windowB},
|
|
Action: tcp.StepBSends,
|
|
AState: tcp.StateFinWait2,
|
|
BState: tcp.StateCloseWait,
|
|
},
|
|
2: { // B calls Close() (RFC 9293 Figure 12 step 4). Goes to LAST-ACK.
|
|
// Regression check: Close() must queue FIN|ACK as a single combined flag
|
|
// in pending[0], not as [FlagFIN, FlagACK] in separate pending slots.
|
|
Action: tcp.StepBCloses,
|
|
AState: tcp.StateFinWait2, // A unchanged.
|
|
BState: tcp.StateLastAck,
|
|
BPending: &tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: FINACK, WND: windowB},
|
|
},
|
|
3: { // B sends FIN|ACK to A.
|
|
Seg: tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: FINACK, WND: windowB},
|
|
Action: tcp.StepBSends,
|
|
AState: tcp.StateTimeWait,
|
|
BState: tcp.StateLastAck,
|
|
BPending: nil,
|
|
},
|
|
4: { // A sends final ACK to B. B goes to CLOSED.
|
|
Seg: tcp.Segment{SEQ: issA + 1, ACK: issB + 1, Flags: tcp.FlagACK, WND: windowA},
|
|
Action: tcp.StepASends,
|
|
AState: tcp.StateTimeWait,
|
|
BState: tcp.StateClosed,
|
|
},
|
|
},
|
|
}
|
|
test.RunB(t) // Only run B's perspective since that's where Close() is called.
|
|
}
|
|
|
|
// TestExchangeTest_figure12 demonstrates ExchangeTest which defines both peers' states
|
|
// symmetrically and runs tests from both perspectives with a single definition.
|
|
func TestExchangeTest_figure12(t *testing.T) {
|
|
const issA, issB, windowA, windowB = 100, 300, 1000, 1000
|
|
test := tcp.ExchangeTest{
|
|
ISSA: issA,
|
|
ISSB: issB,
|
|
WindowA: windowA,
|
|
WindowB: windowB,
|
|
InitStateA: tcp.StateEstablished,
|
|
InitStateB: tcp.StateEstablished,
|
|
Steps: []tcp.SegmentStep{
|
|
0: { // A sends FIN|ACK to B (RFC 9293 Figure 12 step 2).
|
|
Seg: tcp.Segment{SEQ: issA, ACK: issB, Flags: FINACK, WND: windowA},
|
|
Action: tcp.StepASends,
|
|
AState: tcp.StateFinWait1,
|
|
BState: tcp.StateCloseWait,
|
|
APending: nil,
|
|
BPending: &tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: tcp.FlagACK, WND: windowB},
|
|
},
|
|
1: { // B sends ACK to A (RFC 9293 Figure 12 step 3). B stays in CLOSE-WAIT.
|
|
Seg: tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: tcp.FlagACK, WND: windowB},
|
|
Action: tcp.StepBSends,
|
|
AState: tcp.StateFinWait2,
|
|
BState: tcp.StateCloseWait,
|
|
APending: nil, // RFC Figure 12 shows no response from A here — bare ACK must not elicit ACK.
|
|
},
|
|
2: { // B calls Close() (RFC 9293 Figure 12 step 4). B goes to LAST-ACK.
|
|
Action: tcp.StepBCloses,
|
|
AState: tcp.StateFinWait2,
|
|
BState: tcp.StateLastAck,
|
|
BPending: &tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: FINACK, WND: windowB},
|
|
},
|
|
3: { // B sends FIN|ACK to A (RFC 9293 Figure 12 step 4 cont).
|
|
Seg: tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: FINACK, WND: windowB},
|
|
Action: tcp.StepBSends,
|
|
AState: tcp.StateTimeWait,
|
|
BState: tcp.StateLastAck,
|
|
APending: &tcp.Segment{SEQ: issA + 1, ACK: issB + 1, Flags: tcp.FlagACK, WND: windowA},
|
|
BPending: nil,
|
|
},
|
|
4: { // A sends final ACK to B (RFC 9293 Figure 12 step 5).
|
|
Seg: tcp.Segment{SEQ: issA + 1, ACK: issB + 1, Flags: tcp.FlagACK, WND: windowA},
|
|
Action: tcp.StepASends,
|
|
AState: tcp.StateTimeWait,
|
|
BState: tcp.StateClosed,
|
|
APending: nil,
|
|
BPending: nil,
|
|
},
|
|
},
|
|
}
|
|
test.Run(t) // Runs both PeerA and PeerB subtests.
|
|
}
|
|
|
|
/*
|
|
Section 3.5 of RFC 9293: Basic 3-way handshake for connection synchronization.
|
|
TCP Peer A TCP Peer B
|
|
|
|
1. CLOSED LISTEN
|
|
|
|
2. SYN-SENT --> <SEQ=100><CTL=SYN> --> SYN-RECEIVED
|
|
|
|
3. ESTABLISHED <-- <SEQ=300><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED
|
|
|
|
4. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK> --> ESTABLISHED
|
|
|
|
5. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK><DATA> --> ESTABLISHED
|
|
*/
|
|
func TestExchangeTest_rfc9293_figure6(t *testing.T) {
|
|
const issA, issB, windowA, windowB = 100, 300, 1000, 1000
|
|
test := tcp.ExchangeTest{
|
|
ISSA: issA,
|
|
ISSB: issB,
|
|
WindowA: windowA,
|
|
WindowB: windowB,
|
|
InitStateA: tcp.StateSynSent,
|
|
InitStateB: tcp.StateListen,
|
|
Steps: []tcp.SegmentStep{
|
|
0: { // A sends SYN to B.
|
|
Seg: tcp.Segment{SEQ: issA, Flags: tcp.FlagSYN, WND: windowA},
|
|
Action: tcp.StepASends,
|
|
AState: tcp.StateSynSent,
|
|
BState: tcp.StateSynRcvd,
|
|
BPending: &tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: SYNACK, WND: windowB},
|
|
},
|
|
1: { // B sends SYNACK to A.
|
|
Seg: tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: SYNACK, WND: windowB},
|
|
Action: tcp.StepBSends,
|
|
AState: tcp.StateEstablished,
|
|
BState: tcp.StateSynRcvd,
|
|
APending: &tcp.Segment{SEQ: issA + 1, ACK: issB + 1, Flags: tcp.FlagACK, WND: windowA},
|
|
},
|
|
2: { // A sends ACK to B. Three-way handshake complete.
|
|
Seg: tcp.Segment{SEQ: issA + 1, ACK: issB + 1, Flags: tcp.FlagACK, WND: windowA},
|
|
Action: tcp.StepASends,
|
|
AState: tcp.StateEstablished,
|
|
BState: tcp.StateEstablished,
|
|
},
|
|
},
|
|
}
|
|
test.Run(t)
|
|
}
|
|
|
|
/*
|
|
Section 3.5 of RFC 9293: Simultaneous Connection Synchronization (SYN).
|
|
TCP Peer A TCP Peer B
|
|
|
|
1. CLOSED CLOSED
|
|
|
|
2. SYN-SENT --> <SEQ=100><CTL=SYN> ...
|
|
|
|
3. SYN-RECEIVED <-- <SEQ=300><CTL=SYN> <-- SYN-SENT
|
|
|
|
4. ... <SEQ=100><CTL=SYN> --> SYN-RECEIVED
|
|
|
|
5. SYN-RECEIVED --> <SEQ=100><ACK=301><CTL=SYN,ACK> ...
|
|
|
|
6. ESTABLISHED <-- <SEQ=300><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED
|
|
|
|
7. ... <SEQ=100><ACK=301><CTL=SYN,ACK> --> ESTABLISHED
|
|
*/
|
|
func TestExchangeTest_rfc9293_figure7(t *testing.T) {
|
|
const issA, issB, windowA, windowB = 100, 300, 1000, 1000
|
|
// NOTE: Simultaneous SYN can only be tested from one perspective because
|
|
// the "simultaneous" nature means segments cross in flight. In a sequential
|
|
// test, when B receives A's SYN first, B goes to SYN-RECEIVED, not SYN-SENT.
|
|
test := tcp.ExchangeTest{
|
|
ISSA: issA,
|
|
ISSB: issB,
|
|
WindowA: windowA,
|
|
WindowB: windowB,
|
|
InitStateA: tcp.StateSynSent,
|
|
InitStateB: tcp.StateSynSent,
|
|
Steps: []tcp.SegmentStep{
|
|
0: { // A sends SYN to B (crosses with B's SYN).
|
|
Seg: tcp.Segment{SEQ: issA, Flags: tcp.FlagSYN, WND: windowA},
|
|
Action: tcp.StepASends,
|
|
AState: tcp.StateSynSent,
|
|
},
|
|
1: { // A receives SYN from B (no ACK - B hasn't received A's SYN yet).
|
|
Seg: tcp.Segment{SEQ: issB, Flags: tcp.FlagSYN, WND: windowB},
|
|
Action: tcp.StepBSends,
|
|
AState: tcp.StateSynRcvd,
|
|
APending: &tcp.Segment{SEQ: issA, ACK: issB + 1, Flags: SYNACK, WND: windowA},
|
|
},
|
|
2: { // A sends SYNACK to B.
|
|
Seg: tcp.Segment{SEQ: issA, ACK: issB + 1, Flags: SYNACK, WND: windowA},
|
|
Action: tcp.StepASends,
|
|
AState: tcp.StateSynRcvd,
|
|
},
|
|
3: { // A receives SYNACK from B.
|
|
Seg: tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: SYNACK, WND: windowB},
|
|
Action: tcp.StepBSends,
|
|
AState: tcp.StateEstablished,
|
|
},
|
|
},
|
|
}
|
|
test.RunA(t) // Only A's perspective - B's is fundamentally different in sequential test.
|
|
}
|
|
|
|
/*
|
|
Recovery from Old Duplicate SYN
|
|
TCP Peer A TCP Peer B
|
|
|
|
1. CLOSED LISTEN
|
|
|
|
2. SYN-SENT --> <SEQ=100><CTL=SYN> ...
|
|
|
|
3. (duplicate) ... <SEQ=90><CTL=SYN> --> SYN-RECEIVED
|
|
|
|
4. SYN-SENT <-- <SEQ=300><ACK=91><CTL=SYN,ACK> <-- SYN-RECEIVED
|
|
|
|
5. SYN-SENT --> <SEQ=91><CTL=RST> --> LISTEN
|
|
|
|
6. ... <SEQ=100><CTL=SYN> --> SYN-RECEIVED
|
|
|
|
7. ESTABLISHED <-- <SEQ=400><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED
|
|
|
|
8. ESTABLISHED --> <SEQ=101><ACK=401><CTL=ACK> --> ESTABLISHED
|
|
|
|
NOTE: This test is asymmetric. A and B have different views because B receives an
|
|
old duplicate SYN that A never sent. Cannot use Run() - must test each perspective separately.
|
|
*/
|
|
func TestExchangeTest_rfc9293_figure8(t *testing.T) {
|
|
const issA, issB, windowA, windowB = 100, 300, 1000, 1000
|
|
const issAold = 90
|
|
const issBnew = 400
|
|
|
|
// Test from A's perspective: A sends SYN, gets wrong SYNACK, sends RST,
|
|
// gets correct SYNACK, sends ACK.
|
|
t.Run("PeerA", func(t *testing.T) {
|
|
var tcbA tcp.ControlBlock
|
|
tcbA.HelperInitState(tcp.StateSynSent, issA, issA, windowA)
|
|
|
|
stepsA := []tcp.SegmentStep{
|
|
0: { // A sends SYN (step 2 in figure).
|
|
Seg: tcp.Segment{SEQ: issA, Flags: tcp.FlagSYN, WND: windowA},
|
|
Action: tcp.StepASends,
|
|
AState: tcp.StateSynSent,
|
|
},
|
|
1: { // A receives SYNACK with wrong ACK (acking old duplicate SYN).
|
|
Seg: tcp.Segment{SEQ: issB, ACK: issAold + 1, Flags: SYNACK, WND: windowB},
|
|
Action: tcp.StepBSends,
|
|
AState: tcp.StateSynSent, // A stays in SYN-SENT because ACK is wrong.
|
|
APending: &tcp.Segment{SEQ: issAold + 1, Flags: tcp.FlagRST, WND: windowA},
|
|
},
|
|
2: { // A sends RST to reject the bad SYNACK.
|
|
Seg: tcp.Segment{SEQ: issAold + 1, Flags: tcp.FlagRST, WND: windowA},
|
|
Action: tcp.StepASends,
|
|
AState: tcp.StateSynSent,
|
|
},
|
|
3: { // A sends duplicate SYN (retransmit, step 6 arrival at B).
|
|
Seg: tcp.Segment{SEQ: issA, Flags: tcp.FlagSYN, WND: windowA},
|
|
Action: tcp.StepASends,
|
|
AState: tcp.StateSynSent,
|
|
},
|
|
4: { // A receives correct SYNACK from B with new ISS.
|
|
Seg: tcp.Segment{SEQ: issBnew, ACK: issA + 1, Flags: SYNACK, WND: windowB},
|
|
Action: tcp.StepBSends,
|
|
AState: tcp.StateEstablished,
|
|
APending: &tcp.Segment{SEQ: issA + 1, ACK: issBnew + 1, Flags: tcp.FlagACK, WND: windowA},
|
|
},
|
|
5: { // A sends ACK to complete handshake.
|
|
Seg: tcp.Segment{SEQ: issA + 1, ACK: issBnew + 1, Flags: tcp.FlagACK, WND: windowA},
|
|
Action: tcp.StepASends,
|
|
AState: tcp.StateEstablished,
|
|
},
|
|
}
|
|
tcbA.HelperSteps(t, stepsA, true)
|
|
})
|
|
|
|
// Test from B's perspective: B receives old SYN, sends SYNACK, receives RST,
|
|
// receives real SYN, sends new SYNACK, receives ACK.
|
|
t.Run("PeerB", func(t *testing.T) {
|
|
var tcbB tcp.ControlBlock
|
|
tcbB.HelperInitState(tcp.StateListen, issB, issB, windowB)
|
|
|
|
stepsB := []tcp.SegmentStep{
|
|
0: { // B receives old duplicate SYN (step 3).
|
|
Seg: tcp.Segment{SEQ: issAold, Flags: tcp.FlagSYN, WND: windowA},
|
|
Action: tcp.StepASends,
|
|
BState: tcp.StateSynRcvd,
|
|
BPending: &tcp.Segment{SEQ: issB, ACK: issAold + 1, Flags: SYNACK, WND: windowB},
|
|
},
|
|
1: { // B sends SYNACK for old SYN.
|
|
Seg: tcp.Segment{SEQ: issB, ACK: issAold + 1, Flags: SYNACK, WND: windowB},
|
|
Action: tcp.StepBSends,
|
|
BState: tcp.StateSynRcvd,
|
|
},
|
|
2: { // B receives RST, goes back to LISTEN.
|
|
Seg: tcp.Segment{SEQ: issAold + 1, Flags: tcp.FlagRST, WND: windowA},
|
|
Action: tcp.StepASends,
|
|
BState: tcp.StateListen,
|
|
},
|
|
3: { // B receives real SYN (step 6).
|
|
Seg: tcp.Segment{SEQ: issA, Flags: tcp.FlagSYN, WND: windowA},
|
|
Action: tcp.StepASends,
|
|
BState: tcp.StateSynRcvd,
|
|
BPending: &tcp.Segment{SEQ: issBnew, ACK: issA + 1, Flags: SYNACK, WND: windowB},
|
|
},
|
|
4: { // B sends new SYNACK.
|
|
Seg: tcp.Segment{SEQ: issBnew, ACK: issA + 1, Flags: SYNACK, WND: windowB},
|
|
Action: tcp.StepBSends,
|
|
BState: tcp.StateSynRcvd,
|
|
},
|
|
5: { // B receives ACK, connection established.
|
|
Seg: tcp.Segment{SEQ: issA + 1, ACK: issBnew + 1, Flags: tcp.FlagACK, WND: windowA},
|
|
Action: tcp.StepASends,
|
|
BState: tcp.StateEstablished,
|
|
},
|
|
}
|
|
tcbB.HelperSteps(t, stepsB, false)
|
|
})
|
|
}
|
|
|
|
/*
|
|
Figure 13: Simultaneous Close Sequence
|
|
TCP Peer A TCP Peer B
|
|
|
|
1. ESTABLISHED ESTABLISHED
|
|
|
|
2. (Close) (Close)
|
|
FIN-WAIT-1 --> <SEQ=100><ACK=300><CTL=FIN,ACK> ... FIN-WAIT-1
|
|
<-- <SEQ=300><ACK=100><CTL=FIN,ACK> <--
|
|
... <SEQ=100><ACK=300><CTL=FIN,ACK> -->
|
|
|
|
3. CLOSING --> <SEQ=101><ACK=301><CTL=ACK> ... CLOSING
|
|
<-- <SEQ=301><ACK=101><CTL=ACK> <--
|
|
... <SEQ=101><ACK=301><CTL=ACK> -->
|
|
|
|
4. TIME-WAIT TIME-WAIT
|
|
(2 MSL) (2 MSL)
|
|
CLOSED CLOSED
|
|
*/
|
|
func TestExchangeTest_rfc9293_figure13(t *testing.T) {
|
|
const issA, issB, windowA, windowB = 100, 300, 1000, 1000
|
|
// NOTE: Simultaneous close can only be tested from one perspective because
|
|
// the "simultaneous" nature means FINs cross in flight. In a sequential test,
|
|
// when B receives A's FIN first, B goes to CLOSE-WAIT, not CLOSING.
|
|
test := tcp.ExchangeTest{
|
|
ISSA: issA,
|
|
ISSB: issB,
|
|
WindowA: windowA,
|
|
WindowB: windowB,
|
|
InitStateA: tcp.StateEstablished,
|
|
InitStateB: tcp.StateEstablished,
|
|
Steps: []tcp.SegmentStep{
|
|
0: { // A sends FIN|ACK to B (crosses with B's FIN|ACK).
|
|
Seg: tcp.Segment{SEQ: issA, ACK: issB, Flags: FINACK, WND: windowA},
|
|
Action: tcp.StepASends,
|
|
AState: tcp.StateFinWait1,
|
|
},
|
|
1: { // A receives FIN|ACK from B (B sent before receiving A's FIN).
|
|
Seg: tcp.Segment{SEQ: issB, ACK: issA, Flags: FINACK, WND: windowB},
|
|
Action: tcp.StepBSends,
|
|
AState: tcp.StateClosing,
|
|
APending: &tcp.Segment{SEQ: issA + 1, ACK: issB + 1, Flags: tcp.FlagACK, WND: windowA},
|
|
},
|
|
2: { // A sends ACK to B.
|
|
Seg: tcp.Segment{SEQ: issA + 1, ACK: issB + 1, Flags: tcp.FlagACK, WND: windowA},
|
|
Action: tcp.StepASends,
|
|
AState: tcp.StateTimeWait,
|
|
},
|
|
},
|
|
}
|
|
test.RunA(t) // Only A's perspective - B's is fundamentally different in sequential test.
|
|
}
|
|
|
|
func TestResetEstablished(t *testing.T) {
|
|
var tcb tcp.ControlBlock
|
|
const windowA, windowB = 502, 4096
|
|
const issA, issB = 0x5e722b7d, 0xbe6e4c0f
|
|
tcb.HelperInitState(tcp.StateEstablished, issA, issA, windowA)
|
|
tcb.HelperInitRcv(issB, issB, windowB)
|
|
|
|
err := tcb.Recv(tcp.Segment{SEQ: issB, ACK: issA, Flags: tcp.FlagRST, WND: windowB})
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
if tcb.State() != tcp.StateClosed {
|
|
t.Error("expected closed state; got ", tcb.State().String())
|
|
}
|
|
checkNoPending(t, &tcb)
|
|
}
|
|
|
|
func TestFinackClose(t *testing.T) {
|
|
var tcb tcp.ControlBlock
|
|
const windowA, windowB = 502, 4096
|
|
const issA, issB = 100, 200
|
|
tcb.HelperInitState(tcp.StateEstablished, issA, issA, windowA)
|
|
tcb.HelperInitRcv(issB, issB, windowB)
|
|
// Start closing process.
|
|
err := tcb.Close()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
seg, ok := tcb.PendingSegment(0)
|
|
if !ok {
|
|
t.Fatal("expected pending segment")
|
|
}
|
|
if !seg.Flags.HasAll(tcp.FlagFIN | tcp.FlagACK) {
|
|
t.Fatalf("expected FIN|ACK; got %s", seg.Flags.String())
|
|
}
|
|
err = tcb.Send(seg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if tcb.State() != tcp.StateFinWait1 {
|
|
t.Fatalf("expected FinWait1; got %s", tcb.State().String())
|
|
}
|
|
// Special case where we receive FINACK all together, we can streamline and go into TimeWait.
|
|
err = tcb.Recv(tcp.Segment{
|
|
SEQ: issB,
|
|
ACK: issA + 1,
|
|
WND: windowB,
|
|
Flags: FINACK,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if tcb.State() != tcp.StateTimeWait {
|
|
t.Fatalf("expected TimeWait after FINACK; got %s", tcb.State().String())
|
|
}
|
|
}
|
|
|
|
func checkNoPending(t *testing.T, tcb *tcp.ControlBlock) bool {
|
|
t.Helper()
|
|
// We extensively test the API for inadvertent state modification in a HasPending or PendingSegment call.
|
|
hasPD := tcb.HasPending()
|
|
pd, ok := tcb.PendingSegment(0)
|
|
hasPD2 := tcb.HasPending()
|
|
if hasPD || ok || hasPD2 {
|
|
t.Errorf("unexpected pending segment: %+v (%v,%v,%v)", pd, hasPD, ok, hasPD2)
|
|
return false
|
|
}
|
|
if hasPD != ok || hasPD != hasPD2 {
|
|
t.Fatalf("inconsistent pending segment: (%v,%v,%v)", hasPD, ok, hasPD2)
|
|
}
|
|
if !ok && pd != (tcp.Segment{}) {
|
|
t.Fatalf("inconsistent pending segment: %+v (%v,%v,%v)", pd, hasPD, ok, hasPD2)
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Full client-server interaction in the sending of "hello world" over TCP in order.
|
|
var exchangeHelloWorld = [][]byte{
|
|
// client SYN1
|
|
0: []byte("\x28\xcd\xc1\x05\x4d\xbb\xd8\x5e\xd3\x43\x03\xeb\x08\x00\x45\x00\x00\x3c\x71\xac\x40\x00\x40\x06\x44\x9b\xc0\xa8\x01\x93\xc0\xa8\x01\x91\x84\x96\x04\xd2\x5e\x72\x2b\x7d\x00\x00\x00\x00\xa0\x02\xfa\xf0\x27\x6d\x00\x00\x02\x04\x05\xb4\x04\x02\x08\x0a\x07\x8b\x86\x4a\x00\x00\x00\x00\x01\x03\x03\x07"),
|
|
// server SYNACK
|
|
1: []byte("\xd8\x5e\xd3\x43\x03\xeb\x28\xcd\xc1\x05\x4d\xbb\x08\x00\x45\x00\x00\x34\x00\x00\x40\x00\x40\x06\xb6\x4f\xc0\xa8\x01\x91\xc0\xa8\x01\x93\x04\xd2\x84\x96\xbe\x6e\x4c\x0f\x5e\x72\x2b\x7e\x80\x12\x10\x00\xc0\xbb\x00\x00\x02\x04\x05\xb4\x03\x03\x00\x04\x02\x00\x00\x00"),
|
|
// client ACK1
|
|
2: []byte("\x28\xcd\xc1\x05\x4d\xbb\xd8\x5e\xd3\x43\x03\xeb\x08\x00\x45\x00\x00\x28\x71\xad\x40\x00\x40\x06\x44\xae\xc0\xa8\x01\x93\xc0\xa8\x01\x91\x84\x96\x04\xd2\x5e\x72\x2b\x7e\xbe\x6e\x4c\x10\x50\x10\x01\xf6\x0b\x92\x00\x00"),
|
|
// client PSHACK0
|
|
3: []byte("\x28\xcd\xc1\x05\x4d\xbb\xd8\x5e\xd3\x43\x03\xeb\x08\x00\x45\x00\x00\x34\x71\xae\x40\x00\x40\x06\x44\xa1\xc0\xa8\x01\x93\xc0\xa8\x01\x91\x84\x96\x04\xd2\x5e\x72\x2b\x7e\xbe\x6e\x4c\x10\x50\x18\x01\xf6\x79\xa5\x00\x00\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x0a"),
|
|
// server ACK1
|
|
4: []byte("\xd8\x5e\xd3\x43\x03\xeb\x28\xcd\xc1\x05\x4d\xbb\x08\x00\x45\x00\x00\x28\x00\x00\x40\x00\x40\x06\xb6\x5b\xc0\xa8\x01\x91\xc0\xa8\x01\x93\x04\xd2\x84\x96\xbe\x6e\x4c\x10\x5e\x72\x2b\x8a\x50\x10\x0f\xf4\xfd\x87\x00\x00\x00\x00\x00\x00\x00\x00"),
|
|
// server PSHACK1
|
|
5: []byte("\xd8\x5e\xd3\x43\x03\xeb\x28\xcd\xc1\x05\x4d\xbb\x08\x00\x45\x00\x00\x34\x00\x00\x40\x00\x40\x06\xb6\x4f\xc0\xa8\x01\x91\xc0\xa8\x01\x93\x04\xd2\x84\x96\xbe\x6e\x4c\x10\x5e\x72\x2b\x8a\x50\x18\x10\x00\x6b\x8f\x00\x00\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x0a"),
|
|
// client ACK2
|
|
6: []byte("\x28\xcd\xc1\x05\x4d\xbb\xd8\x5e\xd3\x43\x03\xeb\x08\x00\x45\x00\x00\x28\x71\xaf\x40\x00\x40\x06\x44\xac\xc0\xa8\x01\x93\xc0\xa8\x01\x91\x84\x96\x04\xd2\x5e\x72\x2b\x8a\xbe\x6e\x4c\x1c\x50\x10\x01\xf6\x0b\x7a\x00\x00"),
|
|
// client PSHACK1
|
|
7: []byte("\x28\xcd\xc1\x05\x4d\xbb\xd8\x5e\xd3\x43\x03\xeb\x08\x00\x45\x00\x00\x34\x71\xb0\x40\x00\x40\x06\x44\x9f\xc0\xa8\x01\x93\xc0\xa8\x01\x91\x84\x96\x04\xd2\x5e\x72\x2b\x8a\xbe\x6e\x4c\x1c\x50\x18\x01\xf6\x79\x8d\x00\x00\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x0a"),
|
|
// server PSHACK2
|
|
8: []byte("\xd8\x5e\xd3\x43\x03\xeb\x28\xcd\xc1\x05\x4d\xbb\x08\x00\x45\x00\x00\x34\x00\x00\x40\x00\x40\x06\xb6\x4f\xc0\xa8\x01\x91\xc0\xa8\x01\x93\x04\xd2\x84\x96\xbe\x6e\x4c\x1c\x5e\x72\x2b\x96\x50\x18\x10\x00\x6b\x77\x00\x00\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x0a"),
|
|
// client ACK3
|
|
9: []byte("\x28\xcd\xc1\x05\x4d\xbb\xd8\x5e\xd3\x43\x03\xeb\x08\x00\x45\x00\x00\x28\x71\xb1\x40\x00\x40\x06\x44\xaa\xc0\xa8\x01\x93\xc0\xa8\x01\x91\x84\x96\x04\xd2\x5e\x72\x2b\x96\xbe\x6e\x4c\x28\x50\x10\x01\xf6\x0b\x62\x00\x00"),
|
|
// client FINACK
|
|
10: []byte("\x28\xcd\xc1\x05\x4d\xbb\xd8\x5e\xd3\x43\x03\xeb\x08\x00\x45\x00\x00\x28\x71\xb2\x40\x00\x40\x06\x44\xa9\xc0\xa8\x01\x93\xc0\xa8\x01\x91\x84\x96\x04\xd2\x5e\x72\x2b\x96\xbe\x6e\x4c\x28\x50\x11\x01\xf6\x0b\x61\x00\x00"),
|
|
// server ACK
|
|
11: []byte("\xd8\x5e\xd3\x43\x03\xeb\x28\xcd\xc1\x05\x4d\xbb\x08\x00\x45\x00\x00\x28\x00\x00\x40\x00\x40\x06\xb6\x5b\xc0\xa8\x01\x91\xc0\xa8\x01\x93\x04\xd2\x84\x96\xbe\x6e\x4c\x28\x5e\x72\x2b\x97\x50\x10\x10\x00\xfd\x56\x00\x00\x00\x00\x00\x00\x00\x00"),
|
|
}
|
|
|
|
// This corresponds to https://github.com/soypat/seqs/issues/19
|
|
// The bug consisted of a panic condition encountered when using wget client with a seqs based server.
|
|
// Thanks to @knieriem for finding this and the detailed report they submitted.
|
|
func TestIssue19(t *testing.T) {
|
|
var tcb tcp.ControlBlock
|
|
assertState := func(state tcp.State) {
|
|
t.Helper()
|
|
if tcb.State() != state {
|
|
t.Fatalf("want state %s; got %s", state.String(), tcb.State().String())
|
|
}
|
|
}
|
|
const httpLen = 1192
|
|
const issA, issB, windowA, windowB = 1, 0, 2000, 2000
|
|
tcb.HelperInitState(tcp.StateEstablished, issA, issA, windowA)
|
|
tcb.HelperInitRcv(issB, issB, windowB)
|
|
|
|
// Send out HTTP request and close connection.
|
|
err := tcb.Send(tcp.Segment{SEQ: issA, ACK: issB, Flags: PSHACK, WND: windowA, DATALEN: httpLen})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = tcb.Close()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assertState(tcp.StateEstablished)
|
|
|
|
pending, ok := tcb.PendingSegment(0)
|
|
if !ok {
|
|
t.Fatal("expected pending segment")
|
|
} else if pending.Flags != FINACK {
|
|
t.Fatalf("expected FINACK; got %s", pending.Flags.String())
|
|
}
|
|
|
|
// Receive ACK of HTTP segment.
|
|
err = tcb.Recv(tcp.Segment{SEQ: issB, ACK: issA + httpLen, Flags: tcp.FlagACK, WND: windowB})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assertState(tcp.StateEstablished)
|
|
err = tcb.Close()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
pending, ok = tcb.PendingSegment(0)
|
|
if !ok {
|
|
t.Fatal("expected pending segment")
|
|
} else if pending.Flags != FINACK {
|
|
t.Fatalf("expected FINACK; got %s", pending.Flags.String())
|
|
}
|
|
|
|
// Send out FINACK.
|
|
err = tcb.Send(pending)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assertState(tcp.StateFinWait1)
|
|
pending, ok = tcb.PendingSegment(0)
|
|
if ok {
|
|
t.Fatal("expected no pending segment after finack")
|
|
}
|
|
|
|
// Receive FINACK response from client.
|
|
err = tcb.Recv(tcp.Segment{SEQ: issB, ACK: issA + httpLen, Flags: FINACK, WND: windowB})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assertState(tcp.StateClosing)
|
|
pending, ok = tcb.PendingSegment(0)
|
|
if !ok {
|
|
t.Fatal("expected pending segment")
|
|
} else if pending.Flags != tcp.FlagACK {
|
|
t.Fatalf("expected ACK; got %s", pending.Flags.String())
|
|
}
|
|
|
|
// Before responding we receive an ACK from client. This is where panic is triggered.
|
|
err = tcb.Recv(tcp.Segment{SEQ: issB + 1, ACK: issA + httpLen + 1, Flags: tcp.FlagACK, WND: windowB})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assertState(tcp.StateTimeWait)
|
|
|
|
// Check we still need to send an ACK.
|
|
pending, ok = tcb.PendingSegment(0)
|
|
if !ok {
|
|
t.Fatal("expected pending segment")
|
|
} else if pending.Flags != tcp.FlagACK {
|
|
t.Fatalf("expected ACK; got %s", pending.Flags.String())
|
|
}
|
|
// Prepare response to client.
|
|
err = tcb.Send(pending)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// TestRcvFinWait2 tests rcvFinWait2 behavior per RFC 9293 §3.10.7.4.
|
|
// In FIN-WAIT-2, the local side has sent FIN and received its ACK.
|
|
// The remote side has NOT closed yet and may still send data or ACKs.
|
|
// TestFinWait1_PartialACK_StaysInFinWait1 tests that receiving an ACK in FIN-WAIT-1
|
|
// that acknowledges data but NOT the FIN does not transition to FIN-WAIT-2.
|
|
// This is a regression test for a bug observed in monitor.log where lneto sent
|
|
// data followed by FIN, received a stale ACK covering only the data, and
|
|
// incorrectly entered FIN-WAIT-2. Per RFC 9293 §3.10.7.4 step 5, FIN-WAIT-2
|
|
// is entered only "if the FIN segment is now acknowledged."
|
|
func TestFinWait1_PartialACK_StaysInFinWait1(t *testing.T) {
|
|
const windowA, windowB = 1024, 64240
|
|
const issA, issB = 100, 300
|
|
const dataLen = 192
|
|
|
|
var tcb tcp.ControlBlock
|
|
tcb.HelperInitState(tcp.StateEstablished, issA, issA, windowA)
|
|
tcb.HelperInitRcv(issB, issB, windowB)
|
|
|
|
// Send data segment (like MQTT publish).
|
|
err := tcb.Send(tcp.Segment{SEQ: issA, ACK: issB, Flags: PSHACK, WND: windowA, DATALEN: dataLen})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Close connection — queues FIN.
|
|
err = tcb.Close()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Send the pending FIN|ACK.
|
|
seg, ok := tcb.PendingSegment(0)
|
|
if !ok {
|
|
t.Fatal("expected pending FIN|ACK")
|
|
}
|
|
if !seg.Flags.HasAll(FINACK) {
|
|
t.Fatalf("expected FIN|ACK; got %s", seg.Flags)
|
|
}
|
|
err = tcb.Send(seg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if tcb.State() != tcp.StateFinWait1 {
|
|
t.Fatalf("expected FIN-WAIT-1; got %s", tcb.State())
|
|
}
|
|
// snd.NXT is now issA + dataLen + 1 (data + FIN).
|
|
|
|
// Receive ACK that covers the data but NOT the FIN (seg.ack = issA + dataLen).
|
|
// This is what the remote sends when it received the data but the FIN was lost.
|
|
err = tcb.Recv(tcp.Segment{SEQ: issB, ACK: tcp.Value(issA + dataLen), Flags: tcp.FlagACK, WND: windowB})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Bug: old code transitions to FIN-WAIT-2 here.
|
|
// Correct: must stay in FIN-WAIT-1 because FIN is not yet acknowledged.
|
|
if tcb.State() != tcp.StateFinWait1 {
|
|
t.Fatalf("expected FIN-WAIT-1 (FIN not yet ACKed); got %s", tcb.State())
|
|
}
|
|
|
|
// Now receive ACK that covers the FIN (seg.ack = issA + dataLen + 1).
|
|
err = tcb.Recv(tcp.Segment{SEQ: issB, ACK: tcp.Value(issA + dataLen + 1), Flags: tcp.FlagACK, WND: windowB})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if tcb.State() != tcp.StateFinWait2 {
|
|
t.Fatalf("expected FIN-WAIT-2 (FIN now ACKed); got %s", tcb.State())
|
|
}
|
|
}
|
|
|
|
func TestRcvFinWait2(t *testing.T) {
|
|
const windowA, windowB = 1000, 1000
|
|
const issA, issB = 100, 300
|
|
|
|
// enterFinWait2 returns a ControlBlock in FIN-WAIT-2 with no pending flags.
|
|
// Simulates: A sent FIN|ACK, B acknowledged it, A drained pending ACK.
|
|
enterFinWait2 := func(t *testing.T) tcp.ControlBlock {
|
|
t.Helper()
|
|
var tcb tcp.ControlBlock
|
|
tcb.HelperInitState(tcp.StateEstablished, issA, issA, windowA)
|
|
tcb.HelperInitRcv(issB, issB, windowB)
|
|
if err := tcb.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
seg, _ := tcb.PendingSegment(0)
|
|
if err := tcb.Send(seg); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := tcb.Recv(tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: tcp.FlagACK, WND: windowB}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if tcb.State() != tcp.StateFinWait2 {
|
|
t.Fatalf("setup: want FIN-WAIT-2; got %s", tcb.State())
|
|
}
|
|
// Drain pending ACK left from rcvFinWait1 so subtests start clean.
|
|
if seg, ok := tcb.PendingSegment(0); ok {
|
|
if err := tcb.Send(seg); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
return tcb
|
|
}
|
|
|
|
// Bare ACK: no state change, no pending flags generated.
|
|
t.Run("BareACK", func(t *testing.T) {
|
|
tcb := enterFinWait2(t)
|
|
err := tcb.Recv(tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: tcp.FlagACK, WND: windowB})
|
|
if err != nil {
|
|
t.Fatalf("bare ACK rejected: %v", err)
|
|
}
|
|
if tcb.State() != tcp.StateFinWait2 {
|
|
t.Fatalf("want FIN-WAIT-2; got %s", tcb.State())
|
|
}
|
|
checkNoPending(t, &tcb)
|
|
})
|
|
|
|
// In-window data: ACK generated, RCV.NXT advanced by payload length.
|
|
t.Run("InWindowData", func(t *testing.T) {
|
|
tcb := enterFinWait2(t)
|
|
rcvNxtBefore := tcb.RecvNext()
|
|
const dataLen = 50
|
|
err := tcb.Recv(tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: PSHACK, WND: windowB, DATALEN: dataLen})
|
|
if err != nil {
|
|
t.Fatalf("in-window data rejected: %v", err)
|
|
}
|
|
if tcb.State() != tcp.StateFinWait2 {
|
|
t.Fatalf("want FIN-WAIT-2; got %s", tcb.State())
|
|
}
|
|
seg, ok := tcb.PendingSegment(0)
|
|
if !ok {
|
|
t.Fatal("expected ACK pending for received data")
|
|
}
|
|
if !seg.Flags.HasAll(tcp.FlagACK) {
|
|
t.Fatalf("pending flags: want ACK; got %s", seg.Flags)
|
|
}
|
|
if seg.ACK != rcvNxtBefore+dataLen {
|
|
t.Fatalf("pending ACK: want %d; got %d", rcvNxtBefore+dataLen, seg.ACK)
|
|
}
|
|
if tcb.RecvNext() != rcvNxtBefore+dataLen {
|
|
t.Fatalf("RCV.NXT: want %d; got %d", rcvNxtBefore+dataLen, tcb.RecvNext())
|
|
}
|
|
})
|
|
|
|
// FIN (without ACK): transition to TIME-WAIT, ACK generated, RCV.NXT advanced by 1.
|
|
t.Run("FIN", func(t *testing.T) {
|
|
tcb := enterFinWait2(t)
|
|
rcvNxtBefore := tcb.RecvNext()
|
|
// Bare FIN without ACK — tests that HasAny(FlagFIN) is used, not HasAll(finack).
|
|
err := tcb.Recv(tcp.Segment{SEQ: issB, Flags: tcp.FlagFIN, WND: windowB})
|
|
if err != nil {
|
|
t.Fatalf("FIN rejected: %v", err)
|
|
}
|
|
if tcb.State() != tcp.StateTimeWait {
|
|
t.Fatalf("want TIME-WAIT; got %s", tcb.State())
|
|
}
|
|
seg, ok := tcb.PendingSegment(0)
|
|
if !ok {
|
|
t.Fatal("expected ACK pending for FIN")
|
|
}
|
|
if !seg.Flags.HasAll(tcp.FlagACK) {
|
|
t.Fatalf("pending flags: want ACK; got %s", seg.Flags)
|
|
}
|
|
if seg.ACK != rcvNxtBefore+1 {
|
|
t.Fatalf("pending ACK: want %d; got %d", rcvNxtBefore+1, seg.ACK)
|
|
}
|
|
if tcb.RecvNext() != rcvNxtBefore+1 {
|
|
t.Fatalf("RCV.NXT: want %d; got %d", rcvNxtBefore+1, tcb.RecvNext())
|
|
}
|
|
})
|
|
}
|
|
|
|
func FuzzTCBActions(f *testing.F) {
|
|
const mtu = 2048
|
|
const (
|
|
actionRecv = iota
|
|
actionSend
|
|
actionClose
|
|
actionMax
|
|
)
|
|
f.Add(
|
|
int64(0x2313_2313),
|
|
[]byte{actionSend, actionRecv, actionSend, actionRecv, actionSend, actionRecv},
|
|
)
|
|
f.Add(
|
|
int64(0x2fefe_feefe),
|
|
[]byte{actionSend, actionRecv, actionSend, actionClose, actionSend, actionRecv},
|
|
)
|
|
f.Add(
|
|
int64(0x2fefe_feefe),
|
|
[]byte{actionClose, actionRecv, actionSend, actionClose, actionSend, actionRecv},
|
|
)
|
|
recvsendSize := func(rng *rand.Rand) int {
|
|
return rng.Int() % mtu
|
|
}
|
|
f.Fuzz(func(t *testing.T, seed int64, actions []byte) {
|
|
if len(actions) == 0 || len(actions) > 100 {
|
|
t.SkipNow()
|
|
}
|
|
rng := rand.New(rand.NewSource(seed))
|
|
var clientISS tcp.Value = tcp.Value(rng.Int31())
|
|
var serverISS tcp.Value = tcp.Value(rng.Int31())
|
|
|
|
var client tcp.ControlBlock
|
|
client.HelperInitState(tcp.StateEstablished, clientISS, clientISS, mtu)
|
|
client.HelperInitRcv(serverISS, serverISS, mtu)
|
|
|
|
var server tcp.ControlBlock
|
|
server.HelperInitState(tcp.StateEstablished, serverISS, serverISS, mtu)
|
|
server.HelperInitRcv(clientISS, clientISS, mtu)
|
|
var closeCalled bool
|
|
// logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
|
// Level: slog.LevelDebug - 2,
|
|
// }))
|
|
// client.SetLogger(logger.WithGroup("client"))
|
|
// server.SetLogger(logger.WithGroup("server"))
|
|
// var exchanges []tcp.Exchange
|
|
// hasPanicked := true
|
|
// defer func() {
|
|
// if hasPanicked {
|
|
// for _, ex := range exchanges {
|
|
// t.Log(ex.RFC9293String(tcp.StateEstablished, tcp.StateEstablished))
|
|
// }
|
|
// }
|
|
// }()
|
|
for _, action := range actions {
|
|
v := recvsendSize(rng)
|
|
switch action % actionMax {
|
|
case actionSend:
|
|
seg, ok := client.PendingSegment(v % mtu)
|
|
if ok {
|
|
// exchanges = append(exchanges, tcp.Exchange{Outgoing: &seg})
|
|
err := client.Send(seg)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
err = server.Recv(seg)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
case actionRecv:
|
|
seg, ok := server.PendingSegment(v % mtu)
|
|
if ok {
|
|
// exchanges = append(exchanges, tcp.Exchange{Incoming: &seg})
|
|
err := server.Send(seg)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
err = client.Recv(seg)
|
|
if err != nil && !closeCalled {
|
|
panic(err)
|
|
}
|
|
}
|
|
case actionClose:
|
|
err := client.Close()
|
|
if err != nil && !closeCalled {
|
|
panic(err)
|
|
}
|
|
closeCalled = true
|
|
return
|
|
}
|
|
}
|
|
// hasPanicked = false
|
|
})
|
|
}
|
|
|
|
func TestFlagsString(t *testing.T) {
|
|
tests := []struct {
|
|
flags tcp.Flags
|
|
want string
|
|
}{
|
|
{flags: 0, want: "[]"},
|
|
{flags: tcp.FlagSYN, want: "[SYN]"},
|
|
{flags: tcp.FlagACK, want: "[ACK]"},
|
|
{flags: tcp.FlagFIN, want: "[FIN]"},
|
|
{flags: tcp.FlagRST, want: "[RST]"},
|
|
{flags: tcp.FlagSYN | tcp.FlagACK, want: "[SYN,ACK]"},
|
|
{flags: tcp.FlagFIN | tcp.FlagACK, want: "[FIN,ACK]"},
|
|
{flags: tcp.FlagPSH | tcp.FlagACK, want: "[PSH,ACK]"},
|
|
// Multi-flag combinations.
|
|
{flags: tcp.FlagFIN | tcp.FlagSYN | tcp.FlagACK, want: "[FIN,SYN,ACK]"},
|
|
{flags: tcp.FlagURG | tcp.FlagACK, want: "[ACK,URG]"},
|
|
{flags: tcp.FlagECE | tcp.FlagCWR, want: "[ECE,CWR]"},
|
|
{flags: tcp.FlagNS | tcp.FlagACK, want: "[ACK,NS ]"},
|
|
// All flags set.
|
|
{flags: tcp.FlagFIN | tcp.FlagSYN | tcp.FlagRST | tcp.FlagPSH | tcp.FlagACK | tcp.FlagURG | tcp.FlagECE | tcp.FlagCWR | tcp.FlagNS, want: "[FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,NS ]"},
|
|
// Invalid flags (bits above mask).
|
|
{flags: 0xFFFF, want: "<invalid TCP flags>"},
|
|
}
|
|
for _, tc := range tests {
|
|
got := tc.flags.String()
|
|
if got != tc.want {
|
|
t.Errorf("Flags(%#x).String() = %q; want %q", uint16(tc.flags), got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFlagsAppendFormat(t *testing.T) {
|
|
tests := []struct {
|
|
flags tcp.Flags
|
|
want string
|
|
}{
|
|
{flags: 0, want: ""},
|
|
{flags: tcp.FlagSYN, want: "SYN"},
|
|
{flags: tcp.FlagACK, want: "ACK"},
|
|
{flags: tcp.FlagFIN, want: "FIN"},
|
|
{flags: tcp.FlagRST, want: "RST"},
|
|
{flags: tcp.FlagSYN | tcp.FlagACK, want: "SYN,ACK"},
|
|
{flags: tcp.FlagFIN | tcp.FlagACK, want: "FIN,ACK"},
|
|
{flags: tcp.FlagPSH | tcp.FlagACK, want: "PSH,ACK"},
|
|
{flags: tcp.FlagFIN | tcp.FlagSYN | tcp.FlagACK, want: "FIN,SYN,ACK"},
|
|
{flags: tcp.FlagURG | tcp.FlagACK, want: "ACK,URG"},
|
|
{flags: tcp.FlagECE | tcp.FlagCWR, want: "ECE,CWR"},
|
|
{flags: tcp.FlagNS | tcp.FlagACK, want: "ACK,NS "},
|
|
{flags: tcp.FlagFIN | tcp.FlagSYN | tcp.FlagRST | tcp.FlagPSH | tcp.FlagACK | tcp.FlagURG | tcp.FlagECE | tcp.FlagCWR | tcp.FlagNS, want: "FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,NS "},
|
|
{flags: 0xFFFF, want: "<invalid TCP flags>"},
|
|
}
|
|
for _, tc := range tests {
|
|
got := string(tc.flags.AppendFormat(nil))
|
|
if got != tc.want {
|
|
t.Errorf("Flags(%#x).AppendFormat(nil) = %q; want %q", uint16(tc.flags), got, tc.want)
|
|
}
|
|
}
|
|
|
|
// Test that AppendFormat appends to existing data.
|
|
prefix := []byte("flags=")
|
|
got := string(SYNACK.AppendFormat(prefix))
|
|
if want := "flags=SYN,ACK"; got != want {
|
|
t.Errorf("AppendFormat with prefix = %q; want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestFlagsStringAppendFormatConsistency(t *testing.T) {
|
|
// Verify String() and AppendFormat() produce consistent results
|
|
// for all single-flag and common multi-flag values.
|
|
allFlags := []tcp.Flags{
|
|
0,
|
|
tcp.FlagFIN, tcp.FlagSYN, tcp.FlagRST, tcp.FlagPSH,
|
|
tcp.FlagACK, tcp.FlagURG, tcp.FlagECE, tcp.FlagCWR, tcp.FlagNS,
|
|
SYNACK, FINACK, PSHACK,
|
|
tcp.FlagFIN | tcp.FlagSYN | tcp.FlagRST | tcp.FlagPSH | tcp.FlagACK | tcp.FlagURG | tcp.FlagECE | tcp.FlagCWR | tcp.FlagNS,
|
|
}
|
|
for _, flags := range allFlags {
|
|
str := flags.String()
|
|
appended := string(flags.AppendFormat(nil))
|
|
// String() wraps in brackets, AppendFormat() does not.
|
|
if flags == 0 {
|
|
if str != "[]" {
|
|
t.Errorf("Flags(0).String() = %q; want %q", str, "[]")
|
|
}
|
|
if appended != "" {
|
|
t.Errorf("Flags(0).AppendFormat() = %q; want empty", appended)
|
|
}
|
|
continue
|
|
}
|
|
wantStr := "[" + appended + "]"
|
|
if str != wantStr {
|
|
t.Errorf("Flags(%#x): String()=%q inconsistent with AppendFormat()=%q (want %q)", uint16(flags), str, appended, wantStr)
|
|
}
|
|
}
|
|
}
|