package stats import ( "net/netip" "testing" "time" ) func TestParseResponse(t *testing.T) { loc := time.FixedZone("test", 3600) for _, tc := range []struct { line string ok bool client, view, name, typ, rc string }{ // Real lines from named 9.20.24 (with and without views). {`07-Oct-2026 19:46:22.184 client @0x71358b8ff000 127.0.0.1#53937 (www.example.test): view internal: response: www.example.test IN A NOERROR 1 0 1 +E(0)K (127.0.0.1)`, true, "127.0.0.1", "internal", "www.example.test", "A", "NOERROR"}, {`07-Oct-2026 19:46:22.195 client @0x71358b8ff000 10.10.0.37#51709 (nope.example.test): response: Nope.Example.Test IN AAAA NXDOMAIN 0 1 1 +E(0)K (127.0.0.1)`, true, "10.10.0.37", "", "nope.example.test", "AAAA", "NXDOMAIN"}, {`07-Oct-2026 19:46:22.217 client @0x71358b8ff000 2001:db8::7#55006 (other.zone): view external net: response: other.zone. IN MX REFUSED 0 0 1 +E(0)K (2001:db8::1)`, true, "2001:db8::7", "external net", "other.zone", "MX", "REFUSED"}, {`07-Oct-2026 19:46:22.217 client @0x71358b8ff000 ::ffff:10.0.0.1#55006 (.): response: . IN NS NOERROR 13 0 27 + (10.0.0.2)`, true, "10.0.0.1", "", ".", "NS", "NOERROR"}, // Query lines and other messages are not responses. {`07-Oct-2026 19:46:22.184 client @0x71358b8ff000 127.0.0.1#53937 (www.example.test): view internal: query: www.example.test IN A +E(0)K (127.0.0.1)`, false, "", "", "", "", ""}, {`07-Oct-2026 19:46:20.000 logging channel 'jarl_responses' file '/var/log/named/jarl-responses.log': rolled`, false, "", "", "", "", ""}, {``, false, "", "", "", "", ""}, } { ev, ok := ParseResponse(tc.line, loc) if ok != tc.ok { t.Errorf("%q: ok = %v", tc.line, ok) continue } if !ok { continue } if ev.Client.String() != tc.client || ev.View != tc.view || ev.Name != tc.name || ev.Type != tc.typ || ev.RCode != tc.rc { t.Errorf("%q: got %+v", tc.line, ev) } if want := time.Date(2026, 10, 7, 19, 46, 22, 0, loc); ev.Time.Truncate(time.Second) != want { t.Errorf("%q: time %v, want %v", tc.line, ev.Time, want) } } } func TestParseRateLimit(t *testing.T) { loc := time.UTC for _, tc := range []struct { line string want RateLimit views bool }{ {line: "12-Oct-2026 09:56:35.996 client @0x70a247adf000 127.0.0.2#59296 (www.rrl.test): would rate limit slip response to 127.0.0.0/24 for www.rrl.test IN A (0ce833a4)", want: RateLimit{Would: true, Action: "slip", Prefix: "127.0.0.0/24", Name: "www.rrl.test", Type: "A"}}, {line: "12-Oct-2026 09:56:38.303 client @0x7eee390df000 127.0.0.2#35687 (www.rrl.test): rate limit drop response to 127.0.0.0/24 for www.rrl.test IN A (0ce833a4)", want: RateLimit{Action: "drop", Prefix: "127.0.0.0/24", Name: "www.rrl.test", Type: "A"}}, {line: "12-Oct-2026 09:56:38.303 client @0x7eee390df000 2001:db8::7#35687 (Adbraun.lol): view external: rate limit drop response to 2001:db8::/56 for adbraun.lol IN ANY (0ce833a4)", want: RateLimit{View: "external", Action: "drop", Prefix: "2001:db8::/56", Name: "adbraun.lol", Type: "ANY"}}, } { rl, ok := ParseRateLimit(tc.line, loc) rl.Time, rl.Client = time.Time{}, netip.Addr{} if !ok || rl != tc.want { t.Errorf("%s:\n got %+v %v\nwant %+v", tc.line, rl, ok, tc.want) } } if _, ok := ParseRateLimit("12-Oct-2026 09:56:35.996 rate-limit: info: would limit responses to 127.0.0.0/24 for www.rrl.test IN A (0ce833a4)", loc); ok { t.Error("the start-of-limiting line counted as a response") } } // Signed queries have "/key NAME" after the client's port. func TestParseSigned(t *testing.T) { loc := time.UTC ev, ok := ParseResponse("12-Oct-2026 10:11:06.490 client @0x7a22f754b000 127.0.0.1#34745/key jarl-view-external (ns1.adbraun.test): view external: response: ns1.adbraun.test IN A NOERROR 1 0 2 +E(0)SK (127.0.0.1)", loc) if !ok || ev.View != "external" || ev.Name != "ns1.adbraun.test" || ev.Port != "34745" { t.Errorf("signed response = %+v %v", ev, ok) } rl, ok := ParseRateLimit("12-Oct-2026 10:11:06.599 client @0x7a22f754b000 127.0.0.1#34856/key jarl-view-external (ns1.adbraun.test): view external: would rate limit slip response to 127.0.0.0/24 for ns1.adbraun.test IN A (e777e288)", loc) if !ok || rl.View != "external" || !rl.Would || rl.Action != "slip" || rl.Prefix != "127.0.0.0/24" { t.Errorf("signed rate limit = %+v %v", rl, ok) } if _, ok := ParseRewrite("09-Oct-2026 23:49:13.089 client @0x7bd607320000 127.0.0.1#56905/key k1 (x.ads.example): view internal: rpz QNAME NXDOMAIN rewrite x.ads.example/A/IN via x.ads.example.rules.jarl.invalid", loc); !ok { t.Error("signed rewrite not parsed") } }