tstest/natlab: add test for no control and rotated disco key

Updates #12639

Signed-off-by: Claus Lensbøl <claus@tailscale.com>
This commit is contained in:
Claus Lensbøl
2025-12-19 12:14:49 -05:00
parent 2f03518e3c
commit 1af9a75732
3 changed files with 72 additions and 9 deletions

View File

@@ -133,6 +133,17 @@ func easyAnd6(c *vnet.Config) *vnet.Node {
vnet.EasyNAT))
}
func easyNoControlDiscoRotate(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.SetPostConnectControlBlackhole(true)
return c.AddNode(vnet.RotateDisco, nw)
}
func v6AndBlackholedIPv4(c *vnet.Config) *vnet.Node {
n := c.NumNodes() + 1
nw := c.AddNetwork(
@@ -364,7 +375,9 @@ func (nt *natTest) runTest(addNode ...addNodeFunc) pingRoute {
var clients []*vnet.NodeAgentClient
for _, n := range nodes {
clients = append(clients, nt.vnet.NodeAgentClient(n))
client := nt.vnet.NodeAgentClient(n)
n.SetClient(client)
clients = append(clients, client)
}
sts := make([]*ipnstate.Status, len(nodes))
@@ -415,6 +428,13 @@ func (nt *natTest) runTest(addNode ...addNodeFunc) pingRoute {
return ""
}
for _, node := range c.Nodes() {
node.Network().PostConnectedToControl()
if err := node.PostConnectedToControl(ctx); err != nil {
t.Fatalf("post control error: %s", err)
}
}
pingRes, err := ping(ctx, clients[0], sts[1].Self.TailscaleIPs[0])
if err != nil {
t.Fatalf("ping failure: %v", err)
@@ -526,6 +546,12 @@ func TestEasyEasy(t *testing.T) {
nt.want(routeDirect)
}
func TestEasyEasyNoControlDiscoRotate(t *testing.T) {
nt := newNatTest(t)
nt.runTest(easy, easyNoControlDiscoRotate)
nt.want(routeDirect)
}
// Issue tailscale/corp#26438: use learned DERP route as send path of last
// resort
//

View File

@@ -5,6 +5,7 @@ package vnet
import (
"cmp"
"context"
"fmt"
"iter"
"net/netip"
@@ -114,6 +115,8 @@ func (c *Config) AddNode(opts ...any) *Node {
switch o {
case HostFirewall:
n.hostFW = true
case RotateDisco:
n.rotateDisco = true
case VerboseSyslog:
n.verboseSyslog = true
default:
@@ -137,6 +140,7 @@ type NodeOption string
const (
HostFirewall NodeOption = "HostFirewall"
RotateDisco NodeOption = "RotateDisco"
VerboseSyslog NodeOption = "VerboseSyslog"
)
@@ -197,12 +201,14 @@ func (c *Config) AddNetwork(opts ...any) *Network {
// Node is the configuration of a node in the virtual network.
type Node struct {
err error
num int // 1-based node number
n *node // nil until NewServer called
err error
num int // 1-based node number
n *node // nil until NewServer called
client *NodeAgentClient
env []TailscaledEnv
hostFW bool
rotateDisco bool
verboseSyslog bool
// TODO(bradfitz): this is halfway converted to supporting multiple NICs
@@ -243,6 +249,19 @@ func (n *Node) SetVerboseSyslog(v bool) {
n.verboseSyslog = v
}
func (n *Node) SetClient(c *NodeAgentClient) {
n.client = c
}
func (n *Node) PostConnectedToControl(ctx context.Context) error {
if n.rotateDisco {
if err := n.client.DebugAction(ctx, "rotate-disco-key"); err != nil {
return err
}
}
return nil
}
// IsV6Only reports whether this node is only connected to IPv6 networks.
func (n *Node) IsV6Only() bool {
for _, net := range n.nets {
@@ -275,10 +294,12 @@ 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
breakWAN4 bool // whether to break WAN IPv4 connectivity
wanIP4 netip.Addr // IPv4 WAN IP, if any
lanIP4 netip.Prefix
nodes []*Node
breakWAN4 bool // whether to break WAN IPv4 connectivity
postConnectBreakControl bool // whether to break control connectivity after noces have connected
network *network
svcs set.Set[NetworkService]
@@ -310,6 +331,10 @@ func (n *Network) SetBlackholedIPv4(v bool) {
n.breakWAN4 = v
}
func (n *Network) SetPostConnectControlBlackhole(v bool) {
n.postConnectBreakControl = v
}
func (n *Network) CanV4() bool {
return n.lanIP4.IsValid() || n.wanIP4.IsValid()
}
@@ -325,6 +350,10 @@ func (n *Network) CanTakeMoreNodes() bool {
return len(n.nodes) < 150
}
func (n *Network) PostConnectedToControl() {
n.network.BreakControl(n.postConnectBreakControl)
}
// NetworkService is a service that can be added to a network.
type NetworkService string
@@ -390,6 +419,8 @@ func (s *Server) initFromConfig(c *Config) error {
}
netOfConf[conf] = n
s.networks.Add(n)
conf.network = n
if conf.wanIP4.IsValid() {
if conf.wanIP4.Is6() {
return fmt.Errorf("invalid IPv6 address in wanIP")

View File

@@ -518,6 +518,7 @@ type network struct {
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
breakControl bool // break control connectivity
latency time.Duration // latency applied to interface writes
lossRate float64 // probability of dropping a packet (0.0 to 1.0)
nodesByIP4 map[netip.Addr]*node // by LAN IPv4
@@ -578,6 +579,10 @@ func (n *network) MACOfIP(ip netip.Addr) (_ MAC, ok bool) {
return MAC{}, false
}
func (n *network) BreakControl(v bool) {
n.breakControl = v
}
type node struct {
mac MAC
num int // 1-based node number
@@ -1263,7 +1268,8 @@ func (n *network) HandleEthernetPacketForRouter(ep EthernetPacket) {
}
if toForward && n.s.shouldInterceptTCP(packet) {
if flow.dst.Is4() && n.breakWAN4 {
if (flow.dst.Is4() && n.breakWAN4) ||
(fakeControl.Match(flow.dst) && n.breakControl) {
// Blackhole the packet.
return
}