diff --git a/cmd/sniproxy/sniproxy_test.go b/cmd/sniproxy/sniproxy_test.go index 324445ba4..9fa11b7ab 100644 --- a/cmd/sniproxy/sniproxy_test.go +++ b/cmd/sniproxy/sniproxy_test.go @@ -24,6 +24,7 @@ "tailscale.com/tsnet" "tailscale.com/tstest/integration" "tailscale.com/tstest/integration/testcontrol" + "tailscale.com/tstest/nettest" "tailscale.com/types/appctype" "tailscale.com/types/ipproto" "tailscale.com/types/key" @@ -111,6 +112,7 @@ func startNode(t *testing.T, ctx context.Context, controlURL, hostname string) ( } func TestSNIProxyWithNetmapConfig(t *testing.T) { + nettest.SkipIfNoNetwork(t) c, controlURL := startControl(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() @@ -189,6 +191,7 @@ func TestSNIProxyWithNetmapConfig(t *testing.T) { } func TestSNIProxyWithFlagConfig(t *testing.T) { + nettest.SkipIfNoNetwork(t) _, controlURL := startControl(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index ef5463a95..bc66fc025 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -614,6 +614,8 @@ func (b *LocalBackend) ReloadConfig() (ok bool, err error) { return true, nil } +var assumeNetworkUpdateForTest = envknob.RegisterBool("TS_ASSUME_NETWORK_UP_FOR_TEST") + // pauseOrResumeControlClientLocked pauses b.cc if there is no network available // or if the LocalBackend is in Stopped state with a valid NetMap. In all other // cases, it unpauses it. It is a no-op if b.cc is nil. @@ -624,7 +626,7 @@ func (b *LocalBackend) pauseOrResumeControlClientLocked() { return } networkUp := b.prevIfState.AnyInterfaceUp() - b.cc.SetPaused((b.state == ipn.Stopped && b.netMap != nil) || (!networkUp && !testenv.InTest())) + b.cc.SetPaused((b.state == ipn.Stopped && b.netMap != nil) || (!networkUp && !testenv.InTest() && !assumeNetworkUpdateForTest())) } // linkChange is our network monitor callback, called whenever the network changes. diff --git a/net/netcheck/netcheck.go b/net/netcheck/netcheck.go index 989b3846f..1a0be9ebf 100644 --- a/net/netcheck/netcheck.go +++ b/net/netcheck/netcheck.go @@ -396,9 +396,7 @@ func makeProbePlan(dm *tailcfg.DERPMap, ifState *netmon.State, last *Report) (pl have6if := ifState.HaveV6 have4if := ifState.HaveV4 plan = make(probePlan) - if !have4if && !have6if { - return plan - } + had4 := len(last.RegionV4Latency) > 0 had6 := len(last.RegionV6Latency) > 0 hadBoth := have6if && had4 && had6 @@ -452,10 +450,10 @@ func makeProbePlan(dm *tailcfg.DERPMap, ifState *netmon.State, last *Report) (pl if try > 1 { delay += time.Duration(try) * 50 * time.Millisecond } - if do4 { + if do4 || n.IsTestNode() { p4 = append(p4, probe{delay: delay, node: n.Name, proto: probeIPv4}) } - if do6 { + if do6 || n.IsTestNode() { p6 = append(p6, probe{delay: delay, node: n.Name, proto: probeIPv6}) } } @@ -478,10 +476,10 @@ func makeProbePlanInitial(dm *tailcfg.DERPMap, ifState *netmon.State) (plan prob for try := 0; try < 3; try++ { n := reg.Nodes[try%len(reg.Nodes)] delay := time.Duration(try) * defaultInitialRetransmitTime - if ifState.HaveV4 && nodeMight4(n) { + if ifState.HaveV4 && nodeMight4(n) || n.IsTestNode() { p4 = append(p4, probe{delay: delay, node: n.Name, proto: probeIPv4}) } - if ifState.HaveV6 && nodeMight6(n) { + if ifState.HaveV6 && nodeMight6(n) || n.IsTestNode() { p6 = append(p6, probe{delay: delay, node: n.Name, proto: probeIPv6}) } } diff --git a/net/netcheck/netcheck_test.go b/net/netcheck/netcheck_test.go index 9390f3280..3652c2e55 100644 --- a/net/netcheck/netcheck_test.go +++ b/net/netcheck/netcheck_test.go @@ -23,6 +23,7 @@ "tailscale.com/net/stun/stuntest" "tailscale.com/tailcfg" "tailscale.com/tstest" + "tailscale.com/tstest/nettest" ) func TestHairpinSTUN(t *testing.T) { @@ -882,6 +883,8 @@ func TestSortRegions(t *testing.T) { } func TestNoCaptivePortalWhenUDP(t *testing.T) { + nettest.SkipIfNoNetwork(t) // empirically. not sure why. + // Override noRedirectClient to handle the /generate_204 endpoint var generate204Called atomic.Bool tr := RoundTripFunc(func(req *http.Request) *http.Response { @@ -934,6 +937,7 @@ func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { } func TestNodeAddrResolve(t *testing.T) { + nettest.SkipIfNoNetwork(t) c := &Client{ Logf: t.Logf, UseDNSCache: true, diff --git a/net/stun/stuntest/stuntest.go b/net/stun/stuntest/stuntest.go index 511f2df32..321757807 100644 --- a/net/stun/stuntest/stuntest.go +++ b/net/stun/stuntest/stuntest.go @@ -118,13 +118,14 @@ func DERPMapOf(stun ...string) *tailcfg.DERPMap { ipv4 = "none" } node := &tailcfg.DERPNode{ - Name: fmt.Sprint(regionID) + "a", - RegionID: regionID, - HostName: fmt.Sprintf("d%d%s", regionID, tailcfg.DotInvalid), - IPv4: ipv4, - IPv6: ipv6, - STUNPort: port, - STUNOnly: true, + Name: fmt.Sprint(regionID) + "a", + RegionID: regionID, + HostName: fmt.Sprintf("d%d%s", regionID, tailcfg.DotInvalid), + IPv4: ipv4, + IPv6: ipv6, + STUNPort: port, + STUNOnly: true, + STUNTestIP: host, } m.Regions[regionID] = &tailcfg.DERPRegion{ RegionID: regionID, diff --git a/tailcfg/derpmap.go b/tailcfg/derpmap.go index 915d714d1..056152157 100644 --- a/tailcfg/derpmap.go +++ b/tailcfg/derpmap.go @@ -183,6 +183,10 @@ type DERPNode struct { CanPort80 bool `json:",omitempty"` } +func (n *DERPNode) IsTestNode() bool { + return n.STUNTestIP != "" || n.IPv4 == "127.0.0.1" +} + // DotInvalid is a fake DNS TLD used in tests for an invalid hostname. const DotInvalid = ".invalid" diff --git a/tstest/integration/integration_test.go b/tstest/integration/integration_test.go index 41c04fce2..4c7528d7d 100644 --- a/tstest/integration/integration_test.go +++ b/tstest/integration/integration_test.go @@ -1249,6 +1249,7 @@ func (n *testNode) StartDaemonAsIPNGOOS(ipnGOOS string) *Daemon { "TS_DEBUG_FAKE_GOOS="+ipnGOOS, "TS_LOGS_DIR="+t.TempDir(), "TS_NETCHECK_GENERATE_204_URL="+n.env.ControlServer.URL+"/generate_204", + "TS_ASSUME_NETWORK_UP_FOR_TEST=1", // don't pause control client in airplane mode (no wifi, etc) ) if version.IsRace() { cmd.Env = append(cmd.Env, "GORACE=halt_on_error=1")