Переглянути джерело

all: use network less when running in v86 emulator

Updates #5794

Change-Id: I1d8b005a1696835c9062545f87b7bab643cfc44d
Signed-off-by: Brad Fitzpatrick <[email protected]>
Brad Fitzpatrick 11 місяців тому
батько
коміт
65c7a37bc6

+ 8 - 1
control/controlclient/direct.go

@@ -1086,7 +1086,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap
 		} else {
 			vlogf("netmap: got new map")
 		}
-		if resp.ControlDialPlan != nil {
+		if resp.ControlDialPlan != nil && !ignoreDialPlan() {
 			if c.dialPlan != nil {
 				c.logf("netmap: got new dial plan from control")
 				c.dialPlan.Store(resp.ControlDialPlan)
@@ -1774,6 +1774,13 @@ func makeScreenTimeDetectingDialFunc(dial dialFunc) (dialFunc, *atomic.Bool) {
 	}, ab
 }
 
+func ignoreDialPlan() bool {
+	// If we're running in v86 (a JavaScript-based emulation of a 32-bit x86)
+	// our networking is very limited. Let's ignore the dial plan since it's too
+	// complicated to race that many IPs anyway.
+	return hostinfo.IsInVM86()
+}
+
 func isTCPLoopback(a net.Addr) bool {
 	if ta, ok := a.(*net.TCPAddr); ok {
 		return ta.IP.IsLoopback()

+ 26 - 0
control/controlclient/map.go

@@ -19,6 +19,7 @@ import (
 
 	"tailscale.com/control/controlknobs"
 	"tailscale.com/envknob"
+	"tailscale.com/hostinfo"
 	"tailscale.com/tailcfg"
 	"tailscale.com/tstime"
 	"tailscale.com/types/key"
@@ -308,6 +309,31 @@ func (ms *mapSession) updateStateFromResponse(resp *tailcfg.MapResponse) {
 			}
 		}
 
+		// In the copy/v86 wasm environment with limited networking, if the
+		// control plane didn't pick our DERP home for us, do it ourselves and
+		// mark all but the lowest region as NoMeasureNoHome. For prod, this
+		// will be Region 1, NYC, a compromise between the US and Europe. But
+		// really the control plane should pick this. This is only a fallback.
+		if hostinfo.IsInVM86() {
+			numCanMeasure := 0
+			lowest := 0
+			for rid, r := range dm.Regions {
+				if !r.NoMeasureNoHome {
+					numCanMeasure++
+					if lowest == 0 || rid < lowest {
+						lowest = rid
+					}
+				}
+			}
+			if numCanMeasure > 1 {
+				for rid, r := range dm.Regions {
+					if rid != lowest {
+						r.NoMeasureNoHome = true
+					}
+				}
+			}
+		}
+
 		// Zero-valued fields in a DERPMap mean that we're not changing
 		// anything and are using the previous value(s).
 		if ldm := ms.lastDERPMap; ldm != nil {

+ 12 - 2
hostinfo/hostinfo.go

@@ -21,6 +21,7 @@ import (
 	"go4.org/mem"
 	"tailscale.com/envknob"
 	"tailscale.com/tailcfg"
+	"tailscale.com/types/lazy"
 	"tailscale.com/types/opt"
 	"tailscale.com/types/ptr"
 	"tailscale.com/util/cloudenv"
@@ -497,5 +498,14 @@ func IsNATLabGuestVM() bool {
 	return false
 }
 
-// NAT Lab VMs have a unique MAC address prefix.
-// See
+const copyV86DeviceModel = "copy-v86"
+
+var isV86Cache lazy.SyncValue[bool]
+
+// IsInVM86 reports whether we're running in the copy/v86 wasm emulator,
+// https://github.com/copy/v86/.
+func IsInVM86() bool {
+	return isV86Cache.Get(func() bool {
+		return New().DeviceModel == copyV86DeviceModel
+	})
+}

+ 39 - 0
hostinfo/hostinfo_plan9.go

@@ -0,0 +1,39 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package hostinfo
+
+import (
+	"bytes"
+	"os"
+	"strings"
+
+	"tailscale.com/tailcfg"
+	"tailscale.com/types/lazy"
+)
+
+func init() {
+	RegisterHostinfoNewHook(func(hi *tailcfg.Hostinfo) {
+		if isPlan9V86() {
+			hi.DeviceModel = copyV86DeviceModel
+		}
+	})
+}
+
+var isPlan9V86Cache lazy.SyncValue[bool]
+
+// isPlan9V86 reports whether we're running in the wasm
+// environment (https://github.com/copy/v86/).
+func isPlan9V86() bool {
+	return isPlan9V86Cache.Get(func() bool {
+		v, _ := os.ReadFile("/dev/cputype")
+		s, _, _ := strings.Cut(string(v), " ")
+		if s != "PentiumIV/Xeon" {
+			return false
+		}
+
+		v, _ = os.ReadFile("/dev/config")
+		v, _, _ = bytes.Cut(v, []byte{'\n'})
+		return string(v) == "# pcvm - small kernel used to run in vm"
+	})
+}

+ 15 - 1
net/netcheck/netcheck.go

@@ -25,6 +25,7 @@ import (
 
 	"tailscale.com/derp/derphttp"
 	"tailscale.com/envknob"
+	"tailscale.com/hostinfo"
 	"tailscale.com/net/captivedetection"
 	"tailscale.com/net/dnscache"
 	"tailscale.com/net/neterror"
@@ -863,7 +864,7 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap, opts *GetRe
 		c.curState = nil
 	}()
 
-	if runtime.GOOS == "js" || runtime.GOOS == "tamago" {
+	if runtime.GOOS == "js" || runtime.GOOS == "tamago" || (runtime.GOOS == "plan9" && hostinfo.IsInVM86()) {
 		if err := c.runHTTPOnlyChecks(ctx, last, rs, dm); err != nil {
 			return nil, err
 		}
@@ -1063,6 +1064,19 @@ func (c *Client) runHTTPOnlyChecks(ctx context.Context, last *Report, rs *report
 			regions = append(regions, dr)
 		}
 	}
+
+	if len(regions) == 1 && hostinfo.IsInVM86() {
+		// If we only have 1 region that's probably and we're in a
+		// network-limited v86 environment, don't actually probe it. Just fake
+		// some results.
+		rg := regions[0]
+		if len(rg.Nodes) > 0 {
+			node := rg.Nodes[0]
+			rs.addNodeLatency(node, netip.AddrPort{}, 999*time.Millisecond)
+			return nil
+		}
+	}
+
 	c.logf("running HTTP-only netcheck against %v regions", len(regions))
 
 	var wg sync.WaitGroup

+ 4 - 2
wgengine/magicsock/magicsock.go

@@ -719,7 +719,7 @@ func (c *Conn) updateEndpoints(why string) {
 		c.muCond.Broadcast()
 	}()
 	c.dlogf("[v1] magicsock: starting endpoint update (%s)", why)
-	if c.noV4Send.Load() && runtime.GOOS != "js" && !c.onlyTCP443.Load() {
+	if c.noV4Send.Load() && runtime.GOOS != "js" && !c.onlyTCP443.Load() && !hostinfo.IsInVM86() {
 		c.mu.Lock()
 		closed := c.closed
 		c.mu.Unlock()
@@ -2767,7 +2767,9 @@ func (c *Conn) Rebind() {
 		c.logf("Rebind; defIf=%q, ips=%v", defIf, ifIPs)
 	}
 
-	c.maybeCloseDERPsOnRebind(ifIPs)
+	if len(ifIPs) > 0 {
+		c.maybeCloseDERPsOnRebind(ifIPs)
+	}
 	c.resetEndpointStates()
 }