From c4d0237e5cae9cdd5933aae99e76b85a35f9c6f2 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 3 Sep 2024 15:00:18 -0700 Subject: [PATCH] tstest/natlab: add dual stack with blackholed IPv4 This reproduces the bug report from https://github.com/tailscale/tailscale/issues/13346 It does not yet fix it. Updates #13346 Change-Id: Ia5af7b0481a64a37efe259c798facdda6d9da618 Signed-off-by: Brad Fitzpatrick --- tstest/integration/nat/nat_test.go | 25 +++++++++++++++++++++++++ tstest/natlab/vnet/conf.go | 14 +++++++++++--- tstest/natlab/vnet/vnet.go | 13 +++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/tstest/integration/nat/nat_test.go b/tstest/integration/nat/nat_test.go index 0a5dc7828..a8c20d6bd 100644 --- a/tstest/integration/nat/nat_test.go +++ b/tstest/integration/nat/nat_test.go @@ -115,6 +115,17 @@ func easyAnd6(c *vnet.Config) *vnet.Node { vnet.EasyNAT)) } +func v6AndBlackholedIPv4(c *vnet.Config) *vnet.Node { + n := c.NumNodes() + 1 + nw := c.AddNetwork( + fmt.Sprintf("2.%d.%d.%d", n, n, n), // public IP + fmt.Sprintf("192.168.%d.1/24", n), + v6cidr(n), + vnet.EasyNAT) + nw.SetBlackholedIPv4(true) + return c.AddNode(nw) +} + func just6(c *vnet.Config) *vnet.Node { n := c.NumNodes() + 1 return c.AddNode(c.AddNetwork(v6cidr(n))) // public IPv6 prefix @@ -482,6 +493,20 @@ func TestSingleJustIPv6(t *testing.T) { nt.runTest(just6) } +var knownBroken = flag.Bool("known-broken", false, "run known-broken tests") + +// TestSingleDualStackButBrokenIPv4 tests a dual-stack node with broken +// (blackholed) IPv4. +// +// See https://github.com/tailscale/tailscale/issues/13346 +func TestSingleDualBrokenIPv4(t *testing.T) { + if !*knownBroken { + t.Skip("skipping known-broken test; set --known-broken to run; see https://github.com/tailscale/tailscale/issues/13346") + } + nt := newNatTest(t) + nt.runTest(v6AndBlackholedIPv4) +} + func TestJustIPv6(t *testing.T) { nt := newNatTest(t) nt.runTest(just6, just6) diff --git a/tstest/natlab/vnet/conf.go b/tstest/natlab/vnet/conf.go index a92014ae6..cf71a6674 100644 --- a/tstest/natlab/vnet/conf.go +++ b/tstest/natlab/vnet/conf.go @@ -272,9 +272,10 @@ type Network struct { wanIP6 netip.Prefix // global unicast router in host bits; CIDR is /64 delegated to LAN - wanIP4 netip.Addr // IPv4 WAN IP, if any - lanIP4 netip.Prefix - nodes []*Node + wanIP4 netip.Addr // IPv4 WAN IP, if any + lanIP4 netip.Prefix + nodes []*Node + breakWAN4 bool // whether to break WAN IPv4 connectivity svcs set.Set[NetworkService] @@ -282,6 +283,12 @@ type Network struct { err error // carried error } +// SetBlackholedIPv4 sets whether the network should blackhole all IPv4 traffic +// out to the Internet. (DHCP etc continues to work on the LAN.) +func (n *Network) SetBlackholedIPv4(v bool) { + n.breakWAN4 = v +} + func (n *Network) CanV4() bool { return n.lanIP4.IsValid() || n.wanIP4.IsValid() } @@ -353,6 +360,7 @@ func (s *Server) initFromConfig(c *Config) error { v6: conf.wanIP6.IsValid(), wanIP4: conf.wanIP4, lanIP4: conf.lanIP4, + breakWAN4: conf.breakWAN4, nodesByIP4: map[netip.Addr]*node{}, nodesByMAC: map[MAC]*node{}, logf: logger.WithPrefix(s.logf, fmt.Sprintf("[net-%v] ", conf.mac)), diff --git a/tstest/natlab/vnet/vnet.go b/tstest/natlab/vnet/vnet.go index b8203f867..919ae1fa1 100644 --- a/tstest/natlab/vnet/vnet.go +++ b/tstest/natlab/vnet/vnet.go @@ -514,6 +514,7 @@ type network struct { wanIP6 netip.Prefix // router's WAN IPv6, if any, as a /64. wanIP4 netip.Addr // router's LAN IPv4, if any lanIP4 netip.Prefix // router's LAN IP + CIDR (e.g. 192.168.2.1/24) + breakWAN4 bool // break WAN IPv4 connectivity nodesByIP4 map[netip.Addr]*node // by LAN IPv4 nodesByMAC map[MAC]*node logf func(format string, args ...any) @@ -1104,6 +1105,10 @@ func (n *network) HandleUDPPacket(p UDPPacket) { Length: len(buf), InterfaceIndex: n.wanInterfaceID, }, buf) + if p.Dst.Addr().Is4() && n.breakWAN4 { + // Blackhole the packet. + return + } dst := n.doNATIn(p.Src, p.Dst) if !dst.IsValid() { n.logf("Warning: NAT dropped packet; no mapping for %v=>%v", p.Src, p.Dst) @@ -1239,6 +1244,10 @@ func (n *network) HandleEthernetPacketForRouter(ep EthernetPacket) { } if toForward && n.s.shouldInterceptTCP(packet) { + if flow.dst.Is4() && n.breakWAN4 { + // Blackhole the packet. + return + } var base *layers.BaseLayer proto := header.IPv4ProtocolNumber if v4, ok := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4); ok { @@ -1324,6 +1333,10 @@ func (n *network) handleUDPPacketForRouter(ep EthernetPacket, udp *layers.UDP, t } if toForward { + if dstIP.Is4() && n.breakWAN4 { + // Blackhole the packet. + return + } src := netip.AddrPortFrom(srcIP, uint16(udp.SrcPort)) dst := netip.AddrPortFrom(dstIP, uint16(udp.DstPort)) buf, err := n.serializedUDPPacket(src, dst, udp.Payload, nil)