wgengine/magicsock: send a valid payload in TestNetworkDownSendErrors

This test ostensibly checks whether we record an error metric if a packet
is dropped because the network is down, but the network connectivity is
irrelevant -- the send error is actually because the arguments to Send()
are invalid:

    RebindingUDPConn.WriteWireGuardBatchTo:
    [unexpected] offset (0) != Geneve header length (8)

This patch changes the test so we try to send a valid packet, and we
verify this by sending it once before taking the network down.  The new
error is:

    magicsock: network down

which is what we're trying to test.

We then test sending an invalid payload as a separate test case.

Updates tailscale/corp#22075

Signed-off-by: Alex Chan <alexc@tailscale.com>
This commit is contained in:
Alex Chan 2025-09-15 06:53:41 +01:00 committed by Alex Chan
parent 998a667cd5
commit 5c24f0ed80

View File

@ -3131,34 +3131,89 @@ func TestMaybeRebindOnError(t *testing.T) {
}) })
} }
func TestNetworkDownSendErrors(t *testing.T) { func newTestConnAndRegistry(t *testing.T) (*Conn, *usermetric.Registry, func()) {
t.Helper()
bus := eventbus.New() bus := eventbus.New()
defer bus.Close()
netMon := must.Get(netmon.New(bus, t.Logf)) netMon := must.Get(netmon.New(bus, t.Logf))
defer netMon.Close()
reg := new(usermetric.Registry) reg := new(usermetric.Registry)
conn := must.Get(NewConn(Options{ conn := must.Get(NewConn(Options{
DisablePortMapper: true, DisablePortMapper: true,
Logf: t.Logf, Logf: t.Logf,
NetMon: netMon, NetMon: netMon,
Metrics: reg,
EventBus: bus, EventBus: bus,
Metrics: reg,
})) }))
defer conn.Close()
conn.SetNetworkUp(false) return conn, reg, func() {
if err := conn.Send([][]byte{{00}}, &lazyEndpoint{}, 0); err == nil { bus.Close()
t.Error("expected error, got nil") netMon.Close()
} conn.Close()
resp := httptest.NewRecorder()
reg.Handler(resp, new(http.Request))
if !strings.Contains(resp.Body.String(), `tailscaled_outbound_dropped_packets_total{reason="error"} 1`) {
t.Errorf("expected NetworkDown to increment packet dropped metric; got %q", resp.Body.String())
} }
} }
func TestNetworkSendErrors(t *testing.T) {
t.Run("network-down", func(t *testing.T) {
// TODO(alexc): This test case fails on Windows because it never
// successfully sends the first packet:
//
// expected successful Send, got err: "write udp4 0.0.0.0:57516->127.0.0.1:9999:
// wsasendto: The requested address is not valid in its context."
//
// It would be nice to run this test on Windows, but I was already
// on a side quest and it was unclear if this test has ever worked
// correctly on Windows.
if runtime.GOOS == "windows" {
t.Skipf("skipping on %s", runtime.GOOS)
}
conn, reg, close := newTestConnAndRegistry(t)
defer close()
buffs := [][]byte{{00, 00, 00, 00, 00, 00, 00, 00}}
ep := &lazyEndpoint{
src: epAddr{ap: netip.MustParseAddrPort("127.0.0.1:9999")},
}
offset := 8
// Check this is a valid payload to send when the network is up
conn.SetNetworkUp(true)
if err := conn.Send(buffs, ep, offset); err != nil {
t.Errorf("expected successful Send, got err: %q", err)
}
// Now we know the payload would be sent if the network is up,
// send it again when the network is down
conn.SetNetworkUp(false)
err := conn.Send(buffs, ep, offset)
if err == nil {
t.Error("expected error, got nil")
}
resp := httptest.NewRecorder()
reg.Handler(resp, new(http.Request))
if !strings.Contains(resp.Body.String(), `tailscaled_outbound_dropped_packets_total{reason="error"} 1`) {
t.Errorf("expected NetworkDown to increment packet dropped metric; got %q", resp.Body.String())
}
})
t.Run("invalid-payload", func(t *testing.T) {
conn, reg, close := newTestConnAndRegistry(t)
defer close()
conn.SetNetworkUp(false)
err := conn.Send([][]byte{{00}}, &lazyEndpoint{}, 0)
if err == nil {
t.Error("expected error, got nil")
}
resp := httptest.NewRecorder()
reg.Handler(resp, new(http.Request))
if !strings.Contains(resp.Body.String(), `tailscaled_outbound_dropped_packets_total{reason="error"} 1`) {
t.Errorf("expected invalid payload to increment packet dropped metric; got %q", resp.Body.String())
}
})
}
func Test_packetLooksLike(t *testing.T) { func Test_packetLooksLike(t *testing.T) {
discoPub := key.DiscoPublicFromRaw32(mem.B([]byte{1: 1, 30: 30, 31: 31})) discoPub := key.DiscoPublicFromRaw32(mem.B([]byte{1: 1, 30: 30, 31: 31}))
nakedDisco := make([]byte, 0, 512) nakedDisco := make([]byte, 0, 512)