server: self-test — sysctl audit + egress-MTU self-proof ("server proven good")
server-release / image (push) Successful in 15s
server-test / test (push) Successful in 27s
server-release / release (push) Successful in 28s

A measurement server must prove its own host isn't distorting results:
- sysctl audit (/proc/sys): flags accept_ra on a static host, ICMP
  redirects, ICMP rate-limiting of the server's own errors, and disabled
  TCP options — each a measurement-fidelity hazard, with the "why".
- egress-MTU self-proof: DF PMTUD probe (IP_MTU_DISCOVER + getsockopt
  IP_MTU, no root — Linux-only, stub elsewhere) to external anchors. If the
  server's own uplink is below 1500, client MTU tests measure THIS server,
  so we say so.
Exposed at GET /admin/selftest (full report) and as server_selftest
{mtu_ok, sysctl_ok} in the profile so clients can trust or skip MTU tests.
Recommended deploy/99-echolot-sysctl.conf + README section.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mrambossek
2026-07-31 20:44:38 +02:00
co-authored by Claude Opus 5
parent c9e0d06ea2
commit 4ae744aae5
8 changed files with 359 additions and 4 deletions
+126
View File
@@ -0,0 +1,126 @@
// SPDX-FileCopyrightText: 2026 Echolot contributors
// SPDX-License-Identifier: GPL-3.0-or-later
// Package selftest lets the daemon prove its own host is a clean measurement
// target: the kernel isn't silently altering what clients measure, and the
// server's own egress reaches full MTU. If the server side is already broken,
// client-side results (especially MTU/PMTUD) measure the server, not the
// client — so the daemon says so.
package selftest
import (
"os"
"strconv"
"strings"
)
// Severity of a check result.
type Severity string
const (
OK Severity = "ok"
Warn Severity = "warn"
)
// Check is one sysctl (or derived) assertion.
type Check struct {
Name string `json:"name"`
Got string `json:"got"`
Want string `json:"want"`
Severity Severity `json:"severity"`
Why string `json:"why"`
}
// MTUResult is one egress path-MTU probe outcome.
type MTUResult struct {
Target string `json:"target"`
DiscoveredMTU int `json:"discovered_mtu"`
FullMTU bool `json:"full_mtu"` // >= 1500
Err string `json:"err,omitempty"`
}
// Report is the whole self-test.
type Report struct {
Sysctls []Check `json:"sysctls"`
EgressMTU []MTUResult `json:"egress_mtu"`
// SysctlOK / MTUOK are the compact "server proven good" signals; the
// profile surfaces these so a client can skip MTU tests the server can't
// support honestly.
SysctlOK bool `json:"sysctl_ok"`
MTUOK bool `json:"mtu_ok"`
}
// readSysctl reads /proc/sys/<dotted.name>. Empty string if unavailable.
func readSysctl(name string) string {
p := "/proc/sys/" + strings.ReplaceAll(name, ".", "/")
b, err := os.ReadFile(p)
if err != nil {
return ""
}
return strings.TrimSpace(string(b))
}
// sysctlChecks are the measurement-fidelity assertions. Each closure returns
// OK/Warn given the read value; a missing value (non-Linux / restricted) is
// reported as Warn "unreadable" but never fatal.
var sysctlChecks = []struct {
name string
want string
why string
ok func(v string) bool
}{
{"net.ipv6.conf.all.accept_ra", "0", "static v6 host must not let RAs mutate routing (the very thing Echolot detects)", eq("0")},
{"net.ipv4.conf.all.accept_redirects", "0", "ICMP redirects could alter routing mid-measurement", eq("0")},
{"net.ipv4.conf.all.send_redirects", "0", "an endpoint should not emit ICMP redirects", eq("0")},
{"net.ipv4.icmp_echo_ignore_all", "0", "server must answer ping so clients can measure to it", eq("0")},
{"net.ipv4.ip_no_pmtu_disc", "0", "server must honor path MTU on its own sends", eq("0")},
{"net.ipv4.tcp_sack", "1", "so a missing SACK in mss_observed is the path's fault, not the server's", eq("1")},
{"net.ipv4.tcp_timestamps", "1", "so TCP-timestamp absence reflects the path, not the server", eq("1")},
{"net.ipv4.tcp_window_scaling", "1", "so wscale absence reflects the path, not the server", eq("1")},
{"net.ipv4.icmp_ratelimit", "0", "nonzero throttles the server's ICMP errors → false loss/black-hole readings", eq("0")},
}
func eq(want string) func(string) bool { return func(v string) bool { return v == want } }
// Sysctls runs the sysctl audit.
func Sysctls() []Check {
out := make([]Check, 0, len(sysctlChecks))
for _, c := range sysctlChecks {
got := readSysctl(c.name)
sev := Warn
switch {
case got == "":
got = "(unreadable)"
case c.ok(got):
sev = OK
}
out = append(out, Check{Name: c.name, Got: got, Want: c.want, Severity: sev, Why: c.why})
}
return out
}
// Run performs the full self-test: sysctl audit + egress MTU probes to the
// given targets (each "host" — port is irrelevant for PMTUD).
func Run(mtuTargets []string) Report {
r := Report{Sysctls: Sysctls()}
r.SysctlOK = true
for _, c := range r.Sysctls {
if c.Severity == Warn {
r.SysctlOK = false
}
}
r.MTUOK = true
for _, t := range mtuTargets {
res := probeEgressMTU(t)
r.EgressMTU = append(r.EgressMTU, res)
if !res.FullMTU {
r.MTUOK = false
}
}
if len(r.EgressMTU) == 0 {
r.MTUOK = false // couldn't prove it
}
return r
}
var _ = strconv.Atoi