summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@tailscale.com>2025-04-01 04:01:00 -0700
committerBrad Fitzpatrick <brad@danga.com>2025-04-02 07:36:04 -0700
commitbbdd3c3bdecdb9576040d1e2018f73df4ee339fc (patch)
tree44f0d0345ff51b28d3ead73a70b7481857ad848a
parente3282c163231ad9d0bfdf6d43fecfb8ebe154c2c (diff)
downloadtailscale-bbdd3c3bdecdb9576040d1e2018f73df4ee339fc.tar.xz
tailscale-bbdd3c3bdecdb9576040d1e2018f73df4ee339fc.zip
wgengine/router: add Plan 9 implementation
Updates #5794 Change-Id: Ib78a3ea971a2374d405b024ab88658ec34be59a6 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
-rw-r--r--ipn/ipnlocal/local.go5
-rw-r--r--wgengine/router/router_default.go2
-rw-r--r--wgengine/router/router_plan9.go156
3 files changed, 162 insertions, 1 deletions
diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go
index c44827aa4..7d69b884d 100644
--- a/ipn/ipnlocal/local.go
+++ b/ipn/ipnlocal/local.go
@@ -5069,6 +5069,11 @@ func shouldUseOneCGNATRoute(logf logger.Logf, mon *netmon.Monitor, controlKnobs
}
}
+ if versionOS == "plan9" {
+ // Just temporarily during plan9 bringup to have fewer routes to debug.
+ return true
+ }
+
// Also prefer to do this on the Mac, so that we don't need to constantly
// update the network extension configuration (which is disruptive to
// Chrome, see https://github.com/tailscale/tailscale/issues/3102). Only
diff --git a/wgengine/router/router_default.go b/wgengine/router/router_default.go
index 1e675d1fc..8dcbd36d0 100644
--- a/wgengine/router/router_default.go
+++ b/wgengine/router/router_default.go
@@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
-//go:build !windows && !linux && !darwin && !openbsd && !freebsd
+//go:build !windows && !linux && !darwin && !openbsd && !freebsd && !plan9
package router
diff --git a/wgengine/router/router_plan9.go b/wgengine/router/router_plan9.go
new file mode 100644
index 000000000..7ed7686d9
--- /dev/null
+++ b/wgengine/router/router_plan9.go
@@ -0,0 +1,156 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package router
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "net/netip"
+ "os"
+ "strings"
+
+ "github.com/tailscale/wireguard-go/tun"
+ "tailscale.com/health"
+ "tailscale.com/net/netmon"
+ "tailscale.com/types/logger"
+)
+
+func newUserspaceRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor, health *health.Tracker) (Router, error) {
+ r := &plan9Router{
+ logf: logf,
+ tundev: tundev,
+ netMon: netMon,
+ }
+ cleanAllTailscaleRoutes(logf)
+ return r, nil
+}
+
+type plan9Router struct {
+ logf logger.Logf
+ tundev tun.Device
+ netMon *netmon.Monitor
+ health *health.Tracker
+}
+
+func (r *plan9Router) Up() error {
+ return nil
+}
+
+func (r *plan9Router) Set(cfg *Config) error {
+ if cfg == nil {
+ cleanAllTailscaleRoutes(r.logf)
+ return nil
+ }
+
+ var self4, self6 netip.Addr
+ for _, addr := range cfg.LocalAddrs {
+ ctl := r.tundev.File()
+ maskBits := addr.Bits()
+ if addr.Addr().Is4() {
+ // The mask sizes in Plan9 are in IPv6 bits, even for IPv4.
+ maskBits += (128 - 32)
+ self4 = addr.Addr()
+ }
+ if addr.Addr().Is6() {
+ self6 = addr.Addr()
+ }
+ _, err := fmt.Fprintf(ctl, "add %s /%d\n", addr.Addr().String(), maskBits)
+ r.logf("route/plan9: add %s /%d = %v", addr.Addr().String(), maskBits, err)
+ }
+
+ ipr, err := os.OpenFile("/net/iproute", os.O_RDWR, 0)
+ if err != nil {
+ return fmt.Errorf("open /net/iproute: %w", err)
+ }
+ defer ipr.Close()
+
+ // TODO(bradfitz): read existing routes, delete ones tagged "tail"
+ // that aren't in cfg.LocalRoutes.
+
+ if _, err := fmt.Fprintf(ipr, "tag tail\n"); err != nil {
+ return fmt.Errorf("tag tail: %w", err)
+ }
+
+ for _, route := range cfg.Routes {
+ maskBits := route.Bits()
+ if route.Addr().Is4() {
+ // The mask sizes in Plan9 are in IPv6 bits, even for IPv4.
+ maskBits += (128 - 32)
+ }
+ var nextHop netip.Addr
+ if route.Addr().Is4() {
+ nextHop = self4
+ } else if route.Addr().Is6() {
+ nextHop = self6
+ }
+ if !nextHop.IsValid() {
+ r.logf("route/plan9: skipping route %s: no next hop (no self addr)", route.String())
+ continue
+ }
+ r.logf("route/plan9: plan9.router: add %s /%d %s", route.Addr(), maskBits, nextHop)
+ if _, err := fmt.Fprintf(ipr, "add %s /%d %s\n", route.Addr(), maskBits, nextHop); err != nil {
+ return fmt.Errorf("add %s: %w", route.String(), err)
+ }
+ }
+
+ if len(cfg.LocalRoutes) > 0 {
+ r.logf("route/plan9: TODO: Set LocalRoutes %v", cfg.LocalRoutes)
+ }
+ if len(cfg.SubnetRoutes) > 0 {
+ r.logf("route/plan9: TODO: Set SubnetRoutes %v", cfg.SubnetRoutes)
+ }
+
+ return nil
+}
+
+// UpdateMagicsockPort implements the Router interface. This implementation
+// does nothing and returns nil because this router does not currently need
+// to know what the magicsock UDP port is.
+func (r *plan9Router) UpdateMagicsockPort(_ uint16, _ string) error {
+ return nil
+}
+
+func (r *plan9Router) Close() error {
+ // TODO(bradfitz): unbind
+ return nil
+}
+
+func cleanUp(logf logger.Logf, _ string) {
+ cleanAllTailscaleRoutes(logf)
+}
+
+func cleanAllTailscaleRoutes(logf logger.Logf) {
+ routes, err := os.OpenFile("/net/iproute", os.O_RDWR, 0)
+ if err != nil {
+ logf("cleaning routes: %v", err)
+ return
+ }
+ defer routes.Close()
+
+ // Using io.ReadAll or os.ReadFile on /net/iproute fails; it results in a
+ // 511 byte result when the actual /net/iproute contents are over 1k.
+ // So do it in one big read instead. Who knows.
+ routeBuf := make([]byte, 1<<20)
+ n, err := routes.Read(routeBuf)
+ if err != nil {
+ logf("cleaning routes: %v", err)
+ return
+ }
+ routeBuf = routeBuf[:n]
+
+ bs := bufio.NewScanner(bytes.NewReader(routeBuf))
+ for bs.Scan() {
+ f := strings.Fields(bs.Text())
+ if len(f) < 6 {
+ continue
+ }
+ tag := f[4]
+ if tag != "tail" {
+ continue
+ }
+ _, err := fmt.Fprintf(routes, "remove %s %s\n", f[0], f[1])
+ logf("router: cleaning route %s %s: %v", f[0], f[1], err)
+ }
+}