|
|
|
|
@ -7,6 +7,7 @@ package ipnlocal |
|
|
|
|
import ( |
|
|
|
|
"context" |
|
|
|
|
"crypto/tls" |
|
|
|
|
"encoding/json" |
|
|
|
|
"errors" |
|
|
|
|
"fmt" |
|
|
|
|
"io" |
|
|
|
|
@ -23,9 +24,14 @@ import ( |
|
|
|
|
"sync" |
|
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
"golang.org/x/exp/slices" |
|
|
|
|
"tailscale.com/ipn" |
|
|
|
|
"tailscale.com/logtail/backoff" |
|
|
|
|
"tailscale.com/net/netutil" |
|
|
|
|
"tailscale.com/syncs" |
|
|
|
|
"tailscale.com/tailcfg" |
|
|
|
|
"tailscale.com/types/logger" |
|
|
|
|
"tailscale.com/util/mak" |
|
|
|
|
"tailscale.com/util/strs" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
@ -37,6 +43,173 @@ type serveHTTPContext struct { |
|
|
|
|
DestPort uint16 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// serveListener is the state of host-level net.Listen for a specific (Tailscale IP, serve port)
|
|
|
|
|
// combination. If there are two TailscaleIPs (v4 and v6) and three ports being served,
|
|
|
|
|
// then there will be six of these active and looping in their Run method.
|
|
|
|
|
//
|
|
|
|
|
// This is not used in userspace-networking mode.
|
|
|
|
|
//
|
|
|
|
|
// Most serve traffic is intercepted by netstack. This exists purely for connections
|
|
|
|
|
// from the machine itself, as that goes via the kernel, so we need to be in the
|
|
|
|
|
// kernel's listening/routing tables.
|
|
|
|
|
type serveListener struct { |
|
|
|
|
b *LocalBackend |
|
|
|
|
ap netip.AddrPort |
|
|
|
|
ctx context.Context // valid while listener is desired
|
|
|
|
|
cancel context.CancelFunc // for ctx, to close listener
|
|
|
|
|
logf logger.Logf |
|
|
|
|
bo *backoff.Backoff // for retrying failed Listen calls
|
|
|
|
|
|
|
|
|
|
closeListener syncs.AtomicValue[func() error] // Listener's Close method, if any
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (b *LocalBackend) newServeListener(ctx context.Context, ap netip.AddrPort, logf logger.Logf) *serveListener { |
|
|
|
|
ctx, cancel := context.WithCancel(ctx) |
|
|
|
|
return &serveListener{ |
|
|
|
|
b: b, |
|
|
|
|
ap: ap, |
|
|
|
|
ctx: ctx, |
|
|
|
|
cancel: cancel, |
|
|
|
|
logf: logf, |
|
|
|
|
|
|
|
|
|
bo: backoff.NewBackoff("serve-listener", logf, 30*time.Second), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Close cancels the context and closes the listener, if any.
|
|
|
|
|
func (s *serveListener) Close() error { |
|
|
|
|
s.cancel() |
|
|
|
|
if close, ok := s.closeListener.LoadOk(); ok { |
|
|
|
|
s.closeListener.Store(nil) |
|
|
|
|
close() |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Run starts a net.Listen for the serveListener's address and port.
|
|
|
|
|
// If unable to listen, it retries with exponential backoff.
|
|
|
|
|
// Listen is retried until the context is canceled.
|
|
|
|
|
func (s *serveListener) Run() { |
|
|
|
|
for { |
|
|
|
|
ln, err := net.Listen("tcp", s.ap.String()) |
|
|
|
|
if err != nil { |
|
|
|
|
s.logf("serve failed to listen on %v, backing off: %v", s.ap, err) |
|
|
|
|
s.bo.BackOff(s.ctx, err) |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
s.closeListener.Store(ln.Close) |
|
|
|
|
|
|
|
|
|
s.logf("serve listening on %v", s.ap) |
|
|
|
|
err = s.handleServeListenersAccept(ln) |
|
|
|
|
if s.ctx.Err() != nil { |
|
|
|
|
// context canceled, we're done
|
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
if err != nil { |
|
|
|
|
s.logf("serve listener accept error, retrying: %v", err) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// handleServeListenersAccept accepts connections for the Listener.
|
|
|
|
|
// Calls incoming handler in a new goroutine for each accepted connection.
|
|
|
|
|
func (s *serveListener) handleServeListenersAccept(ln net.Listener) error { |
|
|
|
|
for { |
|
|
|
|
conn, err := ln.Accept() |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
srcAddr := conn.RemoteAddr().(*net.TCPAddr).AddrPort() |
|
|
|
|
getConn := func() (net.Conn, bool) { return conn, true } |
|
|
|
|
sendRST := func() { |
|
|
|
|
s.b.logf("serve RST for %v", srcAddr) |
|
|
|
|
conn.Close() |
|
|
|
|
} |
|
|
|
|
go s.b.HandleInterceptedTCPConn(s.ap.Port(), srcAddr, getConn, sendRST) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// updateServeTCPPortNetMapAddrListenersLocked starts a net.Listen for configured
|
|
|
|
|
// Serve ports on all the node's addresses.
|
|
|
|
|
// Existing Listeners are closed if port no longer in incoming ports list.
|
|
|
|
|
//
|
|
|
|
|
// b.mu must be held.
|
|
|
|
|
func (b *LocalBackend) updateServeTCPPortNetMapAddrListenersLocked(ports []uint16) { |
|
|
|
|
// close existing listeners where port
|
|
|
|
|
// is no longer in incoming ports list
|
|
|
|
|
for ap, sl := range b.serveListeners { |
|
|
|
|
if !slices.Contains(ports, ap.Port()) { |
|
|
|
|
b.logf("closing listener %v", ap) |
|
|
|
|
sl.Close() |
|
|
|
|
delete(b.serveListeners, ap) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
nm := b.netMap |
|
|
|
|
if nm == nil { |
|
|
|
|
b.logf("netMap is nil") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
if nm.SelfNode == nil { |
|
|
|
|
b.logf("netMap SelfNode is nil") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for _, a := range nm.Addresses { |
|
|
|
|
for _, p := range ports { |
|
|
|
|
addrPort := netip.AddrPortFrom(a.Addr(), p) |
|
|
|
|
if _, ok := b.serveListeners[addrPort]; ok { |
|
|
|
|
continue // already listening
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
sl := b.newServeListener(context.Background(), addrPort, b.logf) |
|
|
|
|
mak.Set(&b.serveListeners, addrPort, sl) |
|
|
|
|
|
|
|
|
|
go sl.Run() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// SetServeConfig establishes or replaces the current serve config.
|
|
|
|
|
func (b *LocalBackend) SetServeConfig(config *ipn.ServeConfig) error { |
|
|
|
|
b.mu.Lock() |
|
|
|
|
defer b.mu.Unlock() |
|
|
|
|
|
|
|
|
|
nm := b.netMap |
|
|
|
|
if nm == nil { |
|
|
|
|
return errors.New("netMap is nil") |
|
|
|
|
} |
|
|
|
|
if nm.SelfNode == nil { |
|
|
|
|
return errors.New("netMap SelfNode is nil") |
|
|
|
|
} |
|
|
|
|
profileID := b.pm.CurrentProfile().ID |
|
|
|
|
confKey := ipn.ServeConfigKey(profileID) |
|
|
|
|
|
|
|
|
|
var bs []byte |
|
|
|
|
if config != nil { |
|
|
|
|
j, err := json.Marshal(config) |
|
|
|
|
if err != nil { |
|
|
|
|
return fmt.Errorf("encoding serve config: %w", err) |
|
|
|
|
} |
|
|
|
|
bs = j |
|
|
|
|
} |
|
|
|
|
if err := b.store.WriteState(confKey, bs); err != nil { |
|
|
|
|
return fmt.Errorf("writing ServeConfig to StateStore: %w", err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
b.setTCPPortsInterceptedFromNetmapAndPrefsLocked(b.pm.CurrentPrefs()) |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ServeConfig provides a view of the current serve mappings.
|
|
|
|
|
// If serving is not configured, the returned view is not Valid.
|
|
|
|
|
func (b *LocalBackend) ServeConfig() ipn.ServeConfigView { |
|
|
|
|
b.mu.Lock() |
|
|
|
|
defer b.mu.Unlock() |
|
|
|
|
return b.serveConfig |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (b *LocalBackend) HandleIngressTCPConn(ingressPeer *tailcfg.Node, target ipn.HostPort, srcAddr netip.AddrPort, getConn func() (net.Conn, bool), sendRST func()) { |
|
|
|
|
b.mu.Lock() |
|
|
|
|
sc := b.serveConfig |
|
|
|
|
|