server: upstream trains, observed TTL/DSCP/ECN, rate limits, action ids
Types 0x03/0x04/0x05 land with a bounded columnar train buffer (head kept, truncation declared) and grant-free multi-part reports - a report row is smaller than the packet it answers, so $3.4 holds without a grant. The read loop now collects TTL/TOS cmsgs on Linux, replacing the 0xFF stubs in the observation block with what the kernel saw; downtrain gained a dscp parameter, so DSCP survival is measurable in both directions. Rate limiting ($2.5) exists now: per-credential AND per-source buckets, 429 on the control plane, silent drop on the data plane after the HMAC gate and before the replay window. UDP ceilings default above the largest legitimate run - a limit that clips a real measurement produces a confidently wrong number. Every granted packet carries its action_id at payload[8:16]; overlapping actions were unattributable before. Canary DNS logs now honor the stated 24h privacy default. /admin/enroll-tokens answers the spec's JSON shape. protocol_version 1.0.1 (additive). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f6849f8e6a
commit
8118e213ae
@@ -8,6 +8,7 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// buildQuery makes a single-question DNS query, optionally with an EDNS OPT.
|
||||
@@ -65,7 +66,7 @@ func parseResponse(t *testing.T, resp []byte) (flags uint16, answers []ans) {
|
||||
}
|
||||
|
||||
func newTestServer() *Server {
|
||||
return New("c.echo-lot.app", "fmr", netip.MustParseAddr("192.0.2.1"), netip.MustParseAddr("2001:db8::1"))
|
||||
return New("c.echo-lot.app", "fmr", netip.MustParseAddr("192.0.2.1"), netip.MustParseAddr("2001:db8::1"), 24*time.Hour)
|
||||
}
|
||||
|
||||
func TestReferenceRecords(t *testing.T) {
|
||||
@@ -159,3 +160,29 @@ func TestOutOfZoneNXDomain(t *testing.T) {
|
||||
t.Fatalf("out-of-zone should be NXDOMAIN, flags=%#x", flags)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryLogRetentionForgetsOldEntries(t *testing.T) {
|
||||
s := newTestServer() // 24 h retention
|
||||
now := time.Now().UTC()
|
||||
s.record(Query{QName: "old.sess1.c.echo-lot.app", At: now.Add(-25 * time.Hour)})
|
||||
s.record(Query{QName: "fresh.sess1.c.echo-lot.app", At: now})
|
||||
|
||||
got := s.RecentForPrefix("sess1")
|
||||
if len(got) != 1 || got[0].QName != "fresh.sess1.c.echo-lot.app" {
|
||||
t.Fatalf("retention not enforced: %+v", got)
|
||||
}
|
||||
|
||||
// Reads must age the log too: an idle server still has to forget on schedule.
|
||||
s.log[0].At = now.Add(-25 * time.Hour)
|
||||
if got := s.RecentForPrefix("sess1"); len(got) != 0 {
|
||||
t.Fatalf("read path did not expire entries: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestZeroRetentionKeepsEverything(t *testing.T) {
|
||||
s := New("c.echo-lot.app", "fmr", netip.MustParseAddr("192.0.2.1"), netip.MustParseAddr("2001:db8::1"), 0)
|
||||
s.record(Query{QName: "ancient.sess1.c.echo-lot.app", At: time.Now().UTC().Add(-1000 * time.Hour)})
|
||||
if got := s.RecentForPrefix("sess1"); len(got) != 1 {
|
||||
t.Fatal("retention 0 must mean 'ring cap only', not 'keep nothing'")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user