diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index b61d6aef6..4ef52a4e4 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -250,7 +250,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/paths from tailscale.com/ipn/ipnlocal+ 💣 tailscale.com/portlist from tailscale.com/ipn/ipnlocal tailscale.com/safesocket from tailscale.com/client/tailscale+ - tailscale.com/smallzstd from tailscale.com/ipn/ipnserver+ + tailscale.com/smallzstd from tailscale.com/cmd/tailscaled+ LD 💣 tailscale.com/ssh/tailssh from tailscale.com/cmd/tailscaled tailscale.com/syncs from tailscale.com/net/netcheck+ tailscale.com/tailcfg from tailscale.com/client/tailscale/apitype+ diff --git a/cmd/tailscaled/taildrop.go b/cmd/tailscaled/taildrop.go new file mode 100644 index 000000000..8c521bf87 --- /dev/null +++ b/cmd/tailscaled/taildrop.go @@ -0,0 +1,106 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.19 + +package main + +import ( + "fmt" + "os" + "path/filepath" + + "tailscale.com/ipn/ipnlocal" + "tailscale.com/types/logger" + "tailscale.com/version/distro" +) + +func configureTaildrop(logf logger.Logf, lb *ipnlocal.LocalBackend) { + dg := distro.Get() + switch dg { + case distro.Synology, distro.TrueNAS, distro.QNAP: + // See if they have a "Taildrop" share. + // See https://github.com/tailscale/tailscale/issues/2179#issuecomment-982821319 + path, err := findTaildropDir(dg) + if err != nil { + logf("%s Taildrop support: %v", dg, err) + } else { + logf("%s Taildrop: using %v", dg, path) + lb.SetDirectFileRoot(path) + lb.SetDirectFileDoFinalRename(true) + } + } + +} + +func findTaildropDir(dg distro.Distro) (string, error) { + const name = "Taildrop" + switch dg { + case distro.Synology: + return findSynologyTaildropDir(name) + case distro.TrueNAS: + return findTrueNASTaildropDir(name) + case distro.QNAP: + return findQnapTaildropDir(name) + } + return "", fmt.Errorf("%s is an unsupported distro for Taildrop dir", dg) +} + +// findSynologyTaildropDir looks for the first volume containing a +// "Taildrop" directory. We'd run "synoshare --get Taildrop" command +// but on DSM7 at least, we lack permissions to run that. +func findSynologyTaildropDir(name string) (dir string, err error) { + for i := 1; i <= 16; i++ { + dir = fmt.Sprintf("/volume%v/%s", i, name) + if fi, err := os.Stat(dir); err == nil && fi.IsDir() { + return dir, nil + } + } + return "", fmt.Errorf("shared folder %q not found", name) +} + +// findTrueNASTaildropDir returns the first matching directory of +// /mnt/{name} or /mnt/*/{name} +func findTrueNASTaildropDir(name string) (dir string, err error) { + // If we're running in a jail, a mount point could just be added at /mnt/Taildrop + dir = fmt.Sprintf("/mnt/%s", name) + if fi, err := os.Stat(dir); err == nil && fi.IsDir() { + return dir, nil + } + + // but if running on the host, it may be something like /mnt/Primary/Taildrop + fis, err := os.ReadDir("/mnt") + if err != nil { + return "", fmt.Errorf("error reading /mnt: %w", err) + } + for _, fi := range fis { + dir = fmt.Sprintf("/mnt/%s/%s", fi.Name(), name) + if fi, err := os.Stat(dir); err == nil && fi.IsDir() { + return dir, nil + } + } + return "", fmt.Errorf("shared folder %q not found", name) +} + +// findQnapTaildropDir checks if a Shared Folder named "Taildrop" exists. +func findQnapTaildropDir(name string) (string, error) { + dir := fmt.Sprintf("/share/%s", name) + fi, err := os.Stat(dir) + if err != nil { + return "", fmt.Errorf("shared folder %q not found", name) + } + if fi.IsDir() { + return dir, nil + } + + // share/Taildrop is usually a symlink to CACHEDEV1_DATA/Taildrop/ or some such. + fullpath, err := filepath.EvalSymlinks(dir) + if err != nil { + return "", fmt.Errorf("symlink to shared folder %q not found", name) + } + if fi, err = os.Stat(fullpath); err == nil && fi.IsDir() { + return dir, nil // return the symlink, how QNAP set it up + } + return "", fmt.Errorf("shared folder %q not found", name) +} diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 9fecaad13..012001537 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -33,11 +33,13 @@ import ( "tailscale.com/cmd/tailscaled/childproc" "tailscale.com/control/controlclient" "tailscale.com/envknob" + "tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/ipnserver" "tailscale.com/ipn/store" "tailscale.com/logpolicy" "tailscale.com/logtail" "tailscale.com/net/dns" + "tailscale.com/net/dnsfallback" "tailscale.com/net/netns" "tailscale.com/net/proxymux" "tailscale.com/net/socks5" @@ -45,6 +47,7 @@ import ( "tailscale.com/net/tstun" "tailscale.com/paths" "tailscale.com/safesocket" + "tailscale.com/smallzstd" "tailscale.com/tsweb" "tailscale.com/types/flagtype" "tailscale.com/types/logger" @@ -273,7 +276,21 @@ func statePathOrDefault() string { return "" } -func ipnServerOpts() (o ipnserver.Options) { +// serverOptions is the configuration of the Tailscale node agent. +type serverOptions struct { + // VarRoot is the Tailscale daemon's private writable + // directory (usually "/var/lib/tailscale" on Linux) that + // contains the "tailscaled.state" file, the "certs" directory + // for TLS certs, and the "files" directory for incoming + // Taildrop files before they're moved to a user directory. + // If empty, Taildrop and TLS certs don't function. + VarRoot string + + // LoginFlags specifies the LoginFlags to pass to the client. + LoginFlags controlclient.LoginFlags +} + +func ipnServerOpts() (o serverOptions) { goos := envknob.GOOS() o.VarRoot = args.statedir @@ -297,9 +314,6 @@ func ipnServerOpts() (o ipnserver.Options) { // TODO(bradfitz): if we start using browser LocalStorage // or something, then rethink this. o.LoginFlags = controlclient.LoginEphemeral - fallthrough - default: - o.SurviveDisconnects = true case "windows": // Not those. } @@ -445,11 +459,24 @@ func run() error { if err != nil { return fmt.Errorf("store.New: %w", err) } - srv, err := ipnserver.New(logf, pol.PublicID.String(), store, e, dialer, opts) + logid := pol.PublicID.String() + + lb, err := ipnlocal.NewLocalBackend(logf, logid, store, "", dialer, e, opts.LoginFlags) if err != nil { - return fmt.Errorf("ipnserver.New: %w", err) + return fmt.Errorf("ipnlocal.NewLocalBackend: %w", err) } - ns.SetLocalBackend(srv.LocalBackend()) + lb.SetVarRoot(opts.VarRoot) + if root := lb.TailscaleVarRoot(); root != "" { + dnsfallback.SetCachePath(filepath.Join(root, "derpmap.cached.json")) + } + lb.SetDecompressor(func() (controlclient.Decompressor, error) { + return smallzstd.NewDecoder(nil) + }) + configureTaildrop(logf, lb) + + srv := ipnserver.New(logf, logid) + srv.SetLocalBackend(lb) + ns.SetLocalBackend(lb) if err := ns.Start(); err != nil { log.Fatalf("failed to start netstack: %v", err) } diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go index 3bfea93e8..645d71a36 100644 --- a/cmd/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled_windows.go @@ -31,6 +31,7 @@ import ( "os" "os/exec" "os/signal" + "path/filepath" "sync" "syscall" "time" @@ -40,16 +41,20 @@ import ( "golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc/eventlog" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" + "tailscale.com/control/controlclient" "tailscale.com/envknob" "tailscale.com/ipn" + "tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/ipnserver" "tailscale.com/ipn/store" "tailscale.com/logpolicy" "tailscale.com/logtail/backoff" "tailscale.com/net/dns" + "tailscale.com/net/dnsfallback" "tailscale.com/net/tsdial" "tailscale.com/net/tstun" "tailscale.com/safesocket" + "tailscale.com/smallzstd" "tailscale.com/types/logger" "tailscale.com/util/winutil" "tailscale.com/version" @@ -609,7 +614,7 @@ func (ln *listenerWithReadyConn) Accept() (net.Conn, error) { // // This works around issues on Windows with the wgengine.Engine/wintun creation // failing or hanging. See https://github.com/tailscale/tailscale/issues/6522. -func ipnServerRunWithRetries(ctx context.Context, logf logger.Logf, ln net.Listener, store ipn.StateStore, linkMon *monitor.Mon, dialer *tsdial.Dialer, logid string, getEngine func() (wgengine.Engine, *netstack.Impl, error), opts ipnserver.Options) error { +func ipnServerRunWithRetries(ctx context.Context, logf logger.Logf, ln net.Listener, store ipn.StateStore, linkMon *monitor.Mon, dialer *tsdial.Dialer, logid string, getEngine func() (wgengine.Engine, *netstack.Impl, error), opts serverOptions) error { getEngine = getEngineUntilItWorksWrapper(getEngine) runDone := make(chan struct{}) defer close(runDone) @@ -662,12 +667,23 @@ func ipnServerRunWithRetries(ctx context.Context, logf logger.Logf, ln net.Liste } } - server, err := ipnserver.New(logf, logid, store, eng, dialer, opts) + server := ipnserver.New(logf, logid) + + lb, err := ipnlocal.NewLocalBackend(logf, logid, store, "", dialer, eng, opts.LoginFlags) if err != nil { - return err + return fmt.Errorf("ipnlocal.NewLocalBackend: %w", err) + } + lb.SetVarRoot(opts.VarRoot) + if root := lb.TailscaleVarRoot(); root != "" { + dnsfallback.SetCachePath(filepath.Join(root, "derpmap.cached.json")) } + lb.SetDecompressor(func() (controlclient.Decompressor, error) { + return smallzstd.NewDecoder(nil) + }) + + server.SetLocalBackend(lb) if ns != nil { - ns.SetLocalBackend(server.LocalBackend()) + ns.SetLocalBackend(lb) } return server.Run(ctx, ln) } diff --git a/cmd/tsconnect/wasm/wasm_js.go b/cmd/tsconnect/wasm/wasm_js.go index df20f5169..b928ae7f9 100644 --- a/cmd/tsconnect/wasm/wasm_js.go +++ b/cmd/tsconnect/wasm/wasm_js.go @@ -36,6 +36,7 @@ import ( "tailscale.com/net/netns" "tailscale.com/net/tsdial" "tailscale.com/safesocket" + "tailscale.com/smallzstd" "tailscale.com/tailcfg" "tailscale.com/wgengine" "tailscale.com/wgengine/netstack" @@ -124,15 +125,17 @@ func newIPN(jsConfig js.Value) map[string]any { return ns.DialContextTCP(ctx, dst) } - srv, err := ipnserver.New(logf, lpc.PublicID.String(), store, eng, dialer, ipnserver.Options{ - SurviveDisconnects: true, - LoginFlags: controlclient.LoginEphemeral, - AutostartStateKey: "wasm", - }) + logid := lpc.PublicID.String() + srv := ipnserver.New(logf, logid) + + lb, err := ipnlocal.NewLocalBackend(logf, logid, store, "wasm", dialer, eng, controlclient.LoginEphemeral) if err != nil { - log.Fatalf("ipnserver.New: %v", err) + log.Fatalf("ipnlocal.NewLocalBackend: %v", err) } - lb := srv.LocalBackend() + lb.SetDecompressor(func() (controlclient.Decompressor, error) { + return smallzstd.NewDecoder(nil) + }) + srv.SetLocalBackend(lb) ns.SetLocalBackend(lb) jsIPN := &jsIPN{ diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go index ab22d0482..a9181393e 100644 --- a/ipn/ipnserver/server.go +++ b/ipn/ipnserver/server.go @@ -11,69 +11,27 @@ import ( "io" "net" "net/http" - "os" "os/user" - "path/filepath" "strings" "sync" + "sync/atomic" "time" "unicode" - "tailscale.com/control/controlclient" "tailscale.com/envknob" "tailscale.com/ipn" "tailscale.com/ipn/ipnauth" "tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/localapi" - "tailscale.com/net/dnsfallback" - "tailscale.com/net/tsdial" - "tailscale.com/smallzstd" "tailscale.com/types/logger" "tailscale.com/util/mak" "tailscale.com/util/systemd" - "tailscale.com/version/distro" - "tailscale.com/wgengine" ) -// Options is the configuration of the Tailscale node agent. -type Options struct { - // VarRoot is the Tailscale daemon's private writable - // directory (usually "/var/lib/tailscale" on Linux) that - // contains the "tailscaled.state" file, the "certs" directory - // for TLS certs, and the "files" directory for incoming - // Taildrop files before they're moved to a user directory. - // If empty, Taildrop and TLS certs don't function. - VarRoot string - - // AutostartStateKey, if non-empty, immediately starts the agent - // using the given StateKey. If empty, the agent stays idle and - // waits for a frontend to start it. - AutostartStateKey ipn.StateKey - - // SurviveDisconnects specifies how the server reacts to its - // frontend disconnecting. If true, the server keeps running on - // its existing state, and accepts new frontend connections. If - // false, the server dumps its state and becomes idle. - // - // This is effectively whether the platform is in "server - // mode" by default. On Linux, it's true; on Windows, it's - // false. But on some platforms (currently only Windows), the - // "server mode" can be overridden at runtime with a change in - // Prefs.ForceDaemon/WantRunning. - // - // To support CLI connections (notably, "tailscale status"), - // the actual definition of "disconnect" is when the - // connection count transitions from 1 to 0. - SurviveDisconnects bool - - // LoginFlags specifies the LoginFlags to pass to the client. - LoginFlags controlclient.LoginFlags -} - // Server is an IPN backend and its set of 0 or more active localhost // TCP or unix socket connections talking to that backend. type Server struct { - b *ipnlocal.LocalBackend + lb atomic.Pointer[ipnlocal.LocalBackend] logf logger.Logf backendLogID string // resetOnZero is whether to call bs.Reset on transition from @@ -83,6 +41,9 @@ type Server struct { // is true, the ForceDaemon pref can override this. resetOnZero bool + startBackendOnce sync.Once + runCalled atomic.Bool + // mu guards the fields that follow. // lock order: mu, then LocalBackend.mu mu sync.Mutex @@ -90,8 +51,13 @@ type Server struct { activeReqs map[*http.Request]*ipnauth.ConnIdentity } -// LocalBackend returns the server's LocalBackend. -func (s *Server) LocalBackend() *ipnlocal.LocalBackend { return s.b } +func (s *Server) mustBackend() *ipnlocal.LocalBackend { + lb := s.lb.Load() + if lb == nil { + panic("unexpected: call to mustBackend in path where SetLocalBackend should've been called") + } + return lb +} func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) { if r.Method == "CONNECT" { @@ -104,6 +70,15 @@ func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) { return } + // TODO(bradfitz): add a status HTTP handler that returns whether there's a + // LocalBackend yet, optionally blocking until there is one. See + // https://github.com/tailscale/tailscale/issues/6522 + lb := s.lb.Load() + if lb == nil { + http.Error(w, "no backend", http.StatusServiceUnavailable) + return + } + var ci *ipnauth.ConnIdentity switch v := r.Context().Value(connIdentityContextKey{}).(type) { case *ipnauth.ConnIdentity: @@ -124,7 +99,7 @@ func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) { defer onDone() if strings.HasPrefix(r.URL.Path, "/localapi/") { - lah := localapi.NewHandler(s.b, s.logf, s.backendLogID) + lah := localapi.NewHandler(lb, s.logf, s.backendLogID) lah.PermitRead, lah.PermitWrite = s.localAPIPermissions(ci) lah.PermitCert = s.connCanFetchCerts(ci) lah.ServeHTTP(w, r) @@ -171,7 +146,7 @@ func (s *Server) checkConnIdentityLocked(ci *ipnauth.ConnIdentity) error { return inUseOtherUserError{fmt.Errorf("Tailscale already in use by %s, pid %d", active.User().Username, active.Pid())} } } - if err := s.b.CheckIPNConnectionAllowed(ci); err != nil { + if err := s.mustBackend().CheckIPNConnectionAllowed(ci); err != nil { return inUseOtherUserError{err} } return nil @@ -194,7 +169,7 @@ func (s *Server) localAPIPermissions(ci *ipnauth.ConnIdentity) (read, write bool return true, true } if ci.IsUnixSock() { - return true, !ci.IsReadonlyConn(s.b.OperatorUserID(), logger.Discard) + return true, !ci.IsReadonlyConn(s.mustBackend().OperatorUserID(), logger.Discard) } return false, false } @@ -252,13 +227,15 @@ func (s *Server) addActiveHTTPRequest(req *http.Request, ci *ipnauth.ConnIdentit return nil, errors.New("internal error: nil connIdentity") } + lb := s.mustBackend() + // If the connected user changes, reset the backend server state to make // sure node keys don't leak between users. var doReset bool defer func() { if doReset { s.logf("identity changed; resetting server") - s.b.ResetForClientDisconnect() + lb.ResetForClientDisconnect() } }() @@ -273,7 +250,7 @@ func (s *Server) addActiveHTTPRequest(req *http.Request, ci *ipnauth.ConnIdentit if uid := ci.WindowsUserID(); uid != "" && len(s.activeReqs) == 1 { // Tell the LocalBackend about the identity we're now running as. - s.b.SetCurrentUserID(uid) + lb.SetCurrentUserID(uid) if s.lastUserID != uid { if s.lastUserID != "" { doReset = true @@ -289,11 +266,11 @@ func (s *Server) addActiveHTTPRequest(req *http.Request, ci *ipnauth.ConnIdentit s.mu.Unlock() if remain == 0 && s.resetOnZero { - if s.b.InServerMode() { + if lb.InServerMode() { s.logf("client disconnected; staying alive in server mode") } else { s.logf("client disconnected; stopping server") - s.b.ResetForClientDisconnect() + lb.ResetForClientDisconnect() } } } @@ -304,43 +281,46 @@ func (s *Server) addActiveHTTPRequest(req *http.Request, ci *ipnauth.ConnIdentit // New returns a new Server. // // To start it, use the Server.Run method. -func New(logf logger.Logf, logid string, store ipn.StateStore, eng wgengine.Engine, dialer *tsdial.Dialer, opts Options) (*Server, error) { - b, err := ipnlocal.NewLocalBackend(logf, logid, store, opts.AutostartStateKey, dialer, eng, opts.LoginFlags) - if err != nil { - return nil, fmt.Errorf("NewLocalBackend: %v", err) - } - b.SetVarRoot(opts.VarRoot) - b.SetDecompressor(func() (controlclient.Decompressor, error) { - return smallzstd.NewDecoder(nil) - }) - - if root := b.TailscaleVarRoot(); root != "" { - dnsfallback.SetCachePath(filepath.Join(root, "derpmap.cached.json")) +// +// At some point, either before or after Run, the Server's SetLocalBackend +// method must also be called before Server can do anything useful. +func New(logf logger.Logf, logid string) *Server { + return &Server{ + backendLogID: logid, + logf: logf, + resetOnZero: envknob.GOOS() == "windows", } +} - dg := distro.Get() - switch dg { - case distro.Synology, distro.TrueNAS, distro.QNAP: - // See if they have a "Taildrop" share. - // See https://github.com/tailscale/tailscale/issues/2179#issuecomment-982821319 - path, err := findTaildropDir(dg) - if err != nil { - logf("%s Taildrop support: %v", dg, err) - } else { - logf("%s Taildrop: using %v", dg, path) - b.SetDirectFileRoot(path) - b.SetDirectFileDoFinalRename(true) - } +// SetLocalBackend sets the server's LocalBackend. +// +// If b.Run has already been called, then lb.Start will be called. +// Otherwise Start will be called once Run is called. +func (s *Server) SetLocalBackend(lb *ipnlocal.LocalBackend) { + if lb == nil { + panic("nil LocalBackend") + } + if !s.lb.CompareAndSwap(nil, lb) { + panic("already set") + } + s.startBackendIfNeeded() + // TODO(bradfitz): send status update to GUI long poller waiter. See + // https://github.com/tailscale/tailscale/issues/6522 +} +func (b *Server) startBackendIfNeeded() { + if !b.runCalled.Load() { + return } - - server := &Server{ - b: b, - backendLogID: logid, - logf: logf, - resetOnZero: !opts.SurviveDisconnects, + lb := b.lb.Load() + if lb == nil { + return + } + if lb.Prefs().Valid() { + b.startBackendOnce.Do(func() { + lb.Start(ipn.Options{}) + }) } - return server, nil } // connIdentityContextKey is the http.Request.Context's context.Value key for either an @@ -351,8 +331,16 @@ type connIdentityContextKey struct{} // // If the context is done, the listener is closed. It is also the base context // of all HTTP requests. +// +// If the Server's LocalBackend has already been set, Run starts it. +// Otherwise, the next call to SetLocalBackend will start it. func (s *Server) Run(ctx context.Context, ln net.Listener) error { - defer s.b.Shutdown() + s.runCalled.Store(true) + defer func() { + if lb := s.lb.Load(); lb != nil { + lb.Shutdown() + } + }() runDone := make(chan struct{}) defer close(runDone) @@ -367,9 +355,7 @@ func (s *Server) Run(ctx context.Context, ln net.Listener) error { ln.Close() }() - if s.b.Prefs().Valid() { - s.b.Start(ipn.Options{}) - } + s.startBackendIfNeeded() systemd.Ready() hs := &http.Server{ @@ -404,6 +390,12 @@ func (s *Server) Run(ctx context.Context, ln net.Listener) error { // Windows and via $DEBUG_LISTENER/debug/ipn when tailscaled's --debug flag // is used to run a debug server. func (s *Server) ServeHTMLStatus(w http.ResponseWriter, r *http.Request) { + lb := s.lb.Load() + if lb == nil { + http.Error(w, "no LocalBackend", http.StatusServiceUnavailable) + return + } + // As this is only meant for debug, verify there's no DNS name being used to // access this. if !strings.HasPrefix(r.Host, "localhost:") && strings.IndexFunc(r.Host, unicode.IsLetter) != -1 { @@ -415,78 +407,7 @@ func (s *Server) ServeHTMLStatus(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Frame-Options", "DENY") w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("Content-Type", "text/html; charset=utf-8") - st := s.b.Status() + st := lb.Status() // TODO(bradfitz): add LogID and opts to st? st.WriteHTML(w) } - -func findTaildropDir(dg distro.Distro) (string, error) { - const name = "Taildrop" - switch dg { - case distro.Synology: - return findSynologyTaildropDir(name) - case distro.TrueNAS: - return findTrueNASTaildropDir(name) - case distro.QNAP: - return findQnapTaildropDir(name) - } - return "", fmt.Errorf("%s is an unsupported distro for Taildrop dir", dg) -} - -// findSynologyTaildropDir looks for the first volume containing a -// "Taildrop" directory. We'd run "synoshare --get Taildrop" command -// but on DSM7 at least, we lack permissions to run that. -func findSynologyTaildropDir(name string) (dir string, err error) { - for i := 1; i <= 16; i++ { - dir = fmt.Sprintf("/volume%v/%s", i, name) - if fi, err := os.Stat(dir); err == nil && fi.IsDir() { - return dir, nil - } - } - return "", fmt.Errorf("shared folder %q not found", name) -} - -// findTrueNASTaildropDir returns the first matching directory of -// /mnt/{name} or /mnt/*/{name} -func findTrueNASTaildropDir(name string) (dir string, err error) { - // If we're running in a jail, a mount point could just be added at /mnt/Taildrop - dir = fmt.Sprintf("/mnt/%s", name) - if fi, err := os.Stat(dir); err == nil && fi.IsDir() { - return dir, nil - } - - // but if running on the host, it may be something like /mnt/Primary/Taildrop - fis, err := os.ReadDir("/mnt") - if err != nil { - return "", fmt.Errorf("error reading /mnt: %w", err) - } - for _, fi := range fis { - dir = fmt.Sprintf("/mnt/%s/%s", fi.Name(), name) - if fi, err := os.Stat(dir); err == nil && fi.IsDir() { - return dir, nil - } - } - return "", fmt.Errorf("shared folder %q not found", name) -} - -// findQnapTaildropDir checks if a Shared Folder named "Taildrop" exists. -func findQnapTaildropDir(name string) (string, error) { - dir := fmt.Sprintf("/share/%s", name) - fi, err := os.Stat(dir) - if err != nil { - return "", fmt.Errorf("shared folder %q not found", name) - } - if fi.IsDir() { - return dir, nil - } - - // share/Taildrop is usually a symlink to CACHEDEV1_DATA/Taildrop/ or some such. - fullpath, err := filepath.EvalSymlinks(dir) - if err != nil { - return "", fmt.Errorf("symlink to shared folder %q not found", name) - } - if fi, err = os.Stat(fullpath); err == nil && fi.IsDir() { - return dir, nil // return the symlink, how QNAP set it up - } - return "", fmt.Errorf("shared folder %q not found", name) -} diff --git a/tstest/integration/tailscaled_deps_test_darwin.go b/tstest/integration/tailscaled_deps_test_darwin.go index b5250c39c..723f51617 100644 --- a/tstest/integration/tailscaled_deps_test_darwin.go +++ b/tstest/integration/tailscaled_deps_test_darwin.go @@ -17,11 +17,13 @@ import ( _ "tailscale.com/derp/derphttp" _ "tailscale.com/envknob" _ "tailscale.com/ipn" + _ "tailscale.com/ipn/ipnlocal" _ "tailscale.com/ipn/ipnserver" _ "tailscale.com/ipn/store" _ "tailscale.com/logpolicy" _ "tailscale.com/logtail" _ "tailscale.com/net/dns" + _ "tailscale.com/net/dnsfallback" _ "tailscale.com/net/interfaces" _ "tailscale.com/net/netns" _ "tailscale.com/net/portmapper" @@ -32,6 +34,7 @@ import ( _ "tailscale.com/net/tstun" _ "tailscale.com/paths" _ "tailscale.com/safesocket" + _ "tailscale.com/smallzstd" _ "tailscale.com/ssh/tailssh" _ "tailscale.com/tailcfg" _ "tailscale.com/tsweb" diff --git a/tstest/integration/tailscaled_deps_test_freebsd.go b/tstest/integration/tailscaled_deps_test_freebsd.go index b5250c39c..723f51617 100644 --- a/tstest/integration/tailscaled_deps_test_freebsd.go +++ b/tstest/integration/tailscaled_deps_test_freebsd.go @@ -17,11 +17,13 @@ import ( _ "tailscale.com/derp/derphttp" _ "tailscale.com/envknob" _ "tailscale.com/ipn" + _ "tailscale.com/ipn/ipnlocal" _ "tailscale.com/ipn/ipnserver" _ "tailscale.com/ipn/store" _ "tailscale.com/logpolicy" _ "tailscale.com/logtail" _ "tailscale.com/net/dns" + _ "tailscale.com/net/dnsfallback" _ "tailscale.com/net/interfaces" _ "tailscale.com/net/netns" _ "tailscale.com/net/portmapper" @@ -32,6 +34,7 @@ import ( _ "tailscale.com/net/tstun" _ "tailscale.com/paths" _ "tailscale.com/safesocket" + _ "tailscale.com/smallzstd" _ "tailscale.com/ssh/tailssh" _ "tailscale.com/tailcfg" _ "tailscale.com/tsweb" diff --git a/tstest/integration/tailscaled_deps_test_linux.go b/tstest/integration/tailscaled_deps_test_linux.go index b5250c39c..723f51617 100644 --- a/tstest/integration/tailscaled_deps_test_linux.go +++ b/tstest/integration/tailscaled_deps_test_linux.go @@ -17,11 +17,13 @@ import ( _ "tailscale.com/derp/derphttp" _ "tailscale.com/envknob" _ "tailscale.com/ipn" + _ "tailscale.com/ipn/ipnlocal" _ "tailscale.com/ipn/ipnserver" _ "tailscale.com/ipn/store" _ "tailscale.com/logpolicy" _ "tailscale.com/logtail" _ "tailscale.com/net/dns" + _ "tailscale.com/net/dnsfallback" _ "tailscale.com/net/interfaces" _ "tailscale.com/net/netns" _ "tailscale.com/net/portmapper" @@ -32,6 +34,7 @@ import ( _ "tailscale.com/net/tstun" _ "tailscale.com/paths" _ "tailscale.com/safesocket" + _ "tailscale.com/smallzstd" _ "tailscale.com/ssh/tailssh" _ "tailscale.com/tailcfg" _ "tailscale.com/tsweb" diff --git a/tstest/integration/tailscaled_deps_test_openbsd.go b/tstest/integration/tailscaled_deps_test_openbsd.go index 37bf376c5..ecb62f533 100644 --- a/tstest/integration/tailscaled_deps_test_openbsd.go +++ b/tstest/integration/tailscaled_deps_test_openbsd.go @@ -17,11 +17,13 @@ import ( _ "tailscale.com/derp/derphttp" _ "tailscale.com/envknob" _ "tailscale.com/ipn" + _ "tailscale.com/ipn/ipnlocal" _ "tailscale.com/ipn/ipnserver" _ "tailscale.com/ipn/store" _ "tailscale.com/logpolicy" _ "tailscale.com/logtail" _ "tailscale.com/net/dns" + _ "tailscale.com/net/dnsfallback" _ "tailscale.com/net/interfaces" _ "tailscale.com/net/netns" _ "tailscale.com/net/portmapper" @@ -32,6 +34,7 @@ import ( _ "tailscale.com/net/tstun" _ "tailscale.com/paths" _ "tailscale.com/safesocket" + _ "tailscale.com/smallzstd" _ "tailscale.com/tailcfg" _ "tailscale.com/tsweb" _ "tailscale.com/types/flagtype" diff --git a/tstest/integration/tailscaled_deps_test_windows.go b/tstest/integration/tailscaled_deps_test_windows.go index b00d59b15..cc826abab 100644 --- a/tstest/integration/tailscaled_deps_test_windows.go +++ b/tstest/integration/tailscaled_deps_test_windows.go @@ -22,12 +22,14 @@ import ( _ "tailscale.com/derp/derphttp" _ "tailscale.com/envknob" _ "tailscale.com/ipn" + _ "tailscale.com/ipn/ipnlocal" _ "tailscale.com/ipn/ipnserver" _ "tailscale.com/ipn/store" _ "tailscale.com/logpolicy" _ "tailscale.com/logtail" _ "tailscale.com/logtail/backoff" _ "tailscale.com/net/dns" + _ "tailscale.com/net/dnsfallback" _ "tailscale.com/net/interfaces" _ "tailscale.com/net/netns" _ "tailscale.com/net/portmapper" @@ -38,6 +40,7 @@ import ( _ "tailscale.com/net/tstun" _ "tailscale.com/paths" _ "tailscale.com/safesocket" + _ "tailscale.com/smallzstd" _ "tailscale.com/tailcfg" _ "tailscale.com/tsweb" _ "tailscale.com/types/flagtype"