bcap/tcp_test.go
2026-03-24 23:39:55 +08:00

748 lines
19 KiB
Go

package bcap
import (
"testing"
"time"
"github.com/gopacket/gopacket"
)
func TestObserveTCPStateTransitions(t *testing.T) {
analyzer := newTestAnalyzer()
defer analyzer.Stop()
base := time.Unix(1700000000, 0)
cases := []struct {
name string
packet func(time.Time) observedPacket
want uint8
wantLen int
}{
{
name: "syn",
packet: func(ts time.Time) observedPacket {
return mustObservePacket(t, analyzer, mustBuildTCPPacket(t, ts, tcpPacketSpec{
srcIP: "10.0.0.1",
dstIP: "10.0.0.2",
srcPort: 40000,
dstPort: 3306,
seq: 100,
syn: true,
window: 4096,
}))
},
want: StateTcpConnect1,
wantLen: 0,
},
{
name: "synack",
packet: func(ts time.Time) observedPacket {
return mustObservePacket(t, analyzer, mustBuildTCPPacket(t, ts, tcpPacketSpec{
srcIP: "10.0.0.2",
dstIP: "10.0.0.1",
srcPort: 3306,
dstPort: 40000,
seq: 500,
ack: 101,
syn: true,
ackFlag: true,
window: 4096,
}))
},
want: StateTcpConnect2,
wantLen: 0,
},
{
name: "ack",
packet: func(ts time.Time) observedPacket {
return mustObservePacket(t, analyzer, mustBuildTCPPacket(t, ts, tcpPacketSpec{
srcIP: "10.0.0.1",
dstIP: "10.0.0.2",
srcPort: 40000,
dstPort: 3306,
seq: 101,
ack: 501,
ackFlag: true,
window: 4096,
}))
},
want: StateTcpConnect3,
wantLen: 0,
},
{
name: "server-ack",
packet: func(ts time.Time) observedPacket {
return mustObservePacket(t, analyzer, mustBuildTCPPacket(t, ts, tcpPacketSpec{
srcIP: "10.0.0.2",
dstIP: "10.0.0.1",
srcPort: 3306,
dstPort: 40000,
seq: 501,
ack: 101,
ackFlag: true,
window: 4096,
}))
},
want: StateTcpAckOk,
wantLen: 0,
},
{
name: "client-data",
packet: func(ts time.Time) observedPacket {
return mustObservePacket(t, analyzer, mustBuildTCPPacket(t, ts, tcpPacketSpec{
srcIP: "10.0.0.1",
dstIP: "10.0.0.2",
srcPort: 40000,
dstPort: 3306,
seq: 101,
ack: 501,
ackFlag: true,
window: 4096,
payload: []byte("hello"),
}))
},
want: StateTcpAckOk,
wantLen: 5,
},
{
name: "retransmission",
packet: func(ts time.Time) observedPacket {
return mustObservePacket(t, analyzer, mustBuildTCPPacket(t, ts, tcpPacketSpec{
srcIP: "10.0.0.1",
dstIP: "10.0.0.2",
srcPort: 40000,
dstPort: 3306,
seq: 101,
ack: 501,
ackFlag: true,
window: 4096,
payload: []byte("hello"),
}))
},
want: StateTcpRetransmit,
wantLen: 5,
},
{
name: "server-ack-after-data",
packet: func(ts time.Time) observedPacket {
return mustObservePacket(t, analyzer, mustBuildTCPPacket(t, ts, tcpPacketSpec{
srcIP: "10.0.0.2",
dstIP: "10.0.0.1",
srcPort: 3306,
dstPort: 40000,
seq: 501,
ack: 106,
ackFlag: true,
window: 4096,
}))
},
want: StateTcpAckOk,
wantLen: 0,
},
{
name: "keepalive",
packet: func(ts time.Time) observedPacket {
return mustObservePacket(t, analyzer, mustBuildTCPPacket(t, ts, tcpPacketSpec{
srcIP: "10.0.0.1",
dstIP: "10.0.0.2",
srcPort: 40000,
dstPort: 3306,
seq: 105,
ack: 501,
ackFlag: true,
window: 4096,
}))
},
want: StateTcpKeepalive,
wantLen: 0,
},
}
for i, tc := range cases {
info := tc.packet(base.Add(time.Duration(i) * time.Millisecond))
if info.StateDescript() != tc.want {
t.Fatalf("%s: state = %d, want %d", tc.name, info.StateDescript(), tc.want)
}
if info.TcpPayloads() != tc.wantLen {
t.Fatalf("%s: payload len = %d, want %d", tc.name, info.TcpPayloads(), tc.wantLen)
}
}
}
func TestObserveTCPRepeatedKeepaliveWithoutReverseResponse(t *testing.T) {
analyzer := newTestAnalyzer()
defer analyzer.Stop()
base := time.Unix(1700000500, 0)
first := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base, tcpPacketSpec{
srcIP: "122.210.105.240",
dstIP: "122.210.110.99",
srcPort: 3306,
dstPort: 60818,
seq: 172126745,
ack: 2951532891,
ackFlag: true,
window: 258,
}))
if first.StateDescript() != StateTcpAckOk {
t.Fatalf("first probe state = %d, want %d", first.StateDescript(), StateTcpAckOk)
}
second := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(75*time.Second), tcpPacketSpec{
srcIP: "122.210.105.240",
dstIP: "122.210.110.99",
srcPort: 3306,
dstPort: 60818,
seq: 172126745,
ack: 2951532891,
ackFlag: true,
window: 258,
}))
if second.StateDescript() != StateTcpKeepalive {
t.Fatalf("second probe state = %d, want %d", second.StateDescript(), StateTcpKeepalive)
}
third := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(150*time.Second), tcpPacketSpec{
srcIP: "122.210.105.240",
dstIP: "122.210.110.99",
srcPort: 3306,
dstPort: 60818,
seq: 172126745,
ack: 2951532891,
ackFlag: true,
window: 258,
}))
if third.StateDescript() != StateTcpKeepalive {
t.Fatalf("third probe state = %d, want %d", third.StateDescript(), StateTcpKeepalive)
}
}
func TestObserveTCPKeepaliveResponseAck(t *testing.T) {
analyzer := newTestAnalyzer()
defer analyzer.Stop()
base := time.Unix(1700000530, 0)
_ = mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base, tcpPacketSpec{
srcIP: "122.210.105.240",
dstIP: "122.210.110.99",
srcPort: 3306,
dstPort: 60818,
seq: 172126745,
ack: 2951532891,
ackFlag: true,
window: 258,
}))
probe := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(75*time.Second), tcpPacketSpec{
srcIP: "122.210.105.240",
dstIP: "122.210.110.99",
srcPort: 3306,
dstPort: 60818,
seq: 172126745,
ack: 2951532891,
ackFlag: true,
window: 258,
}))
if probe.StateDescript() != StateTcpKeepalive {
t.Fatalf("probe state = %d, want %d", probe.StateDescript(), StateTcpKeepalive)
}
resp := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(75*time.Second+50*time.Millisecond), tcpPacketSpec{
srcIP: "122.210.110.99",
dstIP: "122.210.105.240",
srcPort: 60818,
dstPort: 3306,
seq: 2951532891,
ack: 172126746,
ackFlag: true,
window: 1024,
}))
if resp.StateDescript() != StateTcpKeepalive {
t.Fatalf("keepalive response state = %d, want %d", resp.StateDescript(), StateTcpKeepalive)
}
if resp.Hints.TCP == nil || resp.Hints.TCP.Event != TCPEventKeepaliveResp {
t.Fatalf("response event = %#v, want %q", resp.Hints.TCP, TCPEventKeepaliveResp)
}
}
func TestObserveTCPKeepaliveResponseAfterWindowFallsBackToAck(t *testing.T) {
analyzer := newTestAnalyzer()
defer analyzer.Stop()
base := time.Unix(1700000540, 0)
_ = mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base, tcpPacketSpec{
srcIP: "122.210.105.240",
dstIP: "122.210.110.99",
srcPort: 3306,
dstPort: 60818,
seq: 172126745,
ack: 2951532891,
ackFlag: true,
window: 258,
}))
probe := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(75*time.Second), tcpPacketSpec{
srcIP: "122.210.105.240",
dstIP: "122.210.110.99",
srcPort: 3306,
dstPort: 60818,
seq: 172126745,
ack: 2951532891,
ackFlag: true,
window: 258,
}))
if probe.StateDescript() != StateTcpKeepalive {
t.Fatalf("probe state = %d, want %d", probe.StateDescript(), StateTcpKeepalive)
}
resp := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(75*time.Second+keepaliveResponseMaxDelay+time.Second), tcpPacketSpec{
srcIP: "122.210.110.99",
dstIP: "122.210.105.240",
srcPort: 60818,
dstPort: 3306,
seq: 2951532891,
ack: 172126746,
ackFlag: true,
window: 1024,
}))
if resp.StateDescript() != StateTcpAckOk {
t.Fatalf("late keepalive response state = %d, want %d", resp.StateDescript(), StateTcpAckOk)
}
}
func TestObserveTCPRepeatedAckWithoutGapStaysAck(t *testing.T) {
analyzer := newTestAnalyzer()
defer analyzer.Stop()
base := time.Unix(1700000550, 0)
_ = mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base, tcpPacketSpec{
srcIP: "10.10.0.1",
dstIP: "10.10.0.2",
srcPort: 3306,
dstPort: 60818,
seq: 5000,
ack: 9000,
ackFlag: true,
window: 512,
}))
next := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(100*time.Millisecond), tcpPacketSpec{
srcIP: "10.10.0.1",
dstIP: "10.10.0.2",
srcPort: 3306,
dstPort: 60818,
seq: 5000,
ack: 9000,
ackFlag: true,
window: 512,
}))
if next.StateDescript() != StateTcpAckOk {
t.Fatalf("short-gap ack state = %d, want %d", next.StateDescript(), StateTcpAckOk)
}
}
func TestObserveTCPPostHandshakeDataIsNotConnect3Again(t *testing.T) {
analyzer := newTestAnalyzer()
defer analyzer.Stop()
base := time.Unix(1700000565, 0)
syn := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base, tcpPacketSpec{
srcIP: "10.11.0.1",
dstIP: "10.11.0.2",
srcPort: 40000,
dstPort: 3306,
seq: 100,
syn: true,
window: 4096,
}))
if syn.StateDescript() != StateTcpConnect1 {
t.Fatalf("syn state = %d, want %d", syn.StateDescript(), StateTcpConnect1)
}
synack := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(time.Millisecond), tcpPacketSpec{
srcIP: "10.11.0.2",
dstIP: "10.11.0.1",
srcPort: 3306,
dstPort: 40000,
seq: 500,
ack: 101,
syn: true,
ackFlag: true,
window: 4096,
}))
if synack.StateDescript() != StateTcpConnect2 {
t.Fatalf("synack state = %d, want %d", synack.StateDescript(), StateTcpConnect2)
}
ack := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(2*time.Millisecond), tcpPacketSpec{
srcIP: "10.11.0.1",
dstIP: "10.11.0.2",
srcPort: 40000,
dstPort: 3306,
seq: 101,
ack: 501,
ackFlag: true,
window: 4096,
}))
if ack.StateDescript() != StateTcpConnect3 {
t.Fatalf("handshake ack state = %d, want %d", ack.StateDescript(), StateTcpConnect3)
}
data := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(3*time.Millisecond), tcpPacketSpec{
srcIP: "10.11.0.1",
dstIP: "10.11.0.2",
srcPort: 40000,
dstPort: 3306,
seq: 101,
ack: 501,
ackFlag: true,
window: 4096,
payload: []byte("hello"),
}))
if data.StateDescript() != StateTcpAckOk {
t.Fatalf("post-handshake data state = %d, want %d", data.StateDescript(), StateTcpAckOk)
}
}
func TestObserveTCPHandshakeAcrossSequenceWraparound(t *testing.T) {
analyzer := newTestAnalyzer()
defer analyzer.Stop()
base := time.Unix(1700000575, 0)
syn := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base, tcpPacketSpec{
srcIP: "10.12.0.1",
dstIP: "10.12.0.2",
srcPort: 40001,
dstPort: 3306,
seq: ^uint32(0),
syn: true,
window: 4096,
}))
if syn.StateDescript() != StateTcpConnect1 {
t.Fatalf("wrap syn state = %d, want %d", syn.StateDescript(), StateTcpConnect1)
}
synack := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(time.Millisecond), tcpPacketSpec{
srcIP: "10.12.0.2",
dstIP: "10.12.0.1",
srcPort: 3306,
dstPort: 40001,
seq: 500,
ack: 0,
syn: true,
ackFlag: true,
window: 4096,
}))
if synack.StateDescript() != StateTcpConnect2 {
t.Fatalf("wrap synack state = %d, want %d", synack.StateDescript(), StateTcpConnect2)
}
ack := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(2*time.Millisecond), tcpPacketSpec{
srcIP: "10.12.0.1",
dstIP: "10.12.0.2",
srcPort: 40001,
dstPort: 3306,
seq: 0,
ack: 501,
ackFlag: true,
window: 4096,
}))
if ack.StateDescript() != StateTcpConnect3 {
t.Fatalf("wrap handshake ack state = %d, want %d", ack.StateDescript(), StateTcpConnect3)
}
data := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(3*time.Millisecond), tcpPacketSpec{
srcIP: "10.12.0.1",
dstIP: "10.12.0.2",
srcPort: 40001,
dstPort: 3306,
seq: 0,
ack: 501,
ackFlag: true,
window: 4096,
payload: []byte("hi"),
}))
if data.StateDescript() != StateTcpAckOk {
t.Fatalf("wrap post-handshake data state = %d, want %d", data.StateDescript(), StateTcpAckOk)
}
}
func TestObserveTCPGapFillDoesNotLookLikeRetransmission(t *testing.T) {
analyzer := newTestAnalyzer()
defer analyzer.Stop()
base := time.Unix(1700000580, 0)
_, _ = mustEstablishTCPConnection(
t,
analyzer,
base,
"10.20.0.1",
44000,
"10.20.0.2",
3306,
100,
500,
)
_ = mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(3*time.Millisecond), tcpPacketSpec{
srcIP: "10.20.0.2",
dstIP: "10.20.0.1",
srcPort: 3306,
dstPort: 44000,
seq: 501,
ack: 101,
ackFlag: true,
window: 4096,
}))
first := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(4*time.Millisecond), tcpPacketSpec{
srcIP: "10.20.0.1",
dstIP: "10.20.0.2",
srcPort: 44000,
dstPort: 3306,
seq: 101,
ack: 501,
ackFlag: true,
window: 4096,
payload: []byte("AAAAA"),
}))
if first.StateDescript() != StateTcpAckOk {
t.Fatalf("first payload state = %d, want %d", first.StateDescript(), StateTcpAckOk)
}
later := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(5*time.Millisecond), tcpPacketSpec{
srcIP: "10.20.0.1",
dstIP: "10.20.0.2",
srcPort: 44000,
dstPort: 3306,
seq: 111,
ack: 501,
ackFlag: true,
window: 4096,
payload: []byte("CCCCC"),
}))
if later.StateDescript() != StateTcpAckOk {
t.Fatalf("later payload state = %d, want %d", later.StateDescript(), StateTcpAckOk)
}
gapFill := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(6*time.Millisecond), tcpPacketSpec{
srcIP: "10.20.0.1",
dstIP: "10.20.0.2",
srcPort: 44000,
dstPort: 3306,
seq: 106,
ack: 501,
ackFlag: true,
window: 4096,
payload: []byte("BBBBB"),
}))
if gapFill.StateDescript() != StateTcpAckOk {
t.Fatalf("gap-fill state = %d, want %d", gapFill.StateDescript(), StateTcpAckOk)
}
}
func TestObserveTCPRSTClearsTrackedFlows(t *testing.T) {
analyzer := newTestAnalyzer()
defer analyzer.Stop()
base := time.Unix(1700001000, 0)
inputs := []gopacket.Packet{
mustBuildTCPPacket(t, base, tcpPacketSpec{
srcIP: "10.0.0.1",
dstIP: "10.0.0.2",
srcPort: 40001,
dstPort: 3306,
seq: 100,
syn: true,
window: 4096,
}),
mustBuildTCPPacket(t, base.Add(time.Millisecond), tcpPacketSpec{
srcIP: "10.0.0.2",
dstIP: "10.0.0.1",
srcPort: 3306,
dstPort: 40001,
seq: 500,
ack: 101,
syn: true,
ackFlag: true,
window: 4096,
}),
mustBuildTCPPacket(t, base.Add(2*time.Millisecond), tcpPacketSpec{
srcIP: "10.0.0.1",
dstIP: "10.0.0.2",
srcPort: 40001,
dstPort: 3306,
seq: 101,
ack: 501,
ackFlag: true,
window: 4096,
}),
}
for _, packet := range inputs {
_ = mustObservePacket(t, analyzer, packet)
}
info := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(3*time.Millisecond), tcpPacketSpec{
srcIP: "10.0.0.2",
dstIP: "10.0.0.1",
srcPort: 3306,
dstPort: 40001,
seq: 501,
ack: 101,
ackFlag: true,
rst: true,
window: 4096,
}))
if info.StateDescript() != StateTcpRst {
t.Fatalf("rst state = %d, want %d", info.StateDescript(), StateTcpRst)
}
if got := analyzer.Tracker().ActiveFlowCount(); got != 0 {
t.Fatalf("active flow count = %d, want 0", got)
}
}
func TestObserveTCPFourWayCloseSequence(t *testing.T) {
analyzer := newTestAnalyzer()
defer analyzer.Stop()
base := time.Unix(1700004000, 0)
_, _ = mustEstablishTCPConnection(
t,
analyzer,
base,
"10.0.1.1",
41000,
"10.0.1.2",
3306,
100,
500,
)
info := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(3*time.Millisecond), tcpPacketSpec{
srcIP: "10.0.1.1",
dstIP: "10.0.1.2",
srcPort: 41000,
dstPort: 3306,
seq: 101,
ack: 501,
ackFlag: true,
fin: true,
window: 4096,
}))
if info.StateDescript() != StateTcpDisconnect1 {
t.Fatalf("client fin state = %d, want %d", info.StateDescript(), StateTcpDisconnect1)
}
info = mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(4*time.Millisecond), tcpPacketSpec{
srcIP: "10.0.1.2",
dstIP: "10.0.1.1",
srcPort: 3306,
dstPort: 41000,
seq: 501,
ack: 102,
ackFlag: true,
window: 4096,
}))
if info.StateDescript() != StateTcpDisconnect2 {
t.Fatalf("server ack state = %d, want %d", info.StateDescript(), StateTcpDisconnect2)
}
info = mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(5*time.Millisecond), tcpPacketSpec{
srcIP: "10.0.1.2",
dstIP: "10.0.1.1",
srcPort: 3306,
dstPort: 41000,
seq: 501,
ack: 102,
ackFlag: true,
fin: true,
window: 4096,
}))
if info.StateDescript() != StateTcpDisconnect3 {
t.Fatalf("server fin state = %d, want %d", info.StateDescript(), StateTcpDisconnect3)
}
info = mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(6*time.Millisecond), tcpPacketSpec{
srcIP: "10.0.1.1",
dstIP: "10.0.1.2",
srcPort: 41000,
dstPort: 3306,
seq: 102,
ack: 502,
ackFlag: true,
window: 4096,
}))
if info.StateDescript() != StateTcpDisconnect4 {
t.Fatalf("final ack state = %d, want %d", info.StateDescript(), StateTcpDisconnect4)
}
if got := analyzer.Tracker().ActiveFlowCount(); got != 0 {
t.Fatalf("active flow count after close = %d, want 0", got)
}
}
func TestObserveTCPDisconnect23CombinedFinAck(t *testing.T) {
analyzer := newTestAnalyzer()
defer analyzer.Stop()
base := time.Unix(1700005000, 0)
_, _ = mustEstablishTCPConnection(
t,
analyzer,
base,
"10.0.2.1",
42000,
"10.0.2.2",
3306,
100,
500,
)
info := mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(3*time.Millisecond), tcpPacketSpec{
srcIP: "10.0.2.1",
dstIP: "10.0.2.2",
srcPort: 42000,
dstPort: 3306,
seq: 101,
ack: 501,
ackFlag: true,
fin: true,
window: 4096,
}))
if info.StateDescript() != StateTcpDisconnect1 {
t.Fatalf("client fin state = %d, want %d", info.StateDescript(), StateTcpDisconnect1)
}
info = mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(4*time.Millisecond), tcpPacketSpec{
srcIP: "10.0.2.2",
dstIP: "10.0.2.1",
srcPort: 3306,
dstPort: 42000,
seq: 600,
ack: 102,
ackFlag: true,
fin: true,
window: 4096,
}))
if info.StateDescript() != StateTcpDisconnect23 {
t.Fatalf("combined fin-ack state = %d, want %d", info.StateDescript(), StateTcpDisconnect23)
}
info = mustObservePacket(t, analyzer, mustBuildTCPPacket(t, base.Add(5*time.Millisecond), tcpPacketSpec{
srcIP: "10.0.2.1",
dstIP: "10.0.2.2",
srcPort: 42000,
dstPort: 3306,
seq: 102,
ack: 601,
ackFlag: true,
window: 4096,
}))
if info.StateDescript() != StateTcpDisconnect4 {
t.Fatalf("final ack state = %d, want %d", info.StateDescript(), StateTcpDisconnect4)
}
if got := analyzer.Tracker().ActiveFlowCount(); got != 0 {
t.Fatalf("active flow count after combined close = %d, want 0", got)
}
}