mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
tsnet: add some usability polish, remove WIP env var restriction
Change-Id: Id9ec1713c65cdd597d20b03e21e11cd60b54bb6a Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
997b19545b
commit
2708544018
149
tsnet/tsnet.go
149
tsnet/tsnet.go
@ -9,7 +9,6 @@
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
@ -66,12 +65,15 @@ type Server struct {
|
||||
// as an Ephemeral node (https://tailscale.com/kb/1111/ephemeral-nodes/).
|
||||
Ephemeral bool
|
||||
|
||||
initOnce sync.Once
|
||||
initErr error
|
||||
lb *ipnlocal.LocalBackend
|
||||
// the state directory
|
||||
rootPath string
|
||||
hostname string
|
||||
initOnce sync.Once
|
||||
initErr error
|
||||
lb *ipnlocal.LocalBackend
|
||||
linkMon *monitor.Mon
|
||||
localAPIListener net.Listener
|
||||
rootPath string // the state directory
|
||||
hostname string
|
||||
shutdownCtx context.Context
|
||||
shutdownCancel context.CancelFunc
|
||||
|
||||
mu sync.Mutex
|
||||
listeners map[listenKey]*listener
|
||||
@ -94,17 +96,34 @@ func (s *Server) Start() error {
|
||||
return s.initErr
|
||||
}
|
||||
|
||||
// Close stops the server.
|
||||
//
|
||||
// It must not be called before or concurrently with Start.
|
||||
func (s *Server) Close() error {
|
||||
s.shutdownCancel()
|
||||
s.lb.Shutdown()
|
||||
s.linkMon.Close()
|
||||
s.localAPIListener.Close()
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
for _, ln := range s.listeners {
|
||||
ln.Close()
|
||||
}
|
||||
s.listeners = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) doInit() {
|
||||
|
||||
s.shutdownCtx, s.shutdownCancel = context.WithCancel(context.Background())
|
||||
if err := s.start(); err != nil {
|
||||
s.initErr = fmt.Errorf("tsnet: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) start() error {
|
||||
if !envknob.UseWIPCode() {
|
||||
return errors.New("code disabled without environment variable TAILSCALE_USE_WIP_CODE set true")
|
||||
}
|
||||
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -122,12 +141,18 @@ func (s *Server) start() error {
|
||||
return fmt.Errorf("in-memory store is only supported for Ephemeral nodes")
|
||||
}
|
||||
}
|
||||
|
||||
logf := s.logf
|
||||
|
||||
if s.rootPath == "" {
|
||||
confDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.rootPath = filepath.Join(confDir, "tslib-"+prog)
|
||||
s.rootPath, err = getTSNetDir(logf, confDir, prog)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(s.rootPath, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -138,15 +163,10 @@ func (s *Server) start() error {
|
||||
return fmt.Errorf("%v is not a directory", s.rootPath)
|
||||
}
|
||||
|
||||
logf := s.Logf
|
||||
if logf == nil {
|
||||
logf = log.Printf
|
||||
}
|
||||
|
||||
// TODO(bradfitz): start logtail? don't use filch, perhaps?
|
||||
// only upload plumbed Logf?
|
||||
|
||||
linkMon, err := monitor.New(logf)
|
||||
s.linkMon, err = monitor.New(logf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -154,7 +174,7 @@ func (s *Server) start() error {
|
||||
s.dialer = new(tsdial.Dialer) // mutated below (before used)
|
||||
eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
|
||||
ListenPort: 0,
|
||||
LinkMonitor: linkMon,
|
||||
LinkMonitor: s.linkMon,
|
||||
Dialer: s.dialer,
|
||||
})
|
||||
if err != nil {
|
||||
@ -184,12 +204,14 @@ func (s *Server) start() error {
|
||||
}
|
||||
|
||||
if s.Store == nil {
|
||||
s.Store, err = store.New(logf, filepath.Join(s.rootPath, "tailscaled.state"))
|
||||
stateFile := filepath.Join(s.rootPath, "tailscaled.state")
|
||||
logf("tsnet running state path %s", stateFile)
|
||||
s.Store, err = store.New(logf, stateFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
logid := "tslib-TODO"
|
||||
logid := "tsnet-TODO" // https://github.com/tailscale/tailscale/issues/3866
|
||||
|
||||
loginFlags := controlclient.LoginDefault
|
||||
if s.Ephemeral {
|
||||
@ -200,6 +222,7 @@ func (s *Server) start() error {
|
||||
return fmt.Errorf("NewLocalBackend: %v", err)
|
||||
}
|
||||
lb.SetVarRoot(s.rootPath)
|
||||
logf("tsnet starting with hostname %q, varRoot %q", s.hostname, s.rootPath)
|
||||
s.lb = lb
|
||||
lb.SetDecompressor(func() (controlclient.Decompressor, error) {
|
||||
return smallzstd.NewDecoder(nil)
|
||||
@ -207,17 +230,22 @@ func (s *Server) start() error {
|
||||
prefs := ipn.NewPrefs()
|
||||
prefs.Hostname = s.hostname
|
||||
prefs.WantRunning = true
|
||||
authKey := os.Getenv("TS_AUTHKEY")
|
||||
err = lb.Start(ipn.Options{
|
||||
StateKey: ipn.GlobalDaemonStateKey,
|
||||
UpdatePrefs: prefs,
|
||||
AuthKey: os.Getenv("TS_AUTHKEY"),
|
||||
AuthKey: authKey,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("starting backend: %w", err)
|
||||
}
|
||||
if os.Getenv("TS_LOGIN") == "1" || os.Getenv("TS_AUTHKEY") != "" {
|
||||
if lb.State() == ipn.NeedsLogin || envknob.Bool("TSNET_FORCE_LOGIN") {
|
||||
logf("LocalBackend state is %v; running StartLoginInteractive...")
|
||||
s.lb.StartLoginInteractive()
|
||||
} else if authKey != "" {
|
||||
logf("TS_AUTHKEY is set; but state is %v. Ignoring authkey. Re-run with TSNET_FORCE_LOGIN=1 to force use of authkey.")
|
||||
}
|
||||
go s.printAuthURLLoop()
|
||||
|
||||
// Run the localapi handler, to allow fetching LetsEncrypt certs.
|
||||
lah := localapi.NewHandler(lb, logf, logid)
|
||||
@ -228,6 +256,7 @@ func (s *Server) start() error {
|
||||
// nettest.Listen provides a in-memory pipe based implementation for net.Conn.
|
||||
// TODO(maisem): Rename nettest package to remove "test".
|
||||
lal := nettest.Listen("local-tailscaled.sock:80")
|
||||
s.localAPIListener = lal
|
||||
|
||||
// Override the Tailscale client to use the in-process listener.
|
||||
tailscale.TailscaledDialer = lal.Dial
|
||||
@ -239,6 +268,37 @@ func (s *Server) start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) logf(format string, a ...interface{}) {
|
||||
if s.Logf != nil {
|
||||
s.Logf(format, a...)
|
||||
return
|
||||
}
|
||||
log.Printf(format, a...)
|
||||
}
|
||||
|
||||
// printAuthURLLoop loops once every few seconds while the server is still running and
|
||||
// is in NeedsLogin state, printing out the auth URL.
|
||||
func (s *Server) printAuthURLLoop() {
|
||||
for {
|
||||
if s.shutdownCtx.Err() != nil {
|
||||
return
|
||||
}
|
||||
if st := s.lb.State(); st != ipn.NeedsLogin {
|
||||
s.logf("printAuthURLLoop: state is %v; stopping", st)
|
||||
return
|
||||
}
|
||||
st := s.lb.StatusWithoutPeers()
|
||||
if st.AuthURL != "" {
|
||||
s.logf("To start this tsnet server, restart with TS_AUTHKEY set, or go to: %s", st.AuthURL)
|
||||
}
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
case <-s.shutdownCtx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) forwardTCP(c net.Conn, port uint16) {
|
||||
s.mu.Lock()
|
||||
ln, ok := s.listeners[listenKey{"tcp", "", fmt.Sprint(port)}]
|
||||
@ -256,6 +316,49 @@ func (s *Server) forwardTCP(c net.Conn, port uint16) {
|
||||
}
|
||||
}
|
||||
|
||||
// getTSNetDir usually just returns filepath.Join(confDir, "tsnet-"+prog)
|
||||
// with no error.
|
||||
//
|
||||
// One special case is that it renames old "tslib-" directories to
|
||||
// "tsnet-", and that rename might return an error.
|
||||
//
|
||||
// TODO(bradfitz): remove this maybe 6 months after 2022-03-17,
|
||||
// once people (notably Tailscale corp services) have updated.
|
||||
func getTSNetDir(logf logger.Logf, confDir, prog string) (string, error) {
|
||||
oldPath := filepath.Join(confDir, "tslib-"+prog)
|
||||
newPath := filepath.Join(confDir, "tsnet-"+prog)
|
||||
|
||||
fi, err := os.Lstat(oldPath)
|
||||
if os.IsNotExist(err) {
|
||||
// Common path.
|
||||
return newPath, nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return "", fmt.Errorf("expected old tslib path %q to be a directory; got %v", oldPath, fi.Mode())
|
||||
}
|
||||
|
||||
// At this point, oldPath exists and is a directory. But does
|
||||
// the new path exist?
|
||||
|
||||
fi, err = os.Lstat(newPath)
|
||||
if err == nil && fi.IsDir() {
|
||||
// New path already exists somehow. Ignore the old one and
|
||||
// don't try to migrate it.
|
||||
return newPath, nil
|
||||
}
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
if err := os.Rename(oldPath, newPath); err != nil {
|
||||
return "", err
|
||||
}
|
||||
logf("renamed old tsnet state storage directory %q to %q", oldPath, newPath)
|
||||
return newPath, nil
|
||||
}
|
||||
|
||||
// Listen announces only on the Tailscale network.
|
||||
// It will start the server if it has not been started yet.
|
||||
func (s *Server) Listen(network, addr string) (net.Listener, error) {
|
||||
|
Loading…
Reference in New Issue
Block a user