all: use network less when running in v86 emulator

Updates #5794

Change-Id: I1d8b005a1696835c9062545f87b7bab643cfc44d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2025-04-01 04:01:00 -07:00 committed by Brad Fitzpatrick
parent 29c2bb1db6
commit 65c7a37bc6
6 changed files with 104 additions and 6 deletions

View File

@ -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()

View File

@ -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 {

View File

@ -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
})
}

View File

@ -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"
})
}

View File

@ -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

View File

@ -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)
}
if len(ifIPs) > 0 {
c.maybeCloseDERPsOnRebind(ifIPs)
}
c.resetEndpointStates()
}