p1.1: dynamic self-IP deny (DDNS/trace, keep previous, atomic swap)

Discover WAN via ddns:<host> then trace:<url> before listen. Persist to
self_ip.json. Previous addresses stay denied for keep_previous. Failure
keeps the last known set. /healthz reports age and source.
This commit is contained in:
2026-09-22 22:59:08 -07:00
parent dca635fb62
commit 447842f860
8 changed files with 767 additions and 20 deletions
+37
View File
@@ -26,6 +26,7 @@ import (
"net/url"
"strconv"
"strings"
"sync/atomic"
"syscall"
"time"
)
@@ -45,6 +46,9 @@ type Guard struct {
// AllowPrefixes bypasses the predicate. Production config never sets this;
// only tests and the sifttest-tagged -test-allow-prefix flag do.
AllowPrefixes []netip.Prefix
// self is the discovered WAN deny set (v4 /32, v6 /64), swapped atomically.
self atomic.Pointer[[]netip.Prefix]
}
func (g *Guard) allows(addr netip.Addr) bool {
@@ -273,6 +277,13 @@ func (g *Guard) CheckAddr(addr netip.Addr) error {
return blockedAddr("denied prefix", addr)
}
}
if extras := g.self.Load(); extras != nil {
for _, p := range *extras {
if p.Contains(addr) {
return blockedAddr("denied prefix", addr)
}
}
}
}
switch {
@@ -436,3 +447,29 @@ func (g *Guard) Client() *http.Client {
Timeout: 20 * time.Second,
}
}
// SetSelfDeny atomically replaces the discovered WAN deny prefixes.
// A nil or empty list clears the extra set; static DenyPrefixes are unchanged.
func (g *Guard) SetSelfDeny(prefixes []netip.Prefix) {
if g == nil {
return
}
if len(prefixes) == 0 {
g.self.Store(nil)
return
}
cp := append([]netip.Prefix(nil), prefixes...)
g.self.Store(&cp)
}
// SelfDeny returns a copy of the current discovered deny prefixes.
func (g *Guard) SelfDeny() []netip.Prefix {
if g == nil {
return nil
}
p := g.self.Load()
if p == nil {
return nil
}
return append([]netip.Prefix(nil), *p...)
}