diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a4dccd103..d4c73ab7c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -313,6 +313,12 @@ jobs: # AIX - goos: aix goarch: ppc64 + # Solaris + - goos: solaris + goarch: amd64 + # illumos + - goos: illumos + goarch: amd64 runs-on: ubuntu-22.04 steps: diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 7a5ee0398..9dd00ddd9 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -81,7 +81,7 @@ 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": + case "plan9", "aix", "solaris", "illumos": return "userspace-networking" case "linux": switch distro.Get() { @@ -665,7 +665,7 @@ func handleSubnetsInNetstack() bool { return true } switch runtime.GOOS { - case "windows", "darwin", "freebsd", "openbsd": + case "windows", "darwin", "freebsd", "openbsd", "solaris", "illumos": // Enable on Windows and tailscaled-on-macOS (this doesn't // affect the GUI clients), and on FreeBSD. return true diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 8d2652e0a..ad3bbaef3 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -4176,7 +4176,7 @@ func (b *LocalBackend) peerAPIServicesLocked() (ret []tailcfg.Service) { }) } switch runtime.GOOS { - case "linux", "freebsd", "openbsd", "illumos", "darwin", "windows", "android", "ios": + case "linux", "freebsd", "openbsd", "illumos", "solaris", "darwin", "windows", "android", "ios": // These are the platforms currently supported by // net/dns/resolver/tsdns.go:Resolver.HandleExitNodeDNSQuery. ret = append(ret, tailcfg.Service{ diff --git a/ipn/ipnserver/actor.go b/ipn/ipnserver/actor.go index 63d4b183c..0e716009c 100644 --- a/ipn/ipnserver/actor.go +++ b/ipn/ipnserver/actor.go @@ -96,7 +96,7 @@ func (a *actor) Username() (string, error) { } defer tok.Close() return tok.Username() - case "darwin", "linux": + case "darwin", "linux", "illumos", "solaris": uid, ok := a.ci.Creds().UserID() if !ok { return "", errors.New("missing user ID") diff --git a/ipn/ipnstate/ipnstate.go b/ipn/ipnstate/ipnstate.go index 9f8bd34f6..37ab47714 100644 --- a/ipn/ipnstate/ipnstate.go +++ b/ipn/ipnstate/ipnstate.go @@ -650,6 +650,8 @@ func osEmoji(os string) string { return "🐡" case "illumos": return "☀️" + case "solaris": + return "🌤️" } return "👽" } diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index 831f6a9b6..157f72a65 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -1097,7 +1097,7 @@ func (h *Handler) serveServeConfig(w http.ResponseWriter, r *http.Request) { func authorizeServeConfigForGOOSAndUserContext(goos string, configIn *ipn.ServeConfig, h *Handler) error { switch goos { - case "windows", "linux", "darwin": + case "windows", "linux", "darwin", "illumos", "solaris": default: return nil } @@ -1117,7 +1117,7 @@ func authorizeServeConfigForGOOSAndUserContext(goos string, configIn *ipn.ServeC switch goos { case "windows": return errors.New("must be a Windows local admin to serve a path") - case "linux", "darwin": + case "linux", "darwin", "illumos", "solaris": return errors.New("must be root, or be an operator and able to run 'sudo tailscale' to serve a path") default: // We filter goos at the start of the func, this default case diff --git a/ipn/localapi/localapi_test.go b/ipn/localapi/localapi_test.go index 145910830..b7f0c416c 100644 --- a/ipn/localapi/localapi_test.go +++ b/ipn/localapi/localapi_test.go @@ -237,7 +237,7 @@ func TestShouldDenyServeConfigForGOOSAndUserContext(t *testing.T) { } for _, tt := range tests { - for _, goos := range []string{"linux", "windows", "darwin"} { + for _, goos := range []string{"linux", "windows", "darwin", "illumos", "solaris"} { t.Run(goos+"-"+tt.name, func(t *testing.T) { err := authorizeServeConfigForGOOSAndUserContext(goos, tt.configIn, tt.h) gotErr := err != nil diff --git a/net/dns/manager_default.go b/net/dns/manager_default.go index 11dea5ca8..99ff017da 100644 --- a/net/dns/manager_default.go +++ b/net/dns/manager_default.go @@ -1,7 +1,7 @@ // Copyright (c) Tailscale Inc & AUTHORS // SPDX-License-Identifier: BSD-3-Clause -//go:build !linux && !freebsd && !openbsd && !windows && !darwin +//go:build !linux && !freebsd && !openbsd && !windows && !darwin && !illumos && !solaris package dns diff --git a/net/dns/manager_solaris.go b/net/dns/manager_solaris.go new file mode 100644 index 000000000..1f48efb9e --- /dev/null +++ b/net/dns/manager_solaris.go @@ -0,0 +1,14 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +package dns + +import ( + "tailscale.com/control/controlknobs" + "tailscale.com/health" + "tailscale.com/types/logger" +) + +func NewOSConfigurator(logf logger.Logf, health *health.Tracker, _ *controlknobs.Knobs, iface string) (OSConfigurator, error) { + return newDirectManager(logf, health), nil +} diff --git a/net/dns/resolver/tsdns.go b/net/dns/resolver/tsdns.go index 43ba0acf1..107740b13 100644 --- a/net/dns/resolver/tsdns.go +++ b/net/dns/resolver/tsdns.go @@ -384,7 +384,7 @@ func (r *Resolver) HandlePeerDNSQuery(ctx context.Context, q []byte, from netip. // but for now that's probably good enough. Later we'll // want to blend in everything from scutil --dns. fallthrough - case "linux", "freebsd", "openbsd", "illumos", "ios": + case "linux", "freebsd", "openbsd", "illumos", "solaris", "ios": nameserver, err := stubResolverForOS() if err != nil { r.logf("stubResolverForOS: %v", err) diff --git a/net/netutil/ip_forward.go b/net/netutil/ip_forward.go index 48cee68ea..c64a9e426 100644 --- a/net/netutil/ip_forward.go +++ b/net/netutil/ip_forward.go @@ -63,6 +63,11 @@ func CheckIPForwarding(routes []netip.Prefix, state *netmon.State) (warn, err er switch runtime.GOOS { case "dragonfly", "freebsd", "netbsd", "openbsd": return fmt.Errorf("Subnet routing and exit nodes only work with additional manual configuration on %v, and is not currently officially supported.", runtime.GOOS), nil + case "illumos", "solaris": + _, err := ipForwardingEnabledSunOS(ipv4, "") + if err != nil { + return nil, fmt.Errorf("Couldn't check system's IP forwarding configuration, subnet routing/exit nodes may not work: %w%s", err, "") + } } return nil, nil } @@ -325,3 +330,24 @@ func reversePathFilterValueLinux(iface string) (int, error) { } return v, nil } + +func ipForwardingEnabledSunOS(p protocol, iface string) (bool, error) { + var proto string + if p == ipv4 { + proto = "ipv4" + } else if p == ipv6 { + proto = "ipv6" + } else { + return false, fmt.Errorf("unknown protocol") + } + + ipadmCmd := "\"ipadm show-prop " + proto + " -p forwarding -o CURRENT -c\"" + bs, err := exec.Command("ipadm", "show-prop", proto, "-p", "forwarding", "-o", "CURRENT", "-c").Output() + if err != nil { + return false, fmt.Errorf("couldn't check %s (%v).\nSubnet routes won't work without IP forwarding.", ipadmCmd, err) + } + if string(bs) != "on\n" { + return false, fmt.Errorf("IP forwarding is set to off. Subnet routes won't work. Try 'routeadm -u -e %s-forwarding'", proto) + } + return true, nil +} diff --git a/net/tstun/tstun_stub.go b/net/tstun/tstun_stub.go index 7a4f71a09..3119d647c 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 +//go:build plan9 || aix || solaris || illumos package tstun diff --git a/net/tstun/tun.go b/net/tstun/tun.go index 9f5d42ecc..56c66c83a 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 +//go:build !wasm && !plan9 && !tamago && !aix && !solaris && !illumos // Package tun creates a tuntap device, working around OS-specific // quirks if necessary. diff --git a/paths/paths_unix.go b/paths/paths_unix.go index 6a2b28733..50a8b7ca5 100644 --- a/paths/paths_unix.go +++ b/paths/paths_unix.go @@ -22,7 +22,7 @@ func init() { func statePath() string { switch runtime.GOOS { - case "linux": + case "linux", "illumos", "solaris": return "/var/lib/tailscale/tailscaled.state" case "freebsd", "openbsd": return "/var/db/tailscale/tailscaled.state"