@@ -453,6 +453,76 @@ func TestParseFirewallLogPartialMissingFields(t *testing.T) {
453453 }
454454}
455455
456+ func TestParseFirewallLogIptablesDropped (t * testing.T ) {
457+ // Create a temporary directory for the test
458+ tempDir := testutil .TempDir (t , "test-*" )
459+
460+ // Simulate iptables-dropped traffic: domain="-" but destIPPort has the actual destination.
461+ // This occurs when iptables drops packets before they reach the Squid proxy, so Squid
462+ // only sees the IP layer info and logs domain as "-".
463+ testLogContent := `1761332530.474 172.30.0.20:35288 api.github.com:443 140.82.112.22:443 1.1 CONNECT 200 TCP_TUNNEL:HIER_DIRECT api.github.com:443 "-"
464+ 1761332531.123 172.30.0.20:35289 - 8.8.8.8:53 - - 0 NONE_NONE:HIER_NONE - "-"
465+ 1761332532.456 172.30.0.20:35290 - 1.2.3.4:443 - - 0 NONE_NONE:HIER_NONE - "-"
466+ 1761332533.789 172.30.0.20:35291 - 1.2.3.4:443 - - 0 NONE_NONE:HIER_NONE - "-"
467+ 1761332534.012 172.30.0.20:35292 - - - - 0 NONE_NONE:HIER_NONE - "-"
468+ `
469+
470+ // Write test log file
471+ logPath := filepath .Join (tempDir , "firewall.log" )
472+ err := os .WriteFile (logPath , []byte (testLogContent ), 0644 )
473+ if err != nil {
474+ t .Fatalf ("Failed to create test firewall.log: %v" , err )
475+ }
476+
477+ // Test parsing
478+ analysis , err := parseFirewallLog (logPath , false )
479+ if err != nil {
480+ t .Fatalf ("Failed to parse firewall log: %v" , err )
481+ }
482+
483+ if analysis .TotalRequests != 5 {
484+ t .Errorf ("TotalRequests: got %d, want 5" , analysis .TotalRequests )
485+ }
486+ if analysis .AllowedRequests != 1 {
487+ t .Errorf ("AllowedRequests: got %d, want 1" , analysis .AllowedRequests )
488+ }
489+ if analysis .BlockedRequests != 4 {
490+ t .Errorf ("BlockedRequests: got %d, want 4" , analysis .BlockedRequests )
491+ }
492+
493+ // Iptables-dropped entries with destIPPort should use destIPPort as the key
494+ if stats , ok := analysis .RequestsByDomain ["8.8.8.8:53" ]; ! ok {
495+ t .Error ("8.8.8.8:53 should be in RequestsByDomain (iptables-dropped fallback)" )
496+ } else if stats .Blocked != 1 {
497+ t .Errorf ("8.8.8.8:53 Blocked: got %d, want 1" , stats .Blocked )
498+ }
499+
500+ if stats , ok := analysis .RequestsByDomain ["1.2.3.4:443" ]; ! ok {
501+ t .Error ("1.2.3.4:443 should be in RequestsByDomain (iptables-dropped fallback)" )
502+ } else if stats .Blocked != 2 {
503+ t .Errorf ("1.2.3.4:443 Blocked: got %d, want 2" , stats .Blocked )
504+ }
505+
506+ // "-" should only appear for entries where both domain and destIPPort are "-"
507+ if stats , ok := analysis .RequestsByDomain ["-" ]; ! ok {
508+ t .Error ("\" -\" should be in RequestsByDomain for truly-unknown entries" )
509+ } else if stats .Blocked != 1 {
510+ t .Errorf ("\" -\" Blocked: got %d, want 1" , stats .Blocked )
511+ }
512+
513+ // BlockedDomains should include the real IPs, not just "-"
514+ blockedSet := make (map [string ]bool )
515+ for _ , d := range analysis .BlockedDomains {
516+ blockedSet [d ] = true
517+ }
518+ if ! blockedSet ["8.8.8.8:53" ] {
519+ t .Error ("BlockedDomains should contain 8.8.8.8:53 (iptables-dropped fallback)" )
520+ }
521+ if ! blockedSet ["1.2.3.4:443" ] {
522+ t .Error ("BlockedDomains should contain 1.2.3.4:443 (iptables-dropped fallback)" )
523+ }
524+ }
525+
456526func TestAnalyzeMultipleFirewallLogs (t * testing.T ) {
457527 // Create a temporary directory for the test
458528 tempDir := testutil .TempDir (t , "test-*" )
0 commit comments