bcap/api_test.go

316 lines
8.4 KiB
Go
Raw Normal View History

2026-03-24 23:39:55 +08:00
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
}