mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-11-04 10:11:18 +01:00 
			
		
		
		
	c2n was already a conditional feature, but it didn't have a feature/c2n directory before (rather, it was using consts + DCE). This adds it, and moves some code, which removes the httprec dependency. Also, remove some unnecessary code from our httprec fork. Updates #12614 Change-Id: I2fbe538e09794c517038e35a694a363312c426a2 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
		
			
				
	
	
		
			1371 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1371 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) Tailscale Inc & AUTHORS
 | 
						|
// SPDX-License-Identifier: BSD-3-Clause
 | 
						|
 | 
						|
// Package tsnet provides Tailscale as a library.
 | 
						|
package tsnet
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	crand "crypto/rand"
 | 
						|
	"crypto/tls"
 | 
						|
	"encoding/hex"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"log"
 | 
						|
	"math"
 | 
						|
	"net"
 | 
						|
	"net/http"
 | 
						|
	"net/netip"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"runtime"
 | 
						|
	"slices"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"tailscale.com/client/local"
 | 
						|
	"tailscale.com/control/controlclient"
 | 
						|
	"tailscale.com/envknob"
 | 
						|
	_ "tailscale.com/feature/c2n"
 | 
						|
	_ "tailscale.com/feature/condregister/oauthkey"
 | 
						|
	_ "tailscale.com/feature/condregister/portmapper"
 | 
						|
	_ "tailscale.com/feature/condregister/useproxy"
 | 
						|
	"tailscale.com/health"
 | 
						|
	"tailscale.com/hostinfo"
 | 
						|
	"tailscale.com/internal/client/tailscale"
 | 
						|
	"tailscale.com/ipn"
 | 
						|
	"tailscale.com/ipn/ipnauth"
 | 
						|
	"tailscale.com/ipn/ipnlocal"
 | 
						|
	"tailscale.com/ipn/ipnstate"
 | 
						|
	"tailscale.com/ipn/localapi"
 | 
						|
	"tailscale.com/ipn/store"
 | 
						|
	"tailscale.com/ipn/store/mem"
 | 
						|
	"tailscale.com/logpolicy"
 | 
						|
	"tailscale.com/logtail"
 | 
						|
	"tailscale.com/logtail/filch"
 | 
						|
	"tailscale.com/net/memnet"
 | 
						|
	"tailscale.com/net/netmon"
 | 
						|
	"tailscale.com/net/proxymux"
 | 
						|
	"tailscale.com/net/socks5"
 | 
						|
	"tailscale.com/net/tsdial"
 | 
						|
	"tailscale.com/tsd"
 | 
						|
	"tailscale.com/types/bools"
 | 
						|
	"tailscale.com/types/logger"
 | 
						|
	"tailscale.com/types/logid"
 | 
						|
	"tailscale.com/types/nettype"
 | 
						|
	"tailscale.com/util/clientmetric"
 | 
						|
	"tailscale.com/util/mak"
 | 
						|
	"tailscale.com/util/set"
 | 
						|
	"tailscale.com/util/testenv"
 | 
						|
	"tailscale.com/wgengine"
 | 
						|
	"tailscale.com/wgengine/netstack"
 | 
						|
)
 | 
						|
 | 
						|
// Server is an embedded Tailscale server.
 | 
						|
//
 | 
						|
// Its exported fields may be changed until the first method call.
 | 
						|
type Server struct {
 | 
						|
	// Dir specifies the name of the directory to use for
 | 
						|
	// state. If empty, a directory is selected automatically
 | 
						|
	// under os.UserConfigDir (https://golang.org/pkg/os/#UserConfigDir).
 | 
						|
	// based on the name of the binary.
 | 
						|
	//
 | 
						|
	// If you want to use multiple tsnet services in the same
 | 
						|
	// binary, you will need to make sure that Dir is set uniquely
 | 
						|
	// for each service. A good pattern for this is to have a
 | 
						|
	// "base" directory (such as your mutable storage folder) and
 | 
						|
	// then append the hostname on the end of it.
 | 
						|
	Dir string
 | 
						|
 | 
						|
	// Store specifies the state store to use.
 | 
						|
	//
 | 
						|
	// If nil, a new FileStore is initialized at `Dir/tailscaled.state`.
 | 
						|
	// See tailscale.com/ipn/store for supported stores.
 | 
						|
	//
 | 
						|
	// Logs will automatically be uploaded to log.tailscale.com,
 | 
						|
	// where the configuration file for logging will be saved at
 | 
						|
	// `Dir/tailscaled.log.conf`.
 | 
						|
	Store ipn.StateStore
 | 
						|
 | 
						|
	// Hostname is the hostname to present to the control server.
 | 
						|
	// If empty, the binary name is used.
 | 
						|
	Hostname string
 | 
						|
 | 
						|
	// UserLogf, if non-nil, specifies the logger to use for logs generated by
 | 
						|
	// the Server itself intended to be seen by the user such as the AuthURL for
 | 
						|
	// login and status updates. If unset, log.Printf is used.
 | 
						|
	UserLogf logger.Logf
 | 
						|
 | 
						|
	// Logf, if set is used for logs generated by the backend such as the
 | 
						|
	// LocalBackend and MagicSock. It is verbose and intended for debugging.
 | 
						|
	// If unset, logs are discarded.
 | 
						|
	Logf logger.Logf
 | 
						|
 | 
						|
	// Ephemeral, if true, specifies that the instance should register
 | 
						|
	// as an Ephemeral node (https://tailscale.com/s/ephemeral-nodes).
 | 
						|
	Ephemeral bool
 | 
						|
 | 
						|
	// AuthKey, if non-empty, is the auth key to create the node
 | 
						|
	// and will be preferred over the TS_AUTHKEY environment
 | 
						|
	// variable. If the node is already created (from state
 | 
						|
	// previously stored in Store), then this field is not
 | 
						|
	// used.
 | 
						|
	AuthKey string
 | 
						|
 | 
						|
	// ControlURL optionally specifies the coordination server URL.
 | 
						|
	// If empty, the Tailscale default is used.
 | 
						|
	ControlURL string
 | 
						|
 | 
						|
	// RunWebClient, if true, runs a client for managing this node over
 | 
						|
	// its Tailscale interface on port 5252.
 | 
						|
	RunWebClient bool
 | 
						|
 | 
						|
	// Port is the UDP port to listen on for WireGuard and peer-to-peer
 | 
						|
	// traffic. If zero, a port is automatically selected. Leave this
 | 
						|
	// field at zero unless you know what you are doing.
 | 
						|
	Port uint16
 | 
						|
 | 
						|
	// AdvertiseTags specifies tags that should be applied to this node, for
 | 
						|
	// purposes of ACL enforcement. These can be referenced from the ACL policy
 | 
						|
	// document. Note that advertising a tag on the client doesn't guarantee
 | 
						|
	// that the control server will allow the node to adopt that tag.
 | 
						|
	AdvertiseTags []string
 | 
						|
 | 
						|
	getCertForTesting func(*tls.ClientHelloInfo) (*tls.Certificate, error)
 | 
						|
 | 
						|
	initOnce         sync.Once
 | 
						|
	initErr          error
 | 
						|
	lb               *ipnlocal.LocalBackend
 | 
						|
	sys              *tsd.System
 | 
						|
	netstack         *netstack.Impl
 | 
						|
	netMon           *netmon.Monitor
 | 
						|
	rootPath         string // the state directory
 | 
						|
	hostname         string
 | 
						|
	shutdownCtx      context.Context
 | 
						|
	shutdownCancel   context.CancelFunc
 | 
						|
	proxyCred        string        // SOCKS5 proxy auth for loopbackListener
 | 
						|
	localAPICred     string        // basic auth password for loopbackListener
 | 
						|
	loopbackListener net.Listener  // optional loopback for localapi and proxies
 | 
						|
	localAPIListener net.Listener  // in-memory, used by localClient
 | 
						|
	localClient      *local.Client // in-memory
 | 
						|
	localAPIServer   *http.Server
 | 
						|
	logbuffer        *filch.Filch
 | 
						|
	logtail          *logtail.Logger
 | 
						|
	logid            logid.PublicID
 | 
						|
 | 
						|
	mu                  sync.Mutex
 | 
						|
	listeners           map[listenKey]*listener
 | 
						|
	fallbackTCPHandlers set.HandleSet[FallbackTCPHandler]
 | 
						|
	dialer              *tsdial.Dialer
 | 
						|
	closed              bool
 | 
						|
}
 | 
						|
 | 
						|
// FallbackTCPHandler describes the callback which
 | 
						|
// conditionally handles an incoming TCP flow for the
 | 
						|
// provided (src/port, dst/port) 4-tuple. These are registered
 | 
						|
// as handlers of last resort, and are called only if no
 | 
						|
// listener could handle the incoming flow.
 | 
						|
//
 | 
						|
// If the callback returns intercept=false, the flow is rejected.
 | 
						|
//
 | 
						|
// When intercept=true, the behavior depends on whether the returned handler
 | 
						|
// is non-nil: if nil, the connection is rejected. If non-nil, handler takes
 | 
						|
// over the TCP conn.
 | 
						|
type FallbackTCPHandler func(src, dst netip.AddrPort) (handler func(net.Conn), intercept bool)
 | 
						|
 | 
						|
// Dial connects to the address on the tailnet.
 | 
						|
// It will start the server if it has not been started yet.
 | 
						|
func (s *Server) Dial(ctx context.Context, network, address string) (net.Conn, error) {
 | 
						|
	if err := s.Start(); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if err := s.awaitRunning(ctx); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return s.dialer.UserDial(ctx, network, address)
 | 
						|
}
 | 
						|
 | 
						|
// awaitRunning waits until the backend is in state Running.
 | 
						|
// If the backend is in state Starting, it blocks until it reaches
 | 
						|
// a terminal state (such as Stopped, NeedsMachineAuth)
 | 
						|
// or the context expires.
 | 
						|
func (s *Server) awaitRunning(ctx context.Context) error {
 | 
						|
	st := s.lb.State()
 | 
						|
	for {
 | 
						|
		if err := ctx.Err(); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		switch st {
 | 
						|
		case ipn.Running:
 | 
						|
			return nil
 | 
						|
		case ipn.NeedsLogin, ipn.Starting:
 | 
						|
			// Even after LocalBackend.Start, the state machine is still briefly
 | 
						|
			// in the "NeedsLogin" state. So treat that as also "Starting" and
 | 
						|
			// wait for us to get out of that state.
 | 
						|
			s.lb.WatchNotifications(ctx, ipn.NotifyInitialState, nil, func(n *ipn.Notify) (keepGoing bool) {
 | 
						|
				if n.State != nil {
 | 
						|
					st = *n.State
 | 
						|
				}
 | 
						|
				return st == ipn.NeedsLogin || st == ipn.Starting
 | 
						|
			})
 | 
						|
		default:
 | 
						|
			return fmt.Errorf("tsnet: backend in state %v", st)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// HTTPClient returns an HTTP client that is configured to connect over Tailscale.
 | 
						|
//
 | 
						|
// This is useful if you need to have your tsnet services connect to other devices on
 | 
						|
// your tailnet.
 | 
						|
func (s *Server) HTTPClient() *http.Client {
 | 
						|
	return &http.Client{
 | 
						|
		Transport: &http.Transport{
 | 
						|
			DialContext: s.Dial,
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// LocalClient returns a LocalClient that speaks to s.
 | 
						|
//
 | 
						|
// It will start the server if it has not been started yet. If the server's
 | 
						|
// already been started successfully, it doesn't return an error.
 | 
						|
func (s *Server) LocalClient() (*local.Client, error) {
 | 
						|
	if err := s.Start(); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return s.localClient, nil
 | 
						|
}
 | 
						|
 | 
						|
// Loopback starts a routing server on a loopback address.
 | 
						|
//
 | 
						|
// The server has multiple functions.
 | 
						|
//
 | 
						|
// It can be used as a SOCKS5 proxy onto the tailnet.
 | 
						|
// Authentication is required with the username "tsnet" and
 | 
						|
// the value of proxyCred used as the password.
 | 
						|
//
 | 
						|
// The HTTP server also serves out the "LocalAPI" on /localapi.
 | 
						|
// As the LocalAPI is powerful, access to endpoints requires BOTH passing a
 | 
						|
// "Sec-Tailscale: localapi" HTTP header and passing localAPICred as basic auth.
 | 
						|
//
 | 
						|
// If you only need to use the LocalAPI from Go, then prefer LocalClient
 | 
						|
// as it does not require communication via TCP.
 | 
						|
func (s *Server) Loopback() (addr string, proxyCred, localAPICred string, err error) {
 | 
						|
	if err := s.Start(); err != nil {
 | 
						|
		return "", "", "", err
 | 
						|
	}
 | 
						|
 | 
						|
	if s.loopbackListener == nil {
 | 
						|
		var proxyCred [16]byte
 | 
						|
		if _, err := crand.Read(proxyCred[:]); err != nil {
 | 
						|
			return "", "", "", err
 | 
						|
		}
 | 
						|
		s.proxyCred = hex.EncodeToString(proxyCred[:])
 | 
						|
 | 
						|
		var cred [16]byte
 | 
						|
		if _, err := crand.Read(cred[:]); err != nil {
 | 
						|
			return "", "", "", err
 | 
						|
		}
 | 
						|
		s.localAPICred = hex.EncodeToString(cred[:])
 | 
						|
 | 
						|
		ln, err := net.Listen("tcp", "127.0.0.1:0")
 | 
						|
		if err != nil {
 | 
						|
			return "", "", "", err
 | 
						|
		}
 | 
						|
		s.loopbackListener = ln
 | 
						|
 | 
						|
		socksLn, httpLn := proxymux.SplitSOCKSAndHTTP(ln)
 | 
						|
 | 
						|
		// TODO: add HTTP proxy support. Probably requires factoring
 | 
						|
		// out the CONNECT code from tailscaled/proxy.go that uses
 | 
						|
		// httputil.ReverseProxy and adding auth support.
 | 
						|
		go func() {
 | 
						|
			lah := localapi.NewHandler(localapi.HandlerConfig{
 | 
						|
				Actor:    ipnauth.Self,
 | 
						|
				Backend:  s.lb,
 | 
						|
				Logf:     s.logf,
 | 
						|
				LogID:    s.logid,
 | 
						|
				EventBus: s.sys.Bus.Get(),
 | 
						|
			})
 | 
						|
			lah.PermitWrite = true
 | 
						|
			lah.PermitRead = true
 | 
						|
			lah.RequiredPassword = s.localAPICred
 | 
						|
			h := &localSecHandler{h: lah, cred: s.localAPICred}
 | 
						|
 | 
						|
			if err := http.Serve(httpLn, h); err != nil {
 | 
						|
				s.logf("localapi tcp serve error: %v", err)
 | 
						|
			}
 | 
						|
		}()
 | 
						|
		s5l := logger.WithPrefix(s.logf, "socks5: ")
 | 
						|
		s5s := &socks5.Server{
 | 
						|
			Logf:     s5l,
 | 
						|
			Dialer:   s.dialer.UserDial,
 | 
						|
			Username: "tsnet",
 | 
						|
			Password: s.proxyCred,
 | 
						|
		}
 | 
						|
		go func() {
 | 
						|
			s5l("SOCKS5 server exited: %v", s5s.Serve(socksLn))
 | 
						|
		}()
 | 
						|
	}
 | 
						|
 | 
						|
	lbAddr := s.loopbackListener.Addr()
 | 
						|
	if lbAddr == nil {
 | 
						|
		// https://github.com/tailscale/tailscale/issues/7488
 | 
						|
		panic("loopbackListener has no Addr")
 | 
						|
	}
 | 
						|
	return lbAddr.String(), s.proxyCred, s.localAPICred, nil
 | 
						|
}
 | 
						|
 | 
						|
type localSecHandler struct {
 | 
						|
	h    http.Handler
 | 
						|
	cred string
 | 
						|
}
 | 
						|
 | 
						|
func (h *localSecHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
						|
	if r.Header.Get("Sec-Tailscale") != "localapi" {
 | 
						|
		w.WriteHeader(403)
 | 
						|
		io.WriteString(w, "missing 'Sec-Tailscale: localapi' header")
 | 
						|
		return
 | 
						|
	}
 | 
						|
	h.h.ServeHTTP(w, r)
 | 
						|
}
 | 
						|
 | 
						|
// Start connects the server to the tailnet.
 | 
						|
// Optional: any calls to Dial/Listen will also call Start.
 | 
						|
func (s *Server) Start() error {
 | 
						|
	hostinfo.SetPackage("tsnet")
 | 
						|
	s.initOnce.Do(s.doInit)
 | 
						|
	return s.initErr
 | 
						|
}
 | 
						|
 | 
						|
// Up connects the server to the tailnet and waits until it is running.
 | 
						|
// On success it returns the current status, including a Tailscale IP address.
 | 
						|
func (s *Server) Up(ctx context.Context) (*ipnstate.Status, error) {
 | 
						|
	lc, err := s.LocalClient() // calls Start
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("tsnet.Up: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	watcher, err := lc.WatchIPNBus(ctx, ipn.NotifyInitialState|ipn.NotifyNoPrivateKeys)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("tsnet.Up: %w", err)
 | 
						|
	}
 | 
						|
	defer watcher.Close()
 | 
						|
 | 
						|
	for {
 | 
						|
		n, err := watcher.Next()
 | 
						|
		if err != nil {
 | 
						|
			return nil, fmt.Errorf("tsnet.Up: %w", err)
 | 
						|
		}
 | 
						|
		if n.ErrMessage != nil {
 | 
						|
			return nil, fmt.Errorf("tsnet.Up: backend: %s", *n.ErrMessage)
 | 
						|
		}
 | 
						|
		if s := n.State; s != nil {
 | 
						|
			if *s == ipn.Running {
 | 
						|
				status, err := lc.Status(ctx)
 | 
						|
				if err != nil {
 | 
						|
					return nil, fmt.Errorf("tsnet.Up: %w", err)
 | 
						|
				}
 | 
						|
				if len(status.TailscaleIPs) == 0 {
 | 
						|
					return nil, errors.New("tsnet.Up: running, but no ip")
 | 
						|
				}
 | 
						|
 | 
						|
				// Clear the persisted serve config state to prevent stale configuration
 | 
						|
				// from code changes. This is a temporary workaround until we have a better
 | 
						|
				// way to handle this. (2023-03-11)
 | 
						|
				if err := lc.SetServeConfig(ctx, new(ipn.ServeConfig)); err != nil {
 | 
						|
					return nil, fmt.Errorf("tsnet.Up: %w", err)
 | 
						|
				}
 | 
						|
 | 
						|
				return status, nil
 | 
						|
			}
 | 
						|
			// TODO: in the future, return an error on ipn.NeedsLogin
 | 
						|
			// and ipn.NeedsMachineAuth to improve the UX of trying
 | 
						|
			// out the tsnet package.
 | 
						|
			//
 | 
						|
			// Unfortunately today, even when using an AuthKey we
 | 
						|
			// briefly see these states. It would be nice to fix.
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Close stops the server.
 | 
						|
//
 | 
						|
// It must not be called before or concurrently with Start.
 | 
						|
func (s *Server) Close() error {
 | 
						|
	s.mu.Lock()
 | 
						|
	defer s.mu.Unlock()
 | 
						|
	if s.closed {
 | 
						|
		return fmt.Errorf("tsnet: %w", net.ErrClosed)
 | 
						|
	}
 | 
						|
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 | 
						|
	defer cancel()
 | 
						|
	var wg sync.WaitGroup
 | 
						|
	wg.Add(1)
 | 
						|
	go func() {
 | 
						|
		defer wg.Done()
 | 
						|
		// Perform a best-effort final flush.
 | 
						|
		if s.logtail != nil {
 | 
						|
			s.logtail.Shutdown(ctx)
 | 
						|
		}
 | 
						|
		if s.logbuffer != nil {
 | 
						|
			s.logbuffer.Close()
 | 
						|
		}
 | 
						|
	}()
 | 
						|
	wg.Add(1)
 | 
						|
	go func() {
 | 
						|
		defer wg.Done()
 | 
						|
		if s.localAPIServer != nil {
 | 
						|
			s.localAPIServer.Shutdown(ctx)
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	if s.netstack != nil {
 | 
						|
		s.netstack.Close()
 | 
						|
		s.netstack = nil
 | 
						|
	}
 | 
						|
	if s.shutdownCancel != nil {
 | 
						|
		s.shutdownCancel()
 | 
						|
	}
 | 
						|
	if s.lb != nil {
 | 
						|
		s.lb.Shutdown()
 | 
						|
	}
 | 
						|
	if s.netMon != nil {
 | 
						|
		s.netMon.Close()
 | 
						|
	}
 | 
						|
	if s.dialer != nil {
 | 
						|
		s.dialer.Close()
 | 
						|
	}
 | 
						|
	if s.localAPIListener != nil {
 | 
						|
		s.localAPIListener.Close()
 | 
						|
	}
 | 
						|
	if s.loopbackListener != nil {
 | 
						|
		s.loopbackListener.Close()
 | 
						|
	}
 | 
						|
 | 
						|
	for _, ln := range s.listeners {
 | 
						|
		ln.closeLocked()
 | 
						|
	}
 | 
						|
	wg.Wait()
 | 
						|
	s.sys.Bus.Get().Close()
 | 
						|
	s.closed = true
 | 
						|
	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)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// CertDomains returns the list of domains for which the server can
 | 
						|
// provide TLS certificates. These are also the DNS names for the
 | 
						|
// Server.
 | 
						|
// If the server is not running, it returns nil.
 | 
						|
func (s *Server) CertDomains() []string {
 | 
						|
	nm := s.lb.NetMap()
 | 
						|
	if nm == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	return slices.Clone(nm.DNS.CertDomains)
 | 
						|
}
 | 
						|
 | 
						|
// TailscaleIPs returns IPv4 and IPv6 addresses for this node. If the node
 | 
						|
// has not yet joined a tailnet or is otherwise unaware of its own IP addresses,
 | 
						|
// the returned ip4, ip6 will be !netip.IsValid().
 | 
						|
func (s *Server) TailscaleIPs() (ip4, ip6 netip.Addr) {
 | 
						|
	nm := s.lb.NetMap()
 | 
						|
	if nm == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	addrs := nm.GetAddresses()
 | 
						|
	for _, addr := range addrs.All() {
 | 
						|
		ip := addr.Addr()
 | 
						|
		if ip.Is6() {
 | 
						|
			ip6 = ip
 | 
						|
		}
 | 
						|
		if ip.Is4() {
 | 
						|
			ip4 = ip
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return ip4, ip6
 | 
						|
}
 | 
						|
 | 
						|
// LogtailWriter returns an [io.Writer] that writes to Tailscale's logging service and will be only visible to Tailscale's
 | 
						|
// support team. Logs written there cannot be retrieved by the user. This method always returns a non-nil value.
 | 
						|
func (s *Server) LogtailWriter() io.Writer {
 | 
						|
	if s.logtail == nil {
 | 
						|
		return io.Discard
 | 
						|
	}
 | 
						|
 | 
						|
	return s.logtail
 | 
						|
}
 | 
						|
 | 
						|
func (s *Server) getAuthKey() string {
 | 
						|
	if v := s.AuthKey; v != "" {
 | 
						|
		return v
 | 
						|
	}
 | 
						|
	if v := os.Getenv("TS_AUTHKEY"); v != "" {
 | 
						|
		return v
 | 
						|
	}
 | 
						|
	return os.Getenv("TS_AUTH_KEY")
 | 
						|
}
 | 
						|
 | 
						|
func (s *Server) start() (reterr error) {
 | 
						|
	var closePool closeOnErrorPool
 | 
						|
	defer closePool.closeAllIfError(&reterr)
 | 
						|
 | 
						|
	exe, err := os.Executable()
 | 
						|
	if err != nil {
 | 
						|
		switch runtime.GOOS {
 | 
						|
		case "js", "wasip1":
 | 
						|
			// These platforms don't implement os.Executable (at least as of Go
 | 
						|
			// 1.21), but we don't really care much: it's only used as a default
 | 
						|
			// directory and hostname when they're not supplied. But we can fall
 | 
						|
			// back to "tsnet" as well.
 | 
						|
			exe = "tsnet"
 | 
						|
		case "ios":
 | 
						|
			// When compiled as a framework (via TailscaleKit in libtailscale),
 | 
						|
			// os.Executable() returns an error, so fall back to "tsnet" there
 | 
						|
			// too.
 | 
						|
			exe = "tsnet"
 | 
						|
		default:
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	prog := strings.TrimSuffix(strings.ToLower(filepath.Base(exe)), ".exe")
 | 
						|
 | 
						|
	s.hostname = s.Hostname
 | 
						|
	if s.hostname == "" {
 | 
						|
		s.hostname = prog
 | 
						|
	}
 | 
						|
 | 
						|
	s.rootPath = s.Dir
 | 
						|
	if s.Store != nil {
 | 
						|
		_, isMemStore := s.Store.(*mem.Store)
 | 
						|
		if isMemStore && !s.Ephemeral {
 | 
						|
			return fmt.Errorf("in-memory store is only supported for Ephemeral nodes")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if s.rootPath == "" {
 | 
						|
		confDir, err := os.UserConfigDir()
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		s.rootPath = filepath.Join(confDir, "tsnet-"+prog)
 | 
						|
	}
 | 
						|
	if err := os.MkdirAll(s.rootPath, 0700); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if fi, err := os.Stat(s.rootPath); err != nil {
 | 
						|
		return err
 | 
						|
	} else if !fi.IsDir() {
 | 
						|
		return fmt.Errorf("%v is not a directory", s.rootPath)
 | 
						|
	}
 | 
						|
 | 
						|
	tsLogf := func(format string, a ...any) {
 | 
						|
		if s.logtail != nil {
 | 
						|
			s.logtail.Logf(format, a...)
 | 
						|
		}
 | 
						|
		if s.Logf == nil {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		s.Logf(format, a...)
 | 
						|
	}
 | 
						|
 | 
						|
	sys := tsd.NewSystem()
 | 
						|
	s.sys = sys
 | 
						|
	if err := s.startLogger(&closePool, sys.HealthTracker.Get(), tsLogf); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	s.netMon, err = netmon.New(sys.Bus.Get(), tsLogf)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	closePool.add(s.netMon)
 | 
						|
 | 
						|
	s.dialer = &tsdial.Dialer{Logf: tsLogf} // mutated below (before used)
 | 
						|
	s.dialer.SetBus(sys.Bus.Get())
 | 
						|
	eng, err := wgengine.NewUserspaceEngine(tsLogf, wgengine.Config{
 | 
						|
		EventBus:      sys.Bus.Get(),
 | 
						|
		ListenPort:    s.Port,
 | 
						|
		NetMon:        s.netMon,
 | 
						|
		Dialer:        s.dialer,
 | 
						|
		SetSubsystem:  sys.Set,
 | 
						|
		ControlKnobs:  sys.ControlKnobs(),
 | 
						|
		HealthTracker: sys.HealthTracker.Get(),
 | 
						|
		Metrics:       sys.UserMetricsRegistry(),
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	closePool.add(s.dialer)
 | 
						|
	sys.Set(eng)
 | 
						|
	sys.HealthTracker.Get().SetMetricsRegistry(sys.UserMetricsRegistry())
 | 
						|
 | 
						|
	// TODO(oxtoacart): do we need to support Taildrive on tsnet, and if so, how?
 | 
						|
	ns, err := netstack.Create(tsLogf, sys.Tun.Get(), eng, sys.MagicSock.Get(), s.dialer, sys.DNSManager.Get(), sys.ProxyMapper())
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("netstack.Create: %w", err)
 | 
						|
	}
 | 
						|
	sys.Tun.Get().Start()
 | 
						|
	sys.Set(ns)
 | 
						|
	ns.ProcessLocalIPs = true
 | 
						|
	ns.ProcessSubnets = true
 | 
						|
	ns.GetTCPHandlerForFlow = s.getTCPHandlerForFlow
 | 
						|
	ns.GetUDPHandlerForFlow = s.getUDPHandlerForFlow
 | 
						|
	s.netstack = ns
 | 
						|
	s.dialer.UseNetstackForIP = func(ip netip.Addr) bool {
 | 
						|
		_, ok := eng.PeerForIP(ip)
 | 
						|
		return ok
 | 
						|
	}
 | 
						|
	s.dialer.NetstackDialTCP = func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) {
 | 
						|
		// Note: don't just return ns.DialContextTCP or we'll return
 | 
						|
		// *gonet.TCPConn(nil) instead of a nil interface which trips up
 | 
						|
		// callers.
 | 
						|
		v4, v6 := s.TailscaleIPs()
 | 
						|
		src := bools.IfElse(dst.Addr().Is6(), v6, v4)
 | 
						|
		tcpConn, err := ns.DialContextTCPWithBind(ctx, src, dst)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		return tcpConn, nil
 | 
						|
	}
 | 
						|
	s.dialer.NetstackDialUDP = func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) {
 | 
						|
		// Note: don't just return ns.DialContextUDP or we'll return
 | 
						|
		// *gonet.UDPConn(nil) instead of a nil interface which trips up
 | 
						|
		// callers.
 | 
						|
		v4, v6 := s.TailscaleIPs()
 | 
						|
		src := bools.IfElse(dst.Addr().Is6(), v6, v4)
 | 
						|
		udpConn, err := ns.DialContextUDPWithBind(ctx, src, dst)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		return udpConn, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if s.Store == nil {
 | 
						|
		stateFile := filepath.Join(s.rootPath, "tailscaled.state")
 | 
						|
		s.logf("tsnet running state path %s", stateFile)
 | 
						|
		s.Store, err = store.New(tsLogf, stateFile)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	sys.Set(s.Store)
 | 
						|
 | 
						|
	loginFlags := controlclient.LoginDefault
 | 
						|
	if s.Ephemeral {
 | 
						|
		loginFlags = controlclient.LoginEphemeral
 | 
						|
	}
 | 
						|
	lb, err := ipnlocal.NewLocalBackend(tsLogf, s.logid, sys, loginFlags|controlclient.LocalBackendStartKeyOSNeutral)
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("NewLocalBackend: %v", err)
 | 
						|
	}
 | 
						|
	lb.SetTCPHandlerForFunnelFlow(s.getTCPHandlerForFunnelFlow)
 | 
						|
	lb.SetVarRoot(s.rootPath)
 | 
						|
	s.logf("tsnet starting with hostname %q, varRoot %q", s.hostname, s.rootPath)
 | 
						|
	s.lb = lb
 | 
						|
	if err := ns.Start(lb); err != nil {
 | 
						|
		return fmt.Errorf("failed to start netstack: %w", err)
 | 
						|
	}
 | 
						|
	closePool.addFunc(func() { s.lb.Shutdown() })
 | 
						|
	prefs := ipn.NewPrefs()
 | 
						|
	prefs.Hostname = s.hostname
 | 
						|
	prefs.WantRunning = true
 | 
						|
	prefs.ControlURL = s.ControlURL
 | 
						|
	prefs.RunWebClient = s.RunWebClient
 | 
						|
	prefs.AdvertiseTags = s.AdvertiseTags
 | 
						|
	authKey := s.getAuthKey()
 | 
						|
	// Try to use an OAuth secret to generate an auth key if that functionality
 | 
						|
	// is available.
 | 
						|
	if f, ok := tailscale.HookResolveAuthKey.GetOk(); ok {
 | 
						|
		authKey, err = f(s.shutdownCtx, s.getAuthKey(), prefs.AdvertiseTags)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("resolving auth key: %w", err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	err = lb.Start(ipn.Options{
 | 
						|
		UpdatePrefs: prefs,
 | 
						|
		AuthKey:     authKey,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("starting backend: %w", err)
 | 
						|
	}
 | 
						|
	st := lb.State()
 | 
						|
	if st == ipn.NeedsLogin || envknob.Bool("TSNET_FORCE_LOGIN") {
 | 
						|
		s.logf("LocalBackend state is %v; running StartLoginInteractive...", st)
 | 
						|
		if err := s.lb.StartLoginInteractive(s.shutdownCtx); err != nil {
 | 
						|
			return fmt.Errorf("StartLoginInteractive: %w", err)
 | 
						|
		}
 | 
						|
	} else if authKey != "" {
 | 
						|
		s.logf("Authkey is set; but state is %v. Ignoring authkey. Re-run with TSNET_FORCE_LOGIN=1 to force use of authkey.", st)
 | 
						|
	}
 | 
						|
	go s.printAuthURLLoop()
 | 
						|
 | 
						|
	// Run the localapi handler, to allow fetching LetsEncrypt certs.
 | 
						|
	lah := localapi.NewHandler(localapi.HandlerConfig{
 | 
						|
		Actor:    ipnauth.Self,
 | 
						|
		Backend:  lb,
 | 
						|
		Logf:     tsLogf,
 | 
						|
		LogID:    s.logid,
 | 
						|
		EventBus: sys.Bus.Get(),
 | 
						|
	})
 | 
						|
	lah.PermitWrite = true
 | 
						|
	lah.PermitRead = true
 | 
						|
 | 
						|
	// Create an in-process listener.
 | 
						|
	// nettest.Listen provides a in-memory pipe based implementation for net.Conn.
 | 
						|
	lal := memnet.Listen("local-tailscaled.sock:80")
 | 
						|
	s.localAPIListener = lal
 | 
						|
	s.localClient = &local.Client{Dial: lal.Dial}
 | 
						|
	s.localAPIServer = &http.Server{Handler: lah}
 | 
						|
	s.lb.ConfigureWebClient(s.localClient)
 | 
						|
	go func() {
 | 
						|
		if err := s.localAPIServer.Serve(lal); err != nil && err != http.ErrServerClosed {
 | 
						|
			s.logf("localapi serve error: %v", err)
 | 
						|
		}
 | 
						|
	}()
 | 
						|
	closePool.add(s.localAPIListener)
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Server) startLogger(closePool *closeOnErrorPool, health *health.Tracker, tsLogf logger.Logf) error {
 | 
						|
	if testenv.InTest() {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	cfgPath := filepath.Join(s.rootPath, "tailscaled.log.conf")
 | 
						|
	lpc, err := logpolicy.ConfigFromFile(cfgPath)
 | 
						|
	switch {
 | 
						|
	case os.IsNotExist(err):
 | 
						|
		lpc = logpolicy.NewConfig(logtail.CollectionNode)
 | 
						|
		if err := lpc.Save(cfgPath); err != nil {
 | 
						|
			return fmt.Errorf("logpolicy.Config.Save for %v: %w", cfgPath, err)
 | 
						|
		}
 | 
						|
	case err != nil:
 | 
						|
		return fmt.Errorf("logpolicy.LoadConfig for %v: %w", cfgPath, err)
 | 
						|
	}
 | 
						|
	if err := lpc.Validate(logtail.CollectionNode); err != nil {
 | 
						|
		return fmt.Errorf("logpolicy.Config.Validate for %v: %w", cfgPath, err)
 | 
						|
	}
 | 
						|
	s.logid = lpc.PublicID
 | 
						|
 | 
						|
	s.logbuffer, err = filch.New(filepath.Join(s.rootPath, "tailscaled"), filch.Options{ReplaceStderr: false})
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("error creating filch: %w", err)
 | 
						|
	}
 | 
						|
	closePool.add(s.logbuffer)
 | 
						|
	c := logtail.Config{
 | 
						|
		Collection:   lpc.Collection,
 | 
						|
		PrivateID:    lpc.PrivateID,
 | 
						|
		Stderr:       io.Discard, // log everything to Buffer
 | 
						|
		Buffer:       s.logbuffer,
 | 
						|
		CompressLogs: true,
 | 
						|
		Bus:          s.sys.Bus.Get(),
 | 
						|
		HTTPC:        &http.Client{Transport: logpolicy.NewLogtailTransport(logtail.DefaultHost, s.netMon, health, tsLogf)},
 | 
						|
		MetricsDelta: clientmetric.EncodeLogTailMetricsDelta,
 | 
						|
	}
 | 
						|
	s.logtail = logtail.NewLogger(c, tsLogf)
 | 
						|
	closePool.addFunc(func() { s.logtail.Shutdown(context.Background()) })
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
type closeOnErrorPool []func()
 | 
						|
 | 
						|
func (p *closeOnErrorPool) add(c io.Closer)   { *p = append(*p, func() { c.Close() }) }
 | 
						|
func (p *closeOnErrorPool) addFunc(fn func()) { *p = append(*p, fn) }
 | 
						|
func (p closeOnErrorPool) closeAllIfError(errp *error) {
 | 
						|
	if *errp != nil {
 | 
						|
		for _, closeFn := range p {
 | 
						|
			closeFn()
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (s *Server) logf(format string, a ...any) {
 | 
						|
	if s.logtail != nil {
 | 
						|
		s.logtail.Logf(format, a...)
 | 
						|
	}
 | 
						|
	if s.UserLogf != nil {
 | 
						|
		s.UserLogf(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 && st != ipn.NoState {
 | 
						|
			s.logf("AuthLoop: state is %v; done", 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
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// networkForFamily returns one of "tcp4", "tcp6", "udp4", or "udp6".
 | 
						|
//
 | 
						|
// netBase is "tcp" or "udp" (without any '4' or '6' suffix).
 | 
						|
func networkForFamily(netBase string, is6 bool) string {
 | 
						|
	switch netBase {
 | 
						|
	case "tcp":
 | 
						|
		if is6 {
 | 
						|
			return "tcp6"
 | 
						|
		}
 | 
						|
		return "tcp4"
 | 
						|
	case "udp":
 | 
						|
		if is6 {
 | 
						|
			return "udp6"
 | 
						|
		}
 | 
						|
		return "udp4"
 | 
						|
	}
 | 
						|
	panic("unexpected")
 | 
						|
}
 | 
						|
 | 
						|
// listenerForDstAddr returns a listener for the provided network and
 | 
						|
// destination IP/port. It matches from most specific to least specific.
 | 
						|
// For example:
 | 
						|
//
 | 
						|
//   - ("tcp4", IP, port)
 | 
						|
//   - ("tcp", IP, port)
 | 
						|
//   - ("tcp4", "", port)
 | 
						|
//   - ("tcp", "", port)
 | 
						|
//
 | 
						|
// The netBase is "tcp" or "udp" (without any '4' or '6' suffix).
 | 
						|
//
 | 
						|
// Listeners which do not specify an IP address will match for traffic
 | 
						|
// for the local node (that is, a destination address of the IPv4 or
 | 
						|
// IPv6 address of this node) only. To listen for traffic on other addresses
 | 
						|
// such as those routed inbound via subnet routes, explicitly specify
 | 
						|
// the listening address or use RegisterFallbackTCPHandler.
 | 
						|
func (s *Server) listenerForDstAddr(netBase string, dst netip.AddrPort, funnel bool) (_ *listener, ok bool) {
 | 
						|
	s.mu.Lock()
 | 
						|
	defer s.mu.Unlock()
 | 
						|
 | 
						|
	// Search for a listener with the specified IP
 | 
						|
	for _, net := range [2]string{
 | 
						|
		networkForFamily(netBase, dst.Addr().Is6()),
 | 
						|
		netBase,
 | 
						|
	} {
 | 
						|
		if ln, ok := s.listeners[listenKey{net, dst.Addr(), dst.Port(), funnel}]; ok {
 | 
						|
			return ln, true
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Search for a listener without an IP if the destination was
 | 
						|
	// one of the native IPs of the node.
 | 
						|
	if ip4, ip6 := s.TailscaleIPs(); dst.Addr() == ip4 || dst.Addr() == ip6 {
 | 
						|
		for _, net := range [2]string{
 | 
						|
			networkForFamily(netBase, dst.Addr().Is6()),
 | 
						|
			netBase,
 | 
						|
		} {
 | 
						|
			if ln, ok := s.listeners[listenKey{net, netip.Addr{}, dst.Port(), funnel}]; ok {
 | 
						|
				return ln, true
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil, false
 | 
						|
}
 | 
						|
 | 
						|
func (s *Server) getTCPHandlerForFunnelFlow(src netip.AddrPort, dstPort uint16) (handler func(net.Conn)) {
 | 
						|
	ipv4, ipv6 := s.TailscaleIPs()
 | 
						|
	var dst netip.AddrPort
 | 
						|
	if src.Addr().Is4() {
 | 
						|
		if !ipv4.IsValid() {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		dst = netip.AddrPortFrom(ipv4, dstPort)
 | 
						|
	} else {
 | 
						|
		if !ipv6.IsValid() {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		dst = netip.AddrPortFrom(ipv6, dstPort)
 | 
						|
	}
 | 
						|
	ln, ok := s.listenerForDstAddr("tcp", dst, true)
 | 
						|
	if !ok {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	return ln.handle
 | 
						|
}
 | 
						|
 | 
						|
func (s *Server) getTCPHandlerForFlow(src, dst netip.AddrPort) (handler func(net.Conn), intercept bool) {
 | 
						|
	ln, ok := s.listenerForDstAddr("tcp", dst, false)
 | 
						|
	if !ok {
 | 
						|
		s.mu.Lock()
 | 
						|
		defer s.mu.Unlock()
 | 
						|
		for _, handler := range s.fallbackTCPHandlers {
 | 
						|
			connHandler, intercept := handler(src, dst)
 | 
						|
			if intercept {
 | 
						|
				return connHandler, intercept
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return nil, true // don't handle, don't forward to localhost
 | 
						|
	}
 | 
						|
	return ln.handle, true
 | 
						|
}
 | 
						|
 | 
						|
func (s *Server) getUDPHandlerForFlow(src, dst netip.AddrPort) (handler func(nettype.ConnPacketConn), intercept bool) {
 | 
						|
	ln, ok := s.listenerForDstAddr("udp", dst, false)
 | 
						|
	if !ok {
 | 
						|
		return nil, true // don't handle, don't forward to localhost
 | 
						|
	}
 | 
						|
	return func(c nettype.ConnPacketConn) { ln.handle(c) }, true
 | 
						|
}
 | 
						|
 | 
						|
// Listen announces only on the Tailscale network.
 | 
						|
// It will start the server if it has not been started yet.
 | 
						|
//
 | 
						|
// Listeners which do not specify an IP address will match for traffic
 | 
						|
// for the local node (that is, a destination address of the IPv4 or
 | 
						|
// IPv6 address of this node) only. To listen for traffic on other addresses
 | 
						|
// such as those routed inbound via subnet routes, explicitly specify
 | 
						|
// the listening address or use RegisterFallbackTCPHandler.
 | 
						|
func (s *Server) Listen(network, addr string) (net.Listener, error) {
 | 
						|
	return s.listen(network, addr, listenOnTailnet)
 | 
						|
}
 | 
						|
 | 
						|
// ListenPacket announces on the Tailscale network.
 | 
						|
//
 | 
						|
// The network must be "udp", "udp4" or "udp6". The addr must be of the form
 | 
						|
// "ip:port" (or "[ip]:port") where ip is a valid IPv4 or IPv6 address
 | 
						|
// corresponding to "udp4" or "udp6" respectively. IP must be specified.
 | 
						|
//
 | 
						|
// If s has not been started yet, it will be started.
 | 
						|
func (s *Server) ListenPacket(network, addr string) (net.PacketConn, error) {
 | 
						|
	ap, err := resolveListenAddr(network, addr)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if !ap.Addr().IsValid() {
 | 
						|
		return nil, fmt.Errorf("tsnet.ListenPacket(%q, %q): address must be a valid IP", network, addr)
 | 
						|
	}
 | 
						|
	if network == "udp" {
 | 
						|
		if ap.Addr().Is4() {
 | 
						|
			network = "udp4"
 | 
						|
		} else {
 | 
						|
			network = "udp6"
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if err := s.Start(); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return s.netstack.ListenPacket(network, ap.String())
 | 
						|
}
 | 
						|
 | 
						|
// ListenTLS announces only on the Tailscale network.
 | 
						|
// It returns a TLS listener wrapping the tsnet listener.
 | 
						|
// It will start the server if it has not been started yet.
 | 
						|
func (s *Server) ListenTLS(network, addr string) (net.Listener, error) {
 | 
						|
	if network != "tcp" {
 | 
						|
		return nil, fmt.Errorf("ListenTLS(%q, %q): only tcp is supported", network, addr)
 | 
						|
	}
 | 
						|
	ctx := context.Background()
 | 
						|
	st, err := s.Up(ctx)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if !st.CurrentTailnet.MagicDNSEnabled {
 | 
						|
		return nil, errors.New("tsnet: you must enable MagicDNS in the DNS page of the admin panel to proceed. See https://tailscale.com/s/https")
 | 
						|
	}
 | 
						|
	if len(st.CertDomains) == 0 {
 | 
						|
		return nil, errors.New("tsnet: you must enable HTTPS in the admin panel to proceed. See https://tailscale.com/s/https")
 | 
						|
	}
 | 
						|
 | 
						|
	ln, err := s.listen(network, addr, listenOnTailnet)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return tls.NewListener(ln, &tls.Config{
 | 
						|
		GetCertificate: s.getCert,
 | 
						|
	}), nil
 | 
						|
}
 | 
						|
 | 
						|
// RegisterFallbackTCPHandler registers a callback which will be called
 | 
						|
// to handle a TCP flow to this tsnet node, for which no listeners will handle.
 | 
						|
//
 | 
						|
// If multiple fallback handlers are registered, they will be called in an
 | 
						|
// undefined order. See FallbackTCPHandler for details on handling a flow.
 | 
						|
//
 | 
						|
// The returned function can be used to deregister this callback.
 | 
						|
func (s *Server) RegisterFallbackTCPHandler(cb FallbackTCPHandler) func() {
 | 
						|
	s.mu.Lock()
 | 
						|
	defer s.mu.Unlock()
 | 
						|
	hnd := s.fallbackTCPHandlers.Add(cb)
 | 
						|
	return func() {
 | 
						|
		s.mu.Lock()
 | 
						|
		defer s.mu.Unlock()
 | 
						|
		delete(s.fallbackTCPHandlers, hnd)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// getCert is the GetCertificate function used by ListenTLS.
 | 
						|
//
 | 
						|
// It calls GetCertificate on the localClient, passing in the ClientHelloInfo.
 | 
						|
// For testing, if s.getCertForTesting is set, it will call that instead.
 | 
						|
func (s *Server) getCert(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
 | 
						|
	if s.getCertForTesting != nil {
 | 
						|
		return s.getCertForTesting(hi)
 | 
						|
	}
 | 
						|
	lc, err := s.LocalClient()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return lc.GetCertificate(hi)
 | 
						|
}
 | 
						|
 | 
						|
// FunnelOption is an option passed to ListenFunnel to configure the listener.
 | 
						|
type FunnelOption interface {
 | 
						|
	funnelOption()
 | 
						|
}
 | 
						|
 | 
						|
type funnelOnly struct{}
 | 
						|
 | 
						|
func (funnelOnly) funnelOption() {}
 | 
						|
 | 
						|
// FunnelOnly configures the listener to only respond to connections from Tailscale Funnel.
 | 
						|
// The local tailnet will not be able to connect to the listener.
 | 
						|
func FunnelOnly() FunnelOption { return funnelOnly{} }
 | 
						|
 | 
						|
type funnelTLSConfig struct{ conf *tls.Config }
 | 
						|
 | 
						|
func (f funnelTLSConfig) funnelOption() {}
 | 
						|
 | 
						|
// FunnelTLSConfig configures the TLS configuration for [Server.ListenFunnel]
 | 
						|
//
 | 
						|
// This is rarely needed but can permit requiring client certificates, specific
 | 
						|
// ciphers suites, etc.
 | 
						|
//
 | 
						|
// The provided conf should at least be able to get a certificate, setting
 | 
						|
// GetCertificate, Certificates or GetConfigForClient appropriately.
 | 
						|
// The most common configuration is to set GetCertificate to
 | 
						|
// Server.LocalClient's GetCertificate method.
 | 
						|
//
 | 
						|
// Unless [FunnelOnly] is also used, the configuration is also used for
 | 
						|
// in-tailnet connections that don't arrive over Funnel.
 | 
						|
func FunnelTLSConfig(conf *tls.Config) FunnelOption {
 | 
						|
	return funnelTLSConfig{conf: conf}
 | 
						|
}
 | 
						|
 | 
						|
// ListenFunnel announces on the public internet using Tailscale Funnel.
 | 
						|
//
 | 
						|
// It also by default listens on your local tailnet, so connections can
 | 
						|
// come from either inside or outside your network. To restrict connections
 | 
						|
// to be just from the internet, use the FunnelOnly option.
 | 
						|
//
 | 
						|
// Currently (2023-03-10), Funnel only supports TCP on ports 443, 8443, and 10000.
 | 
						|
// The supported host name is limited to that configured for the tsnet.Server.
 | 
						|
// As such, the standard way to create funnel is:
 | 
						|
//
 | 
						|
//	s.ListenFunnel("tcp", ":443")
 | 
						|
//
 | 
						|
// and the only other supported addrs currently are ":8443" and ":10000".
 | 
						|
//
 | 
						|
// It will start the server if it has not been started yet.
 | 
						|
func (s *Server) ListenFunnel(network, addr string, opts ...FunnelOption) (net.Listener, error) {
 | 
						|
	if network != "tcp" {
 | 
						|
		return nil, fmt.Errorf("ListenFunnel(%q, %q): only tcp is supported", network, addr)
 | 
						|
	}
 | 
						|
	host, portStr, err := net.SplitHostPort(addr)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if host != "" {
 | 
						|
		return nil, fmt.Errorf("ListenFunnel(%q, %q): host must be empty", network, addr)
 | 
						|
	}
 | 
						|
	port, err := strconv.ParseUint(portStr, 10, 16)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Process, validate opts.
 | 
						|
	lnOn := listenOnBoth
 | 
						|
	var tlsConfig *tls.Config
 | 
						|
	for _, opt := range opts {
 | 
						|
		switch v := opt.(type) {
 | 
						|
		case funnelTLSConfig:
 | 
						|
			if v.conf == nil {
 | 
						|
				return nil, errors.New("invalid nil FunnelTLSConfig")
 | 
						|
			}
 | 
						|
			tlsConfig = v.conf
 | 
						|
		case funnelOnly:
 | 
						|
			lnOn = listenOnFunnel
 | 
						|
		default:
 | 
						|
			return nil, fmt.Errorf("unknown opts FunnelOption type %T", v)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if tlsConfig == nil {
 | 
						|
		tlsConfig = &tls.Config{GetCertificate: s.getCert}
 | 
						|
	}
 | 
						|
 | 
						|
	ctx := context.Background()
 | 
						|
	st, err := s.Up(ctx)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	// TODO(sonia,tailscale/corp#10577): We may want to use the interactive enable
 | 
						|
	// flow here instead of CheckFunnelAccess to allow the user to turn on Funnel
 | 
						|
	// if not already on. Specifically when running from a terminal.
 | 
						|
	// See cli.serveEnv.verifyFunnelEnabled.
 | 
						|
	if err := ipn.CheckFunnelAccess(uint16(port), st.Self); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	lc := s.localClient
 | 
						|
 | 
						|
	// May not have funnel enabled. Enable it.
 | 
						|
	srvConfig, err := lc.GetServeConfig(ctx)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if srvConfig == nil {
 | 
						|
		srvConfig = &ipn.ServeConfig{}
 | 
						|
	}
 | 
						|
	if len(st.CertDomains) == 0 {
 | 
						|
		return nil, errors.New("Funnel not available; HTTPS must be enabled. See https://tailscale.com/s/https")
 | 
						|
	}
 | 
						|
	domain := st.CertDomains[0]
 | 
						|
	hp := ipn.HostPort(domain + ":" + portStr)
 | 
						|
	if !srvConfig.AllowFunnel[hp] {
 | 
						|
		mak.Set(&srvConfig.AllowFunnel, hp, true)
 | 
						|
		srvConfig.AllowFunnel[hp] = true
 | 
						|
		if err := lc.SetServeConfig(ctx, srvConfig); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Start a funnel listener.
 | 
						|
	ln, err := s.listen(network, addr, lnOn)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return tls.NewListener(ln, tlsConfig), nil
 | 
						|
}
 | 
						|
 | 
						|
type listenOn string
 | 
						|
 | 
						|
const (
 | 
						|
	listenOnTailnet = listenOn("listen-on-tailnet")
 | 
						|
	listenOnFunnel  = listenOn("listen-on-funnel")
 | 
						|
	listenOnBoth    = listenOn("listen-on-both")
 | 
						|
)
 | 
						|
 | 
						|
// resolveListenAddr resolves a network and address into a netip.AddrPort. The
 | 
						|
// returned netip.AddrPort.Addr will be the zero value if the address is empty.
 | 
						|
// The port must be a valid port number. The caller is responsible for checking
 | 
						|
// the network and address are valid.
 | 
						|
//
 | 
						|
// It resolves well-known port names and validates the address is a valid IP
 | 
						|
// literal for the network.
 | 
						|
func resolveListenAddr(network, addr string) (netip.AddrPort, error) {
 | 
						|
	var zero netip.AddrPort
 | 
						|
	host, portStr, err := net.SplitHostPort(addr)
 | 
						|
	if err != nil {
 | 
						|
		return zero, fmt.Errorf("tsnet: %w", err)
 | 
						|
	}
 | 
						|
	port, err := net.LookupPort(network, portStr)
 | 
						|
	if err != nil || port < 0 || port > math.MaxUint16 {
 | 
						|
		// LookupPort returns an error on out of range values so the bounds
 | 
						|
		// checks on port should be unnecessary, but harmless. If they do
 | 
						|
		// match, worst case this error message says "invalid port: <nil>".
 | 
						|
		return zero, fmt.Errorf("invalid port: %w", err)
 | 
						|
	}
 | 
						|
	if host == "" {
 | 
						|
		return netip.AddrPortFrom(netip.Addr{}, uint16(port)), nil
 | 
						|
	}
 | 
						|
 | 
						|
	bindHostOrZero, err := netip.ParseAddr(host)
 | 
						|
	if err != nil {
 | 
						|
		return zero, fmt.Errorf("invalid Listen addr %q; host part must be empty or IP literal", host)
 | 
						|
	}
 | 
						|
	if strings.HasSuffix(network, "4") && !bindHostOrZero.Is4() {
 | 
						|
		return zero, fmt.Errorf("invalid non-IPv4 addr %v for network %q", host, network)
 | 
						|
	}
 | 
						|
	if strings.HasSuffix(network, "6") && !bindHostOrZero.Is6() {
 | 
						|
		return zero, fmt.Errorf("invalid non-IPv6 addr %v for network %q", host, network)
 | 
						|
	}
 | 
						|
	return netip.AddrPortFrom(bindHostOrZero, uint16(port)), nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Server) listen(network, addr string, lnOn listenOn) (net.Listener, error) {
 | 
						|
	switch network {
 | 
						|
	case "", "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6":
 | 
						|
	default:
 | 
						|
		return nil, errors.New("unsupported network type")
 | 
						|
	}
 | 
						|
	host, err := resolveListenAddr(network, addr)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if err := s.Start(); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	var keys []listenKey
 | 
						|
	switch lnOn {
 | 
						|
	case listenOnTailnet:
 | 
						|
		keys = append(keys, listenKey{network, host.Addr(), host.Port(), false})
 | 
						|
	case listenOnFunnel:
 | 
						|
		keys = append(keys, listenKey{network, host.Addr(), host.Port(), true})
 | 
						|
	case listenOnBoth:
 | 
						|
		keys = append(keys, listenKey{network, host.Addr(), host.Port(), false})
 | 
						|
		keys = append(keys, listenKey{network, host.Addr(), host.Port(), true})
 | 
						|
	}
 | 
						|
 | 
						|
	ln := &listener{
 | 
						|
		s:    s,
 | 
						|
		keys: keys,
 | 
						|
		addr: addr,
 | 
						|
 | 
						|
		closedc: make(chan struct{}),
 | 
						|
		conn:    make(chan net.Conn),
 | 
						|
	}
 | 
						|
	s.mu.Lock()
 | 
						|
	for _, key := range keys {
 | 
						|
		if _, ok := s.listeners[key]; ok {
 | 
						|
			s.mu.Unlock()
 | 
						|
			return nil, fmt.Errorf("tsnet: listener already open for %s, %s", network, addr)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if s.listeners == nil {
 | 
						|
		s.listeners = make(map[listenKey]*listener)
 | 
						|
	}
 | 
						|
	for _, key := range keys {
 | 
						|
		s.listeners[key] = ln
 | 
						|
	}
 | 
						|
	s.mu.Unlock()
 | 
						|
	return ln, nil
 | 
						|
}
 | 
						|
 | 
						|
// GetRootPath returns the root path of the tsnet server.
 | 
						|
// This is where the state file and other data is stored.
 | 
						|
func (s *Server) GetRootPath() string {
 | 
						|
	return s.rootPath
 | 
						|
}
 | 
						|
 | 
						|
// CapturePcap can be called by the application code compiled with tsnet to save a pcap
 | 
						|
// of packets which the netstack within tsnet sees. This is expected to be useful during
 | 
						|
// debugging, probably not useful for production.
 | 
						|
//
 | 
						|
// Packets will be written to the pcap until the process exits. The pcap needs a Lua dissector
 | 
						|
// to be installed in WireShark in order to decode properly: wgengine/capture/ts-dissector.lua
 | 
						|
// in this repository.
 | 
						|
// https://tailscale.com/kb/1023/troubleshooting/#can-i-examine-network-traffic-inside-the-encrypted-tunnel
 | 
						|
func (s *Server) CapturePcap(ctx context.Context, pcapFile string) error {
 | 
						|
	stream, err := s.localClient.StreamDebugCapture(ctx)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	f, err := os.OpenFile(pcapFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
 | 
						|
	if err != nil {
 | 
						|
		stream.Close()
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	go func(stream io.ReadCloser, f *os.File) {
 | 
						|
		defer stream.Close()
 | 
						|
		defer f.Close()
 | 
						|
		_, _ = io.Copy(f, stream)
 | 
						|
	}(stream, f)
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Sys returns a handle to the Tailscale subsystems of this node.
 | 
						|
//
 | 
						|
// This is not a stable API, nor are the APIs of the returned subsystems.
 | 
						|
func (s *Server) Sys() *tsd.System {
 | 
						|
	return s.sys
 | 
						|
}
 | 
						|
 | 
						|
type listenKey struct {
 | 
						|
	network string
 | 
						|
	host    netip.Addr // or zero value for unspecified
 | 
						|
	port    uint16
 | 
						|
	funnel  bool
 | 
						|
}
 | 
						|
 | 
						|
type listener struct {
 | 
						|
	s       *Server
 | 
						|
	keys    []listenKey
 | 
						|
	addr    string
 | 
						|
	conn    chan net.Conn // unbuffered, never closed
 | 
						|
	closedc chan struct{} // closed on [listener.Close]
 | 
						|
	closed  bool          // guarded by s.mu
 | 
						|
}
 | 
						|
 | 
						|
func (ln *listener) Accept() (net.Conn, error) {
 | 
						|
	select {
 | 
						|
	case c := <-ln.conn:
 | 
						|
		return c, nil
 | 
						|
	case <-ln.closedc:
 | 
						|
		return nil, fmt.Errorf("tsnet: %w", net.ErrClosed)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (ln *listener) Addr() net.Addr { return addr{ln} }
 | 
						|
 | 
						|
func (ln *listener) Close() error {
 | 
						|
	ln.s.mu.Lock()
 | 
						|
	defer ln.s.mu.Unlock()
 | 
						|
	return ln.closeLocked()
 | 
						|
}
 | 
						|
 | 
						|
// closeLocked closes the listener.
 | 
						|
// It must be called with ln.s.mu held.
 | 
						|
func (ln *listener) closeLocked() error {
 | 
						|
	if ln.closed {
 | 
						|
		return fmt.Errorf("tsnet: %w", net.ErrClosed)
 | 
						|
	}
 | 
						|
	for _, key := range ln.keys {
 | 
						|
		if v, ok := ln.s.listeners[key]; ok && v == ln {
 | 
						|
			delete(ln.s.listeners, key)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	close(ln.closedc)
 | 
						|
	ln.closed = true
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (ln *listener) handle(c net.Conn) {
 | 
						|
	select {
 | 
						|
	case ln.conn <- c:
 | 
						|
		return
 | 
						|
	case <-ln.closedc:
 | 
						|
	case <-ln.s.shutdownCtx.Done():
 | 
						|
	case <-time.After(time.Second):
 | 
						|
		// TODO(bradfitz): this isn't ideal. Think about how
 | 
						|
		// we how we want to do pushback.
 | 
						|
	}
 | 
						|
	c.Close()
 | 
						|
}
 | 
						|
 | 
						|
// Server returns the tsnet Server associated with the listener.
 | 
						|
func (ln *listener) Server() *Server { return ln.s }
 | 
						|
 | 
						|
type addr struct{ ln *listener }
 | 
						|
 | 
						|
func (a addr) Network() string { return a.ln.keys[0].network }
 | 
						|
func (a addr) String() string  { return a.ln.addr }
 |