// SPDX-FileCopyrightText: 2026 Echolot contributors // SPDX-License-Identifier: GPL-3.0-or-later package config import ( "strings" "testing" ) const ( svc4 = "89.185.109.150" res4 = "89.185.109.151" res6 = "2001:1ad0:c4fe:6767::151" ) func withReserved(l ...Listener) error { c := &Config{ReservedAddrs: res4 + "," + res6} return c.CheckReserved(l) } func TestWildcardBindIsRefused(t *testing.T) { // The realistic failure: every listener defaults to ":port", and the next one added gets // copied from an existing default. Nobody decides to claim the reserved address; it just // happens, and nothing looks wrong afterwards. err := withReserved(Listener{Name: "control-listen", Spec: ":8443"}) if err == nil { t.Fatal("a wildcard bind was allowed while addresses were reserved") } if !strings.Contains(err.Error(), "binds every address") { t.Fatalf("the error should say why a wildcard is the problem, got: %v", err) } } func TestWebPortsOnReservedAreRefusedEvenForMeasurement(t *testing.T) { // The strictest rule, and the one carrying the diagnostic value: 80 and 443 must stay closed // on a reserved address whatever wants them, because their closed state *is* the measurement. for _, spec := range []string{res4 + ":443", "[" + res6 + "]:80"} { err := withReserved(Listener{Name: "control-listen", Spec: spec, Measurement: true}) if err == nil { t.Fatalf("port 80/443 on a reserved address was allowed: %q", spec) } if !strings.Contains(err.Error(), "keep closed") { t.Errorf("the error should explain what is lost, got: %v", err) } } } func TestServiceOnReservedIsRefused(t *testing.T) { if err := withReserved(Listener{Name: "admin-listen", Spec: res4 + ":8444"}); err == nil { t.Fatal("a service was allowed onto a reserved address") } } func TestMeasurementListenersBelongOnReserved(t *testing.T) { // The live fmr config: the UDP data plane, canary DNS and STUN all bind the reserved pair on // purpose. A guard that refused this would be describing a rule nobody wants. err := withReserved( Listener{Name: "udp-listen", Spec: res4 + ":8442,[" + res6 + "]:8442", Measurement: true}, Listener{Name: "dns-listen", Spec: res4 + ":53,[" + res6 + "]:53", Measurement: true}, Listener{Name: "stun-listen", Spec: res4 + ":3478", Measurement: true}, ) if err != nil { t.Fatalf("measurement listeners must be allowed on reserved addresses: %v", err) } } func TestServiceAddressesAreFine(t *testing.T) { err := withReserved( Listener{Name: "control-listen", Spec: svc4 + ":8443,[2001:1ad0:c4fe:6767::150]:8443"}, Listener{Name: "admin-listen", Spec: "127.0.0.1:8444"}, ) if err != nil { t.Fatalf("service addresses should be allowed: %v", err) } } func TestHttpEchoIsNotTreatedAsMeasurement(t *testing.T) { // http-echo is cleartext HTTP. On a reserved address it is precisely the listener that would // ruin the port-80 test, so it does not get the measurement exemption. if err := withReserved(Listener{Name: "http-echo-listen", Spec: res4 + ":8080"}); err == nil { t.Fatal("http-echo was allowed onto a reserved address") } } func TestNoReservationMeansNoOpinion(t *testing.T) { // A host with nothing reserved must keep working exactly as before, wildcards included. c := &Config{} if err := c.CheckReserved([]Listener{{Name: "control-listen", Spec: ":8443"}}); err != nil { t.Fatalf("with no reserved addresses this must not interfere: %v", err) } } func TestEveryOffenderIsNamed(t *testing.T) { // Reporting one problem at a time turns a config fix into several restart cycles, and on a // remote host each cycle is a chance to lock yourself out. err := withReserved( Listener{Name: "control-listen", Spec: ":8443"}, Listener{Name: "tcp-listen", Spec: res4 + ":8441"}, ) if err == nil { t.Fatal("expected a refusal") } for _, want := range []string{"control-listen", "tcp-listen"} { if !strings.Contains(err.Error(), want) { t.Errorf("the error should name %s; got: %v", want, err) } } } func TestHostnamesAreNotGuessedAt(t *testing.T) { // Resolving here would check a name against whatever DNS says at startup, which is not // necessarily what it will say later — and a guard that is sometimes right is worse than one // with a stated limit. if err := withReserved(Listener{Name: "control-listen", Spec: "fmr-1.echo-lot.app:8443"}); err != nil { t.Fatalf("a hostname must be left alone, not resolved: %v", err) } } func TestListenersCoversEverySpec(t *testing.T) { // A listener missing from Listeners() is invisible to the guard, which is the one way this // protection fails silently. Fill every spec with the reserved address: each one that is // actually enumerated produces a complaint naming it. // Every spec on port 443 of the reserved address: the web-port rule applies to measurement // listeners too, so each one that is genuinely enumerated must produce a complaint. c := &Config{ ReservedAddrs: res4, ControlListen: res4 + ":443", UDPListen: res4 + ":443", TCPListen: res4 + ":443", DNSListen: res4 + ":443", HTTPEchoListen: res4 + ":443", AdminListen: res4 + ":443", ACMEHTTPListen: res4 + ":443", StunListen: res4 + ":443", } err := c.CheckReserved(c.Listeners()) if err == nil { t.Fatal("expected a refusal") } // Every spec is on the reserved address; the web-port rule catches even the measurement ones, // so anything missing from Listeners() is invisible here and that is what this asserts. for _, want := range []string{ "control-listen", "udp-listen", "tcp-listen", "dns-listen", "http-echo-listen", "admin-listen", "acme-http-listen", "stun-listen", } { if !strings.Contains(err.Error(), want) { t.Errorf("%s is not enumerated in Listeners(), so the guard cannot see it", want) } } }