- Rename TestNewBankHas14Layers to TestNewBankHasAllLayers, use len(classify.AllClasses()) - Update config_test.go: replace 4 hardcoded 14 counts with len(classify.AllClasses()) - Update TestPrintConfigContainsAllClasses: replace hardcoded list with AllClasses() loop - Update classifier_test.go: change TestAllClassesCount want from 32 to 35
512 lines
17 KiB
Go
512 lines
17 KiB
Go
package classify_test
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gopacket/gopacket"
|
|
"github.com/gopacket/gopacket/layers"
|
|
"github.com/netsynth/netsynth/classify"
|
|
)
|
|
|
|
// buildTCPPacket builds a synthetic IPv4/TCP packet targeting the given dst port.
|
|
func buildTCPPacket(t *testing.T, dstPort uint16) gopacket.Packet {
|
|
t.Helper()
|
|
buf := gopacket.NewSerializeBuffer()
|
|
opts := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: false}
|
|
|
|
eth := &layers.Ethernet{
|
|
SrcMAC: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
|
|
DstMAC: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x02},
|
|
EthernetType: layers.EthernetTypeIPv4,
|
|
}
|
|
ip := &layers.IPv4{
|
|
Version: 4,
|
|
TTL: 64,
|
|
Protocol: layers.IPProtocolTCP,
|
|
SrcIP: []byte{192, 168, 1, 1},
|
|
DstIP: []byte{192, 168, 1, 2},
|
|
}
|
|
tcp := &layers.TCP{
|
|
SrcPort: layers.TCPPort(54321),
|
|
DstPort: layers.TCPPort(dstPort),
|
|
SYN: true,
|
|
}
|
|
if err := tcp.SetNetworkLayerForChecksum(ip); err != nil {
|
|
t.Fatalf("SetNetworkLayerForChecksum: %v", err)
|
|
}
|
|
if err := gopacket.SerializeLayers(buf, opts, eth, ip, tcp, gopacket.Payload{}); err != nil {
|
|
t.Fatalf("SerializeLayers tcp: %v", err)
|
|
}
|
|
return gopacket.NewPacket(buf.Bytes(), layers.LayerTypeEthernet, gopacket.Default)
|
|
}
|
|
|
|
// buildUDPPacket builds a synthetic IPv4/UDP packet targeting the given dst port.
|
|
func buildUDPPacket(t *testing.T, dstPort uint16) gopacket.Packet {
|
|
t.Helper()
|
|
buf := gopacket.NewSerializeBuffer()
|
|
opts := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: false}
|
|
|
|
eth := &layers.Ethernet{
|
|
SrcMAC: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
|
|
DstMAC: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x02},
|
|
EthernetType: layers.EthernetTypeIPv4,
|
|
}
|
|
ip := &layers.IPv4{
|
|
Version: 4,
|
|
TTL: 64,
|
|
Protocol: layers.IPProtocolUDP,
|
|
SrcIP: []byte{192, 168, 1, 1},
|
|
DstIP: []byte{192, 168, 1, 2},
|
|
}
|
|
udp := &layers.UDP{
|
|
SrcPort: layers.UDPPort(54321),
|
|
DstPort: layers.UDPPort(dstPort),
|
|
}
|
|
if err := udp.SetNetworkLayerForChecksum(ip); err != nil {
|
|
t.Fatalf("SetNetworkLayerForChecksum: %v", err)
|
|
}
|
|
if err := gopacket.SerializeLayers(buf, opts, eth, ip, udp, gopacket.Payload{}); err != nil {
|
|
t.Fatalf("SerializeLayers udp: %v", err)
|
|
}
|
|
return gopacket.NewPacket(buf.Bytes(), layers.LayerTypeEthernet, gopacket.Default)
|
|
}
|
|
|
|
// buildICMPPacket builds a synthetic IPv4/ICMPv4 packet.
|
|
func buildICMPPacket(t *testing.T) gopacket.Packet {
|
|
t.Helper()
|
|
buf := gopacket.NewSerializeBuffer()
|
|
opts := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: false}
|
|
|
|
eth := &layers.Ethernet{
|
|
SrcMAC: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
|
|
DstMAC: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x02},
|
|
EthernetType: layers.EthernetTypeIPv4,
|
|
}
|
|
ip := &layers.IPv4{
|
|
Version: 4,
|
|
TTL: 64,
|
|
Protocol: layers.IPProtocolICMPv4,
|
|
SrcIP: []byte{192, 168, 1, 1},
|
|
DstIP: []byte{192, 168, 1, 2},
|
|
}
|
|
icmp := &layers.ICMPv4{
|
|
TypeCode: layers.CreateICMPv4TypeCode(layers.ICMPv4TypeEchoRequest, 0),
|
|
}
|
|
if err := gopacket.SerializeLayers(buf, opts, eth, ip, icmp, gopacket.Payload{}); err != nil {
|
|
t.Fatalf("SerializeLayers icmp: %v", err)
|
|
}
|
|
return gopacket.NewPacket(buf.Bytes(), layers.LayerTypeEthernet, gopacket.Default)
|
|
}
|
|
|
|
// buildUnknownPacket builds a synthetic Ethernet packet with no TCP/UDP/ICMP payload.
|
|
func buildUnknownPacket(t *testing.T) gopacket.Packet {
|
|
t.Helper()
|
|
buf := gopacket.NewSerializeBuffer()
|
|
opts := gopacket.SerializeOptions{FixLengths: true}
|
|
|
|
eth := &layers.Ethernet{
|
|
SrcMAC: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
|
|
DstMAC: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x02},
|
|
EthernetType: layers.EthernetTypeARP,
|
|
}
|
|
if err := gopacket.SerializeLayers(buf, opts, eth, gopacket.Payload{0x01, 0x02, 0x03, 0x04}); err != nil {
|
|
t.Fatalf("SerializeLayers unknown: %v", err)
|
|
}
|
|
return gopacket.NewPacket(buf.Bytes(), layers.LayerTypeEthernet, gopacket.Default)
|
|
}
|
|
|
|
func TestClassify(t *testing.T) {
|
|
c := classify.NewClassifier(classify.DefaultRules)
|
|
|
|
t.Run("TestClassifyICMP", func(t *testing.T) {
|
|
pkt := buildICMPPacket(t)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassICMP {
|
|
t.Errorf("ICMP packet: got class %q, want %q", got.Class, classify.ClassICMP)
|
|
}
|
|
if got.Protocol != "icmp" {
|
|
t.Errorf("ICMP packet: got protocol %q, want %q", got.Protocol, "icmp")
|
|
}
|
|
})
|
|
|
|
t.Run("TestClassifyDNS_UDP", func(t *testing.T) {
|
|
pkt := buildUDPPacket(t, 53)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassDNS {
|
|
t.Errorf("DNS/UDP packet: got class %q, want %q", got.Class, classify.ClassDNS)
|
|
}
|
|
})
|
|
|
|
t.Run("TestClassifyDNS_TCP", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 53)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassDNS {
|
|
t.Errorf("DNS/TCP packet: got class %q, want %q", got.Class, classify.ClassDNS)
|
|
}
|
|
})
|
|
|
|
t.Run("TestClassifyHTTPS", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 443)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassHTTPS {
|
|
t.Errorf("HTTPS packet: got class %q, want %q", got.Class, classify.ClassHTTPS)
|
|
}
|
|
})
|
|
|
|
t.Run("TestClassifyHTTP", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 80)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassHTTP {
|
|
t.Errorf("HTTP packet: got class %q, want %q", got.Class, classify.ClassHTTP)
|
|
}
|
|
})
|
|
|
|
t.Run("TestClassifySSH", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 22)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassSSH {
|
|
t.Errorf("SSH packet: got class %q, want %q", got.Class, classify.ClassSSH)
|
|
}
|
|
})
|
|
|
|
t.Run("TestClassifySMTP", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 25)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassSMTP {
|
|
t.Errorf("SMTP packet: got class %q, want %q", got.Class, classify.ClassSMTP)
|
|
}
|
|
})
|
|
|
|
t.Run("TestClassifyNTP", func(t *testing.T) {
|
|
pkt := buildUDPPacket(t, 123)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassNTP {
|
|
t.Errorf("NTP packet: got class %q, want %q", got.Class, classify.ClassNTP)
|
|
}
|
|
})
|
|
|
|
t.Run("TestClassifyDHCP_port67", func(t *testing.T) {
|
|
pkt := buildUDPPacket(t, 67)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassDHCP {
|
|
t.Errorf("DHCP port 67 packet: got class %q, want %q", got.Class, classify.ClassDHCP)
|
|
}
|
|
})
|
|
|
|
t.Run("TestClassifyDHCP_port68", func(t *testing.T) {
|
|
pkt := buildUDPPacket(t, 68)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassDHCP {
|
|
t.Errorf("DHCP port 68 packet: got class %q, want %q", got.Class, classify.ClassDHCP)
|
|
}
|
|
})
|
|
|
|
t.Run("TestClassifyOtherTCP", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 8080)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassOtherTCP {
|
|
t.Errorf("OtherTCP packet (port 8080): got class %q, want %q", got.Class, classify.ClassOtherTCP)
|
|
}
|
|
})
|
|
|
|
t.Run("TestClassifyOtherUDP", func(t *testing.T) {
|
|
pkt := buildUDPPacket(t, 9999)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassOtherUDP {
|
|
t.Errorf("OtherUDP packet (port 9999): got class %q, want %q", got.Class, classify.ClassOtherUDP)
|
|
}
|
|
})
|
|
|
|
t.Run("TestClassifyUnknown", func(t *testing.T) {
|
|
pkt := buildUnknownPacket(t)
|
|
got := c.Classify(pkt)
|
|
if !strings.HasPrefix(string(got.Class), "unknown-") {
|
|
t.Errorf("Unknown packet: got class %q, want unknown-N bucket", got.Class)
|
|
}
|
|
})
|
|
|
|
// --- Mail (PROTO-01) ---
|
|
t.Run("TestClassifyIMAP_port143", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 143)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassIMAP {
|
|
t.Errorf("IMAP port 143: got class %q, want %q", got.Class, classify.ClassIMAP)
|
|
}
|
|
})
|
|
t.Run("TestClassifyIMAP_port993", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 993)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassIMAP {
|
|
t.Errorf("IMAPS port 993: got class %q, want %q", got.Class, classify.ClassIMAP)
|
|
}
|
|
})
|
|
t.Run("TestClassifyPOP3_port110", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 110)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassPOP3 {
|
|
t.Errorf("POP3 port 110: got class %q, want %q", got.Class, classify.ClassPOP3)
|
|
}
|
|
})
|
|
t.Run("TestClassifyPOP3_port995", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 995)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassPOP3 {
|
|
t.Errorf("POP3S port 995: got class %q, want %q", got.Class, classify.ClassPOP3)
|
|
}
|
|
})
|
|
t.Run("TestClassifySMTPSub_port587", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 587)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassSMTPSub {
|
|
t.Errorf("SMTP-sub port 587: got class %q, want %q", got.Class, classify.ClassSMTPSub)
|
|
}
|
|
})
|
|
|
|
// --- File Transfer (PROTO-02) ---
|
|
t.Run("TestClassifyFTP_port20", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 20)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassFTP {
|
|
t.Errorf("FTP data port 20: got class %q, want %q", got.Class, classify.ClassFTP)
|
|
}
|
|
})
|
|
t.Run("TestClassifyFTP_port21", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 21)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassFTP {
|
|
t.Errorf("FTP control port 21: got class %q, want %q", got.Class, classify.ClassFTP)
|
|
}
|
|
})
|
|
t.Run("TestClassifySMB_port445", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 445)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassSMB {
|
|
t.Errorf("SMB port 445: got class %q, want %q", got.Class, classify.ClassSMB)
|
|
}
|
|
})
|
|
t.Run("TestClassifyTFTP_port69", func(t *testing.T) {
|
|
pkt := buildUDPPacket(t, 69)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassTFTP {
|
|
t.Errorf("TFTP port 69: got class %q, want %q", got.Class, classify.ClassTFTP)
|
|
}
|
|
})
|
|
|
|
// --- Remote Access (PROTO-03) ---
|
|
t.Run("TestClassifyRDP_port3389", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 3389)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassRDP {
|
|
t.Errorf("RDP port 3389: got class %q, want %q", got.Class, classify.ClassRDP)
|
|
}
|
|
})
|
|
t.Run("TestClassifyTelnet_port23", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 23)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassTelnet {
|
|
t.Errorf("Telnet port 23: got class %q, want %q", got.Class, classify.ClassTelnet)
|
|
}
|
|
})
|
|
t.Run("TestClassifyVNC_port5900", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 5900)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassVNC {
|
|
t.Errorf("VNC port 5900: got class %q, want %q", got.Class, classify.ClassVNC)
|
|
}
|
|
})
|
|
|
|
// --- Database (PROTO-04) ---
|
|
t.Run("TestClassifyMySQL_port3306", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 3306)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassMySQL {
|
|
t.Errorf("MySQL port 3306: got class %q, want %q", got.Class, classify.ClassMySQL)
|
|
}
|
|
})
|
|
t.Run("TestClassifyPostgreSQL_port5432", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 5432)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassPostgreSQL {
|
|
t.Errorf("PostgreSQL port 5432: got class %q, want %q", got.Class, classify.ClassPostgreSQL)
|
|
}
|
|
})
|
|
t.Run("TestClassifyRedis_port6379", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 6379)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassRedis {
|
|
t.Errorf("Redis port 6379: got class %q, want %q", got.Class, classify.ClassRedis)
|
|
}
|
|
})
|
|
t.Run("TestClassifyMongoDB_port27017", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 27017)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassMongoDB {
|
|
t.Errorf("MongoDB port 27017: got class %q, want %q", got.Class, classify.ClassMongoDB)
|
|
}
|
|
})
|
|
|
|
// --- Discovery (PROTO-05) ---
|
|
t.Run("TestClassifyMDNS_port5353", func(t *testing.T) {
|
|
pkt := buildUDPPacket(t, 5353)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassMDNS {
|
|
t.Errorf("mDNS port 5353: got class %q, want %q", got.Class, classify.ClassMDNS)
|
|
}
|
|
})
|
|
t.Run("TestClassifySDP_port1900", func(t *testing.T) {
|
|
pkt := buildUDPPacket(t, 1900)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassSSDP {
|
|
t.Errorf("SSDP port 1900: got class %q, want %q", got.Class, classify.ClassSSDP)
|
|
}
|
|
})
|
|
t.Run("TestClassifySNMP_port161", func(t *testing.T) {
|
|
pkt := buildUDPPacket(t, 161)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassSNMP {
|
|
t.Errorf("SNMP port 161: got class %q, want %q", got.Class, classify.ClassSNMP)
|
|
}
|
|
})
|
|
t.Run("TestClassifySNMP_port162", func(t *testing.T) {
|
|
pkt := buildUDPPacket(t, 162)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassSNMP {
|
|
t.Errorf("SNMP-trap port 162: got class %q, want %q", got.Class, classify.ClassSNMP)
|
|
}
|
|
})
|
|
|
|
// --- VoIP (PROTO-06) --- SIP runs on both TCP and UDP
|
|
t.Run("TestClassifySIP_TCP5060", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 5060)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassSIP {
|
|
t.Errorf("SIP TCP 5060: got class %q, want %q", got.Class, classify.ClassSIP)
|
|
}
|
|
})
|
|
t.Run("TestClassifySIP_UDP5060", func(t *testing.T) {
|
|
pkt := buildUDPPacket(t, 5060)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassSIP {
|
|
t.Errorf("SIP UDP 5060: got class %q, want %q", got.Class, classify.ClassSIP)
|
|
}
|
|
})
|
|
|
|
// --- Web extension (PROTO-07) --- QUIC is UDP 443, must NOT match HTTPS (TCP 443)
|
|
t.Run("TestClassifyQUIC_UDP443", func(t *testing.T) {
|
|
pkt := buildUDPPacket(t, 443)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassQUIC {
|
|
t.Errorf("QUIC UDP 443: got class %q, want %q", got.Class, classify.ClassQUIC)
|
|
}
|
|
})
|
|
|
|
// --- Infrastructure extension (PROTO-08) ---
|
|
t.Run("TestClassifyLDAP_port389", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 389)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassLDAP {
|
|
t.Errorf("LDAP port 389: got class %q, want %q", got.Class, classify.ClassLDAP)
|
|
}
|
|
})
|
|
t.Run("TestClassifyLDAP_port636", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 636)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassLDAP {
|
|
t.Errorf("LDAPS port 636: got class %q, want %q", got.Class, classify.ClassLDAP)
|
|
}
|
|
})
|
|
t.Run("TestClassifyKerberos_TCP88", func(t *testing.T) {
|
|
pkt := buildTCPPacket(t, 88)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassKerberos {
|
|
t.Errorf("Kerberos TCP 88: got class %q, want %q", got.Class, classify.ClassKerberos)
|
|
}
|
|
})
|
|
t.Run("TestClassifyKerberos_UDP88", func(t *testing.T) {
|
|
pkt := buildUDPPacket(t, 88)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassKerberos {
|
|
t.Errorf("Kerberos UDP 88: got class %q, want %q", got.Class, classify.ClassKerberos)
|
|
}
|
|
})
|
|
t.Run("TestClassifySyslog_port514", func(t *testing.T) {
|
|
pkt := buildUDPPacket(t, 514)
|
|
got := c.Classify(pkt)
|
|
if got.Class != classify.ClassSyslog {
|
|
t.Errorf("Syslog port 514: got class %q, want %q", got.Class, classify.ClassSyslog)
|
|
}
|
|
})
|
|
|
|
t.Run("TestRulesAreOrderDependent", func(t *testing.T) {
|
|
// A rule list where TCP port 443 maps to ClassHTTP (wrong) placed first,
|
|
// then ClassHTTPS. The first matching rule should win.
|
|
customRules := []classify.Rule{
|
|
{Protocol: "tcp", DstPort: 443, Class: classify.ClassHTTP}, // first match
|
|
{Protocol: "tcp", DstPort: 443, Class: classify.ClassHTTPS},
|
|
{Protocol: "tcp", DstPort: 0, Class: classify.ClassOtherTCP},
|
|
}
|
|
custom := classify.NewClassifier(customRules)
|
|
pkt := buildTCPPacket(t, 443)
|
|
got := custom.Classify(pkt)
|
|
if got.Class != classify.ClassHTTP {
|
|
t.Errorf("Order-dependent rules: got class %q, want %q (first match should win)", got.Class, classify.ClassHTTP)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestAllClassesCount(t *testing.T) {
|
|
classes := classify.AllClasses()
|
|
if len(classes) != 35 {
|
|
t.Errorf("AllClasses() returned %d classes, want 35", len(classes))
|
|
}
|
|
// No plain "unknown" should exist
|
|
for _, c := range classes {
|
|
if c == "unknown" {
|
|
t.Error("AllClasses() still contains plain \"unknown\" — should be removed per D-04")
|
|
}
|
|
}
|
|
// All 4 buckets must be present
|
|
buckets := map[classify.TrafficClass]bool{
|
|
classify.ClassUnknown1: false, classify.ClassUnknown2: false,
|
|
classify.ClassUnknown3: false, classify.ClassUnknown4: false,
|
|
}
|
|
for _, c := range classes {
|
|
if _, ok := buckets[c]; ok {
|
|
buckets[c] = true
|
|
}
|
|
}
|
|
for bucket, found := range buckets {
|
|
if !found {
|
|
t.Errorf("AllClasses() missing bucket %q", bucket)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHashBucketDistribution(t *testing.T) {
|
|
// hashBucket is unexported, test through Classify with a minimal rule set
|
|
// that has no catch-all TCP/UDP rules, so unmatched ports reach hashBucket.
|
|
minimalRules := []classify.Rule{
|
|
{Protocol: "icmp", DstPort: 0, Class: classify.ClassICMP},
|
|
{Protocol: "tcp", DstPort: 443, Class: classify.ClassHTTPS},
|
|
}
|
|
c := classify.NewClassifier(minimalRules)
|
|
seen := make(map[classify.TrafficClass]bool)
|
|
// Try a range of unmatched TCP ports to hit all 4 buckets
|
|
for port := uint16(10000); port < 11000; port++ {
|
|
pkt := buildTCPPacket(t, port)
|
|
got := c.Classify(pkt)
|
|
if strings.HasPrefix(string(got.Class), "unknown-") {
|
|
seen[got.Class] = true
|
|
}
|
|
}
|
|
for _, bucket := range []classify.TrafficClass{
|
|
classify.ClassUnknown1, classify.ClassUnknown2,
|
|
classify.ClassUnknown3, classify.ClassUnknown4,
|
|
} {
|
|
if !seen[bucket] {
|
|
t.Errorf("hashBucket never produced %q across ports 10000-10999", bucket)
|
|
}
|
|
}
|
|
}
|