From a529739a29815ff7abbdbc537af6835b3d650424 Mon Sep 17 00:00:00 2001 From: chaosinthecrd Date: Mon, 16 Mar 2026 17:03:48 +0100 Subject: [PATCH] wgengine/netstack: add tests Signed-off-by: chaosinthecrd --- wgengine/netstack/netstack_test.go | 188 +++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/wgengine/netstack/netstack_test.go b/wgengine/netstack/netstack_test.go index da262fc13..e280ea5ac 100644 --- a/wgengine/netstack/netstack_test.go +++ b/wgengine/netstack/netstack_test.go @@ -453,6 +453,194 @@ func TestShouldProcessInbound(t *testing.T) { }, want: false, }, + { + name: "udp-on-service-vip-with-listener-ipv4", + pkt: &packet.Parsed{ + IPVersion: 4, + IPProto: ipproto.UDP, + Src: netip.MustParseAddrPort("100.101.102.103:1234"), + Dst: netip.MustParseAddrPort("100.100.100.100:8080"), + }, + beforeStart: func(i *Impl) { + i.ProcessLocalIPs = false + i.ProcessSubnets = false + }, + afterStart: func(i *Impl) { + IPServiceMap := netmap.IPServiceMappings{ + serviceIP: "svc:test-service", + } + i.lb.SetIPServiceMappingsForTest(IPServiceMap) + + i.atomicIsVIPServiceIPFunc.Store(func(addr netip.Addr) bool { + return addr == serviceIP + }) + + // Register the service VIP address on the NIC so gVisor can route to it + protocolAddr := tcpip.ProtocolAddress{ + Protocol: header.IPv4ProtocolNumber, + AddressWithPrefix: tcpip.AddrFrom4(serviceIP.As4()).WithPrefix(), + } + + if err := i.ipstack.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { + t.Fatalf("AddProtocolAddress: %v", err) + } + + // Create a UDP listener on the service VIP + pc, err := gonet.DialUDP(i.ipstack, &tcpip.FullAddress{ + NIC: nicID, + Addr: tcpip.AddrFrom4(serviceIP.As4()), + Port: 8080, + }, nil, header.IPv4ProtocolNumber) + if err != nil { + t.Fatalf("DialUDP: %v", err) + } + t.Cleanup(func() { pc.Close() }) + + i.atomicIsLocalIPFunc.Store(looksLikeATailscaleSelfAddress) + }, + want: true, + }, + { + name: "udp-on-service-vip-no-listener-ipv4", + pkt: &packet.Parsed{ + IPVersion: 4, + IPProto: ipproto.UDP, + Src: netip.MustParseAddrPort("100.101.102.103:1234"), + Dst: netip.MustParseAddrPort("100.100.100.100:9999"), + }, + beforeStart: func(i *Impl) { + i.ProcessLocalIPs = false + i.ProcessSubnets = false + }, + afterStart: func(i *Impl) { + IPServiceMap := netmap.IPServiceMappings{ + serviceIP: "svc:test-service", + } + i.lb.SetIPServiceMappingsForTest(IPServiceMap) + + i.atomicIsVIPServiceIPFunc.Store(func(addr netip.Addr) bool { + return addr == serviceIP + }) + + i.atomicIsLocalIPFunc.Store(looksLikeATailscaleSelfAddress) + }, + want: false, + }, + { + name: "udp-on-service-vip-with-listener-ipv6", + pkt: &packet.Parsed{ + IPVersion: 6, + IPProto: ipproto.UDP, + Src: netip.MustParseAddrPort("[fd7a:115c:a1e0::1]:1234"), + Dst: netip.MustParseAddrPort("[fd7a:115c:a1e0::53]:8080"), + }, + beforeStart: func(i *Impl) { + i.ProcessLocalIPs = false + i.ProcessSubnets = false + }, + afterStart: func(i *Impl) { + IPServiceMap := netmap.IPServiceMappings{ + serviceIPv6: "svc:test-service", + } + i.lb.SetIPServiceMappingsForTest(IPServiceMap) + + i.atomicIsVIPServiceIPFunc.Store(func(addr netip.Addr) bool { + return addr == serviceIPv6 + }) + + protocolAddr := tcpip.ProtocolAddress{ + Protocol: header.IPv6ProtocolNumber, + AddressWithPrefix: tcpip.AddrFrom16(serviceIPv6.As16()).WithPrefix(), + } + if err := i.ipstack.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { + t.Fatalf("AddProtocolAddress: %v", err) + } + + pc, err := gonet.DialUDP(i.ipstack, &tcpip.FullAddress{ + NIC: nicID, + Addr: tcpip.AddrFrom16(serviceIPv6.As16()), + Port: 8080, + }, nil, header.IPv6ProtocolNumber) + if err != nil { + t.Fatalf("DialUDP: %v", err) + } + t.Cleanup(func() { pc.Close() }) + + i.atomicIsLocalIPFunc.Store(looksLikeATailscaleSelfAddress) + }, + want: true, + }, + { + name: "udp-on-service-vip-no-listener-ipv6", + pkt: &packet.Parsed{ + IPVersion: 6, + IPProto: ipproto.UDP, + Src: netip.MustParseAddrPort("[fd7a:115c:a1e0::1]:1234"), + Dst: netip.MustParseAddrPort("[fd7a:115c:a1e0:ffff:ffff:ffff:ffff:fffe]:9999"), // serviceIPv6 + }, + beforeStart: func(i *Impl) { + i.ProcessLocalIPs = false + i.ProcessSubnets = false + }, + afterStart: func(i *Impl) { + IPServiceMap := netmap.IPServiceMappings{ + serviceIPv6: "svc:test-service", + } + i.lb.SetIPServiceMappingsForTest(IPServiceMap) + + i.atomicIsVIPServiceIPFunc.Store(func(addr netip.Addr) bool { + return addr == serviceIPv6 + }) + + i.atomicIsLocalIPFunc.Store(looksLikeATailscaleSelfAddress) + }, + want: false, + }, + { + name: "tcp-on-service-vip-with-udp-listener", + pkt: &packet.Parsed{ + IPVersion: 4, + IPProto: ipproto.TCP, + Src: netip.MustParseAddrPort("100.101.102.103:1234"), + Dst: netip.MustParseAddrPort("100.100.100.100:8080"), // serviceIP + TCPFlags: packet.TCPSyn, + }, + beforeStart: func(i *Impl) { + i.ProcessLocalIPs = false + i.ProcessSubnets = false + }, + afterStart: func(i *Impl) { + IPServiceMap := netmap.IPServiceMappings{ + serviceIP: "svc:test-service", + } + i.lb.SetIPServiceMappingsForTest(IPServiceMap) + + i.atomicIsVIPServiceIPFunc.Store(func(addr netip.Addr) bool { + return addr == serviceIP + }) + + protocolAddr := tcpip.ProtocolAddress{ + Protocol: header.IPv4ProtocolNumber, + AddressWithPrefix: tcpip.AddrFrom4(serviceIP.As4()).WithPrefix(), + } + if err := i.ipstack.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { + t.Fatalf("AddProtocolAddress: %v", err) + } + + pc, err := gonet.DialUDP(i.ipstack, &tcpip.FullAddress{ + NIC: nicID, + Addr: tcpip.AddrFrom4(serviceIP.As4()), + Port: 8080, + }, nil, header.IPv4ProtocolNumber) + if err != nil { + t.Fatalf("DialUDP: %v", err) + } + t.Cleanup(func() { pc.Close() }) + + i.atomicIsLocalIPFunc.Store(looksLikeATailscaleSelfAddress) + }, + want: false, + }, // TODO(andrew): test PeerAPI // TODO(andrew): test TCP packets without the SYN flag set