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:
mrambossek
2026-08-02 13:04:54 +02:00
co-authored by Claude Opus 5
parent f6849f8e6a
commit 8118e213ae
26 changed files with 1390 additions and 61 deletions
+19 -1
View File
@@ -46,6 +46,7 @@ import (
"echo-lot.app/server/internal/control"
"echo-lot.app/server/internal/dataplane"
"echo-lot.app/server/internal/oidc"
"echo-lot.app/server/internal/ratelimit"
"echo-lot.app/server/internal/runs"
"echo-lot.app/server/internal/selftest"
"echo-lot.app/server/internal/selfupdate"
@@ -205,6 +206,16 @@ func serve(cfg *config.Config) error {
sessions := session.NewManager(15 * time.Minute)
dp := &dataplane.Server{Sessions: sessions}
// Spec §2.5 ceilings. Control plane answers 429; the data plane drops silently. 0 = off.
if cfg.RateUDPPps > 0 {
pps := float64(cfg.RateUDPPps)
// Burst of two seconds' worth: a 5000-packet train arrives as one burst by design.
dp.PacketRate = ratelimit.New(pps, 2*pps)
}
if cfg.RateUDPKbps > 0 {
bytesPerSec := float64(cfg.RateUDPKbps) * 125 // kbps -> bytes/s
dp.ByteRate = ratelimit.New(bytesPerSec, bytesPerSec)
}
// TCP echo shares the control cert for its elt-echo TLS variant.
tcpSrv := &tcpecho.Server{
TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS12},
@@ -305,6 +316,12 @@ func serve(cfg *config.Config) error {
ctl.FragSend = dp.FragSend
}
ctl.DownThroughput = dp.DownThroughput
if cfg.RateSessionsPerMin > 0 {
ctl.RateSessions = ratelimit.New(float64(cfg.RateSessionsPerMin)/60, float64(cfg.RateSessionsPerMin))
}
if cfg.RateActionsPerMin > 0 {
ctl.RateActions = ratelimit.New(float64(cfg.RateActionsPerMin)/60, float64(cfg.RateActionsPerMin))
}
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
@@ -598,7 +615,8 @@ func serve(cfg *config.Config) error {
var dnsTCP []net.Listener
if dnsAddrs := config.Addrs(cfg.DNSListen); len(dnsAddrs) > 0 && cfg.CanaryZone != "" {
v4, v6 := firstByFamily(dnsAddrs)
cd := canarydns.New(cfg.CanaryZone, cfg.Name, v4, v6)
cd := canarydns.New(cfg.CanaryZone, cfg.Name, v4, v6,
time.Duration(cfg.DNSLogRetentionH)*time.Hour)
for _, addr := range dnsAddrs {
ua, err := net.ResolveUDPAddr("udp", addr)
if err != nil {