package poke import ( "context" "io" "log/slog" "net" "net/netip" "sync/atomic" "testing" "time" ) func discard() *slog.Logger { return slog.New(slog.NewTextHandler(io.Discard, nil)) } // waitFor polls cond up to d, returning true if it became true. func waitFor(d time.Duration, cond func() bool) bool { deadline := time.Now().Add(d) for time.Now().Before(deadline) { if cond() { return true } time.Sleep(2 * time.Millisecond) } return cond() } // TestBindConfinement is red-proof (a): the listener binds EXCLUSIVELY to the address the resolver // returns (the box's WG /32), never the wildcard 0.0.0.0/::. A wildcard bind would ALSO accept the // LAN address — the exact leak the confinement forbids. We assert the bound socket's local address // IS the specific resolved address and is NOT unspecified, and that a datagram to it fires the // trigger. RED-PROOF: a 0.0.0.0 bind makes BoundAddrPort().Addr().IsUnspecified() true → FAIL. func TestBindConfinement(t *testing.T) { want := netip.MustParseAddr("127.0.0.1") // stand-in for the WG /32 on a portable loopback var fires atomic.Int32 l := NewListener( func() (netip.Addr, bool) { return want, true }, func() { fires.Add(1) }, 0, // OS-chosen port so the test never collides discard(), ) ctx, cancel := context.WithCancel(context.Background()) defer cancel() go l.Run(ctx) bctx, bcancel := context.WithTimeout(context.Background(), 3*time.Second) defer bcancel() bound, ok := l.BoundAddrPort(bctx) if !ok { t.Fatal("listener never bound") } if bound.Addr() != want { t.Fatalf("bound to %s, want the specific WG addr %s (a wildcard bind would leak to the LAN)", bound.Addr(), want) } if bound.Addr().IsUnspecified() { t.Fatal("listener bound to the wildcard address (0.0.0.0/::) — NOT confined to the WG /32") } // Functional: a datagram to the WG address fires exactly one trigger. conn, err := net.Dial("udp", bound.String()) if err != nil { t.Fatalf("dial: %v", err) } defer conn.Close() if _, err := conn.Write([]byte("")); err != nil { // contentless t.Fatalf("write poke: %v", err) } if !waitFor(2*time.Second, func() bool { return fires.Load() >= 1 }) { t.Fatal("a poke to the bound WG address did not fire the trigger") } } // TestDebounceCoalescesBurst is red-proof (b): a burst of M pokes within DebounceWindow yields ≤1 // trigger; after the window elapses a further poke fires again. onPoke uses an injected clock so // the test is deterministic. RED-PROOF: remove the leading-edge guard (fire every time) → M fires // for M pokes → the "want 1" assertion FAILS. func TestDebounceCoalescesBurst(t *testing.T) { var fires atomic.Int32 l := NewListener(nil, func() { fires.Add(1) }, 0, discard()) base := time.Unix(1_700_000_000, 0) var clock atomic.Int64 clock.Store(base.UnixNano()) l.now = func() time.Time { return time.Unix(0, clock.Load()) } // A burst of 10 pokes at the SAME instant → exactly one fire. const burst = 10 for i := 0; i < burst; i++ { l.onPoke() } if got := fires.Load(); got != 1 { t.Fatalf("burst of %d pokes fired %d triggers, want exactly 1 (debounce coalescing)", burst, got) } // Still inside the window → no additional fire. clock.Store(base.Add(l.window - time.Millisecond).UnixNano()) l.onPoke() if got := fires.Load(); got != 1 { t.Fatalf("a poke inside the debounce window fired again (total %d), want still 1", got) } // Window elapsed → the next poke fires (immediacy is not lost after the window). clock.Store(base.Add(l.window + time.Millisecond).UnixNano()) l.onPoke() if got := fires.Load(); got != 2 { t.Fatalf("a poke after the window did not fire (total %d), want 2", got) } } // TestRunWaitsForRegistration: with no WG address yet, Run keeps retrying (never returns early, // never binds) — a lost/absent poke path is harmless; the 15-min cycle still reconciles. func TestRunWaitsForRegistration(t *testing.T) { var resolved atomic.Bool l := NewListener( func() (netip.Addr, bool) { if resolved.Load() { return netip.MustParseAddr("127.0.0.1"), true } return netip.Addr{}, false }, func() {}, 0, discard(), ) ctx, cancel := context.WithCancel(context.Background()) defer cancel() done := make(chan struct{}) go func() { l.Run(ctx); close(done) }() // Not bound while unregistered. nb, nbcancel := context.WithTimeout(context.Background(), 200*time.Millisecond) defer nbcancel() if _, ok := l.BoundAddrPort(nb); ok { t.Fatal("listener bound before the box registered") } // Registration appears → it binds within a rebind cycle. resolved.Store(true) bctx, bcancel := context.WithTimeout(context.Background(), 3*rebindInterval) defer bcancel() if _, ok := l.BoundAddrPort(bctx); !ok { t.Fatal("listener never bound after registration appeared") } cancel() select { case <-done: case <-time.After(2 * time.Second): t.Fatal("Run did not exit on ctx cancel") } }