diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 122afe97b..8a2a4b820 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -82,7 +82,9 @@ func defaultTunName() string { // "utun" is recognized by wireguard-go/tun/tun_darwin.go // as a magic value that uses/creates any free number. return "utun" - case "plan9", "aix", "solaris", "illumos": + case "plan9": + return "auto" + case "aix", "solaris", "illumos": return "userspace-networking" case "linux": switch distro.Get() { diff --git a/go.mod b/go.mod index 7be824165..eb99c5373 100644 --- a/go.mod +++ b/go.mod @@ -85,7 +85,7 @@ require ( github.com/tailscale/setec v0.0.0-20250205144240-8898a29c3fbb github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 - github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19 + github.com/tailscale/wireguard-go v0.0.0-20250304000100-91a0587fb251 github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e github.com/tc-hib/winres v0.2.1 github.com/tcnksm/go-httpstat v0.2.0 diff --git a/go.sum b/go.sum index fffa17209..bf5700e49 100644 --- a/go.sum +++ b/go.sum @@ -924,8 +924,8 @@ github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:U github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M= github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y= -github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19 h1:BcEJP2ewTIK2ZCsqgl6YGpuO6+oKqqag5HHb7ehljKw= -github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= +github.com/tailscale/wireguard-go v0.0.0-20250304000100-91a0587fb251 h1:h/41LFTrwMxB9Xvvug0kRdQCU5TlV1+pAMQw0ZtDE3U= +github.com/tailscale/wireguard-go v0.0.0-20250304000100-91a0587fb251/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek= github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg= github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= diff --git a/net/tstun/tstun_stub.go b/net/tstun/tstun_stub.go index 3119d647c..d21eda6b0 100644 --- a/net/tstun/tstun_stub.go +++ b/net/tstun/tstun_stub.go @@ -1,7 +1,7 @@ // Copyright (c) Tailscale Inc & AUTHORS // SPDX-License-Identifier: BSD-3-Clause -//go:build plan9 || aix || solaris || illumos +//go:build aix || solaris || illumos package tstun diff --git a/net/tstun/tun.go b/net/tstun/tun.go index 44ccdfc99..88679daa2 100644 --- a/net/tstun/tun.go +++ b/net/tstun/tun.go @@ -1,7 +1,7 @@ // Copyright (c) Tailscale Inc & AUTHORS // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !plan9 && !tamago && !aix && !solaris && !illumos +//go:build !wasm && !tamago && !aix && !solaris && !illumos // Package tun creates a tuntap device, working around OS-specific // quirks if necessary. @@ -9,6 +9,9 @@ package tstun import ( "errors" + "fmt" + "log" + "os" "runtime" "strings" "time" @@ -45,6 +48,9 @@ func New(logf logger.Logf, tunName string) (tun.Device, string, error) { } dev, err = CreateTAP.Get()(logf, tapName, bridgeName) } else { + if runtime.GOOS == "plan9" { + cleanUpPlan9Interfaces() + } dev, err = tun.CreateTUN(tunName, int(DefaultTUNMTU())) } if err != nil { @@ -65,6 +71,36 @@ func New(logf logger.Logf, tunName string) (tun.Device, string, error) { return dev, name, nil } +func cleanUpPlan9Interfaces() { + maybeUnbind := func(n int) { + b, err := os.ReadFile(fmt.Sprintf("/net/ipifc/%d/status", n)) + if err != nil { + return + } + status := string(b) + if !(strings.HasPrefix(status, "device maxtu ") || + strings.Contains(status, "fd7a:115c:a1e0:")) { + return + } + f, err := os.OpenFile(fmt.Sprintf("/net/ipifc/%d/ctl", n), os.O_RDWR, 0) + if err != nil { + return + } + defer f.Close() + if _, err := fmt.Fprintf(f, "unbind\n"); err != nil { + log.Printf("unbind interface %v: %v", n, err) + return + } + log.Printf("tun: unbound stale interface %v", n) + } + + // A common case: after unclean shutdown we might leave interfaces + // behind. Look for our straggler(s) and clean them up. + for n := 2; n < 5; n++ { + maybeUnbind(n) + } +} + // tunDiagnoseFailure, if non-nil, does OS-specific diagnostics of why // TUN failed to work. var tunDiagnoseFailure func(tunName string, logf logger.Logf, err error) diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 1200003f6..cca253048 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -569,6 +569,17 @@ func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper) return filter.Drop } } + if runtime.GOOS == "plan9" { + isLocalAddr, ok := e.isLocalAddr.LoadOk() + if ok { + if isLocalAddr(p.Dst.Addr()) { + // On Plan9's "tun" equivalent, everything goes back in and out + // the tun, even when the kernel's replying to itself. + t.InjectInboundCopy(p.Buffer()) + return filter.Drop + } + } + } return filter.Accept }