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) } }