From 038f89f18e0dfd09a8031afa45299f42d17d624c Mon Sep 17 00:00:00 2001 From: Markus Graf Date: Fri, 27 Mar 2026 15:58:17 +0100 Subject: [PATCH] feat(10-02): add 28 new classification subtests to TestClassify - Mail: IMAP 143/993, POP3 110/995, SMTPSub 587 (PROTO-01) - File Transfer: FTP 20/21, SMB 445, TFTP 69 (PROTO-02) - Remote Access: RDP 3389, Telnet 23, VNC 5900 (PROTO-03) - Database: MySQL 3306, PostgreSQL 5432, Redis 6379, MongoDB 27017 (PROTO-04) - Discovery: mDNS 5353, SSDP 1900, SNMP 161/162 (PROTO-05) - VoIP: SIP TCP+UDP 5060 (PROTO-06) - Web: QUIC UDP/443 distinct from HTTPS TCP/443 (PROTO-07) - Infrastructure: LDAP 389/636, Kerberos TCP+UDP 88, Syslog 514 (PROTO-08) --- classify/classifier_test.go | 212 ++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) diff --git a/classify/classifier_test.go b/classify/classifier_test.go index 2b7e4a6..30e0844 100644 --- a/classify/classifier_test.go +++ b/classify/classifier_test.go @@ -226,6 +226,218 @@ func TestClassify(t *testing.T) { } }) + // --- 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.