package bcap import ( "net" "testing" "time" ) func TestDecoderDecodeARP(t *testing.T) { decoder := NewDecoder() base := time.Unix(1700005000, 0) packet := mustBuildARPPacket(t, base, arpPacketSpec{ srcMAC: "02:00:00:00:00:01", dstMAC: "ff:ff:ff:ff:ff:ff", senderMAC: "02:00:00:00:00:01", targetMAC: "00:00:00:00:00:00", senderIP: "10.0.0.1", targetIP: "10.0.0.2", operation: 1, }) decoded, err := decoder.Decode(packet) if err != nil { t.Fatalf("decode arp: %v", err) } if decoded.Network.Family != NetworkFamilyARP { t.Fatalf("network family = %q, want %q", decoded.Network.Family, NetworkFamilyARP) } if decoded.Transport.Kind != ProtocolARP { t.Fatalf("protocol = %q, want %q", decoded.Transport.Kind, ProtocolARP) } if decoded.Network.ARP == nil { t.Fatal("expected arp facts") } if decoded.Network.ARP.SenderIP != "10.0.0.1" { t.Fatalf("sender ip = %q, want %q", decoded.Network.ARP.SenderIP, "10.0.0.1") } if decoded.Network.ARP.TargetIP != "10.0.0.2" { t.Fatalf("target ip = %q, want %q", decoded.Network.ARP.TargetIP, "10.0.0.2") } } func TestDecoderDecodeIPv4WithoutTransportReturnsUnknown(t *testing.T) { decoder := NewDecoder() base := time.Unix(1700005001, 0) packet := mustBuildIPv4OnlyPacket(t, base, "10.0.0.1", "10.0.0.2") decoded, err := decoder.Decode(packet) if err != nil { t.Fatalf("decode ipv4-only: %v", err) } if decoded.Network.Family != NetworkFamilyIPv4 { t.Fatalf("network family = %q, want %q", decoded.Network.Family, NetworkFamilyIPv4) } if decoded.Transport.Kind != ProtocolUnknown { t.Fatalf("transport kind = %q, want %q", decoded.Transport.Kind, ProtocolUnknown) } } func TestAnalyzerObservePacketTCP(t *testing.T) { analyzer := NewAnalyzer() defer analyzer.Stop() base := time.Unix(1700005002, 0) obs, err := analyzer.ObservePacket(mustBuildTCPPacket(t, base, tcpPacketSpec{ srcIP: "10.0.0.1", dstIP: "10.0.0.2", srcPort: 12345, dstPort: 80, seq: 100, syn: true, window: 4096, })) if err != nil { t.Fatalf("observe tcp packet: %v", err) } if obs.Flow.Forward.Protocol != ProtocolTCP { t.Fatalf("flow protocol = %q, want %q", obs.Flow.Forward.Protocol, ProtocolTCP) } if obs.Hints.TCP == nil { t.Fatal("expected tcp hint") } if obs.Hints.TCP.Event != TCPEventSYN { t.Fatalf("tcp event = %q, want %q", obs.Hints.TCP.Event, TCPEventSYN) } if obs.Hints.Summary.Code != string(TagTCPHandshakeSYN) { t.Fatalf("summary = %q, want %q", obs.Hints.Summary.Code, TagTCPHandshakeSYN) } } func TestAnalyzerObservePacketARP(t *testing.T) { analyzer := NewAnalyzer() defer analyzer.Stop() base := time.Unix(1700005003, 0) obs, err := analyzer.ObservePacket(mustBuildARPPacket(t, base, arpPacketSpec{ srcMAC: "02:00:00:00:00:01", dstMAC: "ff:ff:ff:ff:ff:ff", senderMAC: "02:00:00:00:00:01", targetMAC: "00:00:00:00:00:00", senderIP: "10.0.0.1", targetIP: "10.0.0.2", operation: 1, })) if err != nil { t.Fatalf("observe arp packet: %v", err) } if obs.Flow.Forward.Protocol != ProtocolARP { t.Fatalf("flow protocol = %q, want %q", obs.Flow.Forward.Protocol, ProtocolARP) } if obs.Hints.ARP == nil { t.Fatal("expected arp hint") } if !obs.Hints.ARP.Request { t.Fatal("expected arp request hint") } if obs.Hints.Summary.Code != string(TagARPRequest) { t.Fatalf("summary = %q, want %q", obs.Hints.Summary.Code, TagARPRequest) } } func TestTrackerObserveDecodedTCPWithoutRawPacket(t *testing.T) { decoder := NewDecoder() tracker := NewTracker() defer tracker.Stop() base := time.Unix(1700005004, 0) decoded, err := decoder.Decode(mustBuildTCPPacket(t, base, tcpPacketSpec{ srcIP: "10.0.0.1", dstIP: "10.0.0.2", srcPort: 12345, dstPort: 80, seq: 100, syn: true, window: 4096, })) if err != nil { t.Fatalf("decode tcp: %v", err) } decoded.Raw.Packet = nil obs, err := tracker.Observe(decoded) if err != nil { t.Fatalf("observe decoded tcp: %v", err) } if obs.Hints.TCP == nil { t.Fatal("expected tcp hint") } if obs.Hints.TCP.Event != TCPEventSYN { t.Fatalf("tcp event = %q, want %q", obs.Hints.TCP.Event, TCPEventSYN) } } func TestAnalyzerObservePacketWithOptionsSrcMACOverride(t *testing.T) { analyzer := NewAnalyzer() defer analyzer.Stop() override := net.HardwareAddr{0x02, 0xaa, 0xbb, 0xcc, 0xdd, 0xee} base := time.Unix(1700005005, 0) obs, err := analyzer.ObservePacketWithOptions(mustBuildTCPPacket(t, base, tcpPacketSpec{ srcIP: "10.0.0.1", dstIP: "10.0.0.2", srcPort: 12345, dstPort: 80, seq: 100, syn: true, window: 4096, }), DecodeOptions{ SrcMACOverride: override, }) if err != nil { t.Fatalf("observe tcp packet with options: %v", err) } if got := obs.Packet.Link.SrcMAC.String(); got != override.String() { t.Fatalf("src mac = %q, want %q", got, override.String()) } if obs.Packet.Meta.RelativeTime != 0 { t.Fatalf("relative time = %v, want 0", obs.Packet.Meta.RelativeTime) } } func TestAnalyzerObservePacketTCPKeepaliveResponse(t *testing.T) { analyzer := NewAnalyzer() defer analyzer.Stop() base := time.Unix(1700005006, 0) _, err := analyzer.ObservePacket(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 err != nil { t.Fatalf("observe baseline packet: %v", err) } probe, err := analyzer.ObservePacket(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 err != nil { t.Fatalf("observe keepalive probe: %v", err) } if probe.Hints.TCP == nil || probe.Hints.TCP.Event != TCPEventKeepalive { t.Fatalf("probe event = %#v, want %q", probe.Hints.TCP, TCPEventKeepalive) } resp, err := analyzer.ObservePacket(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 err != nil { t.Fatalf("observe keepalive response: %v", err) } if resp.Hints.TCP == nil { t.Fatal("expected tcp hint") } if resp.Hints.TCP.LegacyState != StateTcpKeepalive { t.Fatalf("legacy state = %d, want %d", resp.Hints.TCP.LegacyState, StateTcpKeepalive) } if resp.Hints.TCP.Event != TCPEventKeepaliveResp { t.Fatalf("tcp event = %q, want %q", resp.Hints.TCP.Event, TCPEventKeepaliveResp) } if !resp.Hints.TCP.KeepaliveResponse { t.Fatal("expected keepalive response flag") } if resp.Hints.Summary.Code != string(TagTCPKeepaliveResp) { t.Fatalf("summary = %q, want %q", resp.Hints.Summary.Code, TagTCPKeepaliveResp) } if !containsTag(resp.Hints.Tags, TagTCPKeepaliveResp) { t.Fatalf("tags = %v, want %q", resp.Hints.Tags, TagTCPKeepaliveResp) } if !containsTag(resp.Hints.Tags, TagTCPKeepalive) { t.Fatalf("tags = %v, want %q", resp.Hints.Tags, TagTCPKeepalive) } } func TestTrackerCleanupExpiredFlows(t *testing.T) { cfg := DefaultConfig() cfg.ConnectionTimeout = time.Second cfg.CleanupInterval = 0 decoder := NewDecoder() tracker := NewTrackerWithConfig(cfg) defer tracker.Stop() stale, err := decoder.Decode(mustBuildTCPPacket(t, time.Now().Add(-5*time.Second), tcpPacketSpec{ srcIP: "10.0.0.1", dstIP: "10.0.0.2", srcPort: 12345, dstPort: 80, seq: 100, syn: true, window: 4096, })) if err != nil { t.Fatalf("decode stale packet: %v", err) } if _, err := tracker.Observe(stale); err != nil { t.Fatalf("observe stale packet: %v", err) } fresh, err := decoder.Decode(mustBuildTCPPacket(t, time.Now(), tcpPacketSpec{ srcIP: "10.0.0.3", dstIP: "10.0.0.4", srcPort: 23456, dstPort: 443, seq: 200, syn: true, window: 4096, })) if err != nil { t.Fatalf("decode fresh packet: %v", err) } if _, err := tracker.Observe(fresh); err != nil { t.Fatalf("observe fresh packet: %v", err) } if got := tracker.ActiveFlowCount(); got != 2 { t.Fatalf("active flow count = %d, want 2", got) } if removed := tracker.CleanupExpiredFlows(); removed != 1 { t.Fatalf("removed stale flows = %d, want 1", removed) } if got := tracker.ActiveFlowCount(); got != 1 { t.Fatalf("active flow count after cleanup = %d, want 1", got) } } func containsTag(tags []Tag, want Tag) bool { for _, tag := range tags { if tag == want { return true } } return false }