diff --git a/newt/newt_test.go b/newt/newt_test.go index 26951ab..16c25d2 100644 --- a/newt/newt_test.go +++ b/newt/newt_test.go @@ -342,6 +342,17 @@ func TestParseTargetStringNetDialCompatibility(t *testing.T) { } } +// TestPingNilNetstackReturnsError is the regression guard for fosrl/newt#439: +// a still-running ping goroutine calling ping() with a nil *netstack.Net +// (after closeWgTunnel clears it during teardown/reconnect) must return an +// error instead of panicking with a nil pointer dereference. +func TestPingNilNetstackReturnsError(t *testing.T) { + _, err := ping(nil, "127.0.0.1", 100*time.Millisecond) + if err == nil { + t.Fatal("expected error when tnet is nil, got nil") + } +} + // TestShouldFireRecovery is the regression guard for the broken trigger gate // that prevented data-plane recovery from ever firing under default settings // (fosrl/newt#284, #310, pangolin#1004). diff --git a/newt/ping.go b/newt/ping.go index 4126231..4fe9b2f 100644 --- a/newt/ping.go +++ b/newt/ping.go @@ -47,6 +47,10 @@ func pingNative(dst string, timeout time.Duration) (time.Duration, error) { } func ping(tnet *netstack.Net, dst string, timeout time.Duration) (time.Duration, error) { + if tnet == nil { + return 0, fmt.Errorf("netstack not initialized") + } + socket, err := tnet.Dial("ping4", dst) if err != nil { return 0, fmt.Errorf("failed to create ICMP socket: %w", err)