|
|
|
@ -36,6 +36,7 @@ import (
|
|
|
|
"tailscale.com/types/key"
|
|
|
|
"tailscale.com/types/key"
|
|
|
|
"tailscale.com/types/logger"
|
|
|
|
"tailscale.com/types/logger"
|
|
|
|
"tailscale.com/types/nettype"
|
|
|
|
"tailscale.com/types/nettype"
|
|
|
|
|
|
|
|
"tailscale.com/types/views"
|
|
|
|
"tailscale.com/util/eventbus"
|
|
|
|
"tailscale.com/util/eventbus"
|
|
|
|
"tailscale.com/util/set"
|
|
|
|
"tailscale.com/util/set"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
@ -72,15 +73,16 @@ type Server struct {
|
|
|
|
closeCh chan struct{}
|
|
|
|
closeCh chan struct{}
|
|
|
|
netChecker *netcheck.Client
|
|
|
|
netChecker *netcheck.Client
|
|
|
|
|
|
|
|
|
|
|
|
mu sync.Mutex // guards the following fields
|
|
|
|
mu sync.Mutex // guards the following fields
|
|
|
|
derpMap *tailcfg.DERPMap
|
|
|
|
derpMap *tailcfg.DERPMap
|
|
|
|
addrDiscoveryOnce bool // addrDiscovery completed once (successfully or unsuccessfully)
|
|
|
|
onlyStaticAddrPorts bool // no dynamic addr port discovery when set
|
|
|
|
addrPorts []netip.AddrPort // the ip:port pairs returned as candidate endpoints
|
|
|
|
staticAddrPorts views.Slice[netip.AddrPort] // static ip:port pairs set with [Server.SetStaticAddrPorts]
|
|
|
|
closed bool
|
|
|
|
dynamicAddrPorts []netip.AddrPort // dynamically discovered ip:port pairs
|
|
|
|
lamportID uint64
|
|
|
|
closed bool
|
|
|
|
nextVNI uint32
|
|
|
|
lamportID uint64
|
|
|
|
byVNI map[uint32]*serverEndpoint
|
|
|
|
nextVNI uint32
|
|
|
|
byDisco map[key.SortedPairOfDiscoPublic]*serverEndpoint
|
|
|
|
byVNI map[uint32]*serverEndpoint
|
|
|
|
|
|
|
|
byDisco map[key.SortedPairOfDiscoPublic]*serverEndpoint
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
const (
|
|
|
|
@ -278,15 +280,17 @@ func (e *serverEndpoint) isBound() bool {
|
|
|
|
|
|
|
|
|
|
|
|
// NewServer constructs a [Server] listening on port. If port is zero, then
|
|
|
|
// NewServer constructs a [Server] listening on port. If port is zero, then
|
|
|
|
// port selection is left up to the host networking stack. If
|
|
|
|
// port selection is left up to the host networking stack. If
|
|
|
|
// len(overrideAddrs) > 0 these will be used in place of dynamic discovery,
|
|
|
|
// onlyStaticAddrPorts is true, then dynamic addr:port discovery will be
|
|
|
|
// which is useful to override in tests.
|
|
|
|
// disabled, and only addr:port's set via [Server.SetStaticAddrPorts] will be
|
|
|
|
func NewServer(logf logger.Logf, port int, overrideAddrs []netip.Addr) (s *Server, err error) {
|
|
|
|
// used.
|
|
|
|
|
|
|
|
func NewServer(logf logger.Logf, port int, onlyStaticAddrPorts bool) (s *Server, err error) {
|
|
|
|
s = &Server{
|
|
|
|
s = &Server{
|
|
|
|
logf: logf,
|
|
|
|
logf: logf,
|
|
|
|
disco: key.NewDisco(),
|
|
|
|
disco: key.NewDisco(),
|
|
|
|
bindLifetime: defaultBindLifetime,
|
|
|
|
bindLifetime: defaultBindLifetime,
|
|
|
|
steadyStateLifetime: defaultSteadyStateLifetime,
|
|
|
|
steadyStateLifetime: defaultSteadyStateLifetime,
|
|
|
|
closeCh: make(chan struct{}),
|
|
|
|
closeCh: make(chan struct{}),
|
|
|
|
|
|
|
|
onlyStaticAddrPorts: onlyStaticAddrPorts,
|
|
|
|
byDisco: make(map[key.SortedPairOfDiscoPublic]*serverEndpoint),
|
|
|
|
byDisco: make(map[key.SortedPairOfDiscoPublic]*serverEndpoint),
|
|
|
|
nextVNI: minVNI,
|
|
|
|
nextVNI: minVNI,
|
|
|
|
byVNI: make(map[uint32]*serverEndpoint),
|
|
|
|
byVNI: make(map[uint32]*serverEndpoint),
|
|
|
|
@ -321,19 +325,7 @@ func NewServer(logf logger.Logf, port int, overrideAddrs []netip.Addr) (s *Serve
|
|
|
|
return nil, err
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if len(overrideAddrs) > 0 {
|
|
|
|
if !s.onlyStaticAddrPorts {
|
|
|
|
addrPorts := make(set.Set[netip.AddrPort], len(overrideAddrs))
|
|
|
|
|
|
|
|
for _, addr := range overrideAddrs {
|
|
|
|
|
|
|
|
if addr.IsValid() {
|
|
|
|
|
|
|
|
if addr.Is4() {
|
|
|
|
|
|
|
|
addrPorts.Add(netip.AddrPortFrom(addr, s.uc4Port))
|
|
|
|
|
|
|
|
} else if s.uc6 != nil {
|
|
|
|
|
|
|
|
addrPorts.Add(netip.AddrPortFrom(addr, s.uc6Port))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
s.addrPorts = addrPorts.Slice()
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
s.wg.Add(1)
|
|
|
|
s.wg.Add(1)
|
|
|
|
go s.addrDiscoveryLoop()
|
|
|
|
go s.addrDiscoveryLoop()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -429,8 +421,7 @@ func (s *Server) addrDiscoveryLoop() {
|
|
|
|
s.logf("error discovering IP:port candidates: %v", err)
|
|
|
|
s.logf("error discovering IP:port candidates: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s.mu.Lock()
|
|
|
|
s.mu.Lock()
|
|
|
|
s.addrPorts = addrPorts
|
|
|
|
s.dynamicAddrPorts = addrPorts
|
|
|
|
s.addrDiscoveryOnce = true
|
|
|
|
|
|
|
|
s.mu.Unlock()
|
|
|
|
s.mu.Unlock()
|
|
|
|
case <-s.closeCh:
|
|
|
|
case <-s.closeCh:
|
|
|
|
return
|
|
|
|
return
|
|
|
|
@ -747,6 +738,15 @@ func (s *Server) getNextVNILocked() (uint32, error) {
|
|
|
|
return 0, errors.New("VNI pool exhausted")
|
|
|
|
return 0, errors.New("VNI pool exhausted")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// getAllAddrPortsCopyLocked returns a copy of the combined
|
|
|
|
|
|
|
|
// [Server.staticAddrPorts] and [Server.dynamicAddrPorts] slices.
|
|
|
|
|
|
|
|
func (s *Server) getAllAddrPortsCopyLocked() []netip.AddrPort {
|
|
|
|
|
|
|
|
addrPorts := make([]netip.AddrPort, 0, len(s.dynamicAddrPorts)+s.staticAddrPorts.Len())
|
|
|
|
|
|
|
|
addrPorts = append(addrPorts, s.staticAddrPorts.AsSlice()...)
|
|
|
|
|
|
|
|
addrPorts = append(addrPorts, slices.Clone(s.dynamicAddrPorts)...)
|
|
|
|
|
|
|
|
return addrPorts
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// AllocateEndpoint allocates an [endpoint.ServerEndpoint] for the provided pair
|
|
|
|
// AllocateEndpoint allocates an [endpoint.ServerEndpoint] for the provided pair
|
|
|
|
// of [key.DiscoPublic]'s. If an allocation already exists for discoA and discoB
|
|
|
|
// of [key.DiscoPublic]'s. If an allocation already exists for discoA and discoB
|
|
|
|
// it is returned without modification/reallocation. AllocateEndpoint returns
|
|
|
|
// it is returned without modification/reallocation. AllocateEndpoint returns
|
|
|
|
@ -760,11 +760,8 @@ func (s *Server) AllocateEndpoint(discoA, discoB key.DiscoPublic) (endpoint.Serv
|
|
|
|
return endpoint.ServerEndpoint{}, ErrServerClosed
|
|
|
|
return endpoint.ServerEndpoint{}, ErrServerClosed
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if len(s.addrPorts) == 0 {
|
|
|
|
if s.staticAddrPorts.Len() == 0 && len(s.dynamicAddrPorts) == 0 {
|
|
|
|
if !s.addrDiscoveryOnce {
|
|
|
|
return endpoint.ServerEndpoint{}, ErrServerNotReady{RetryAfter: endpoint.ServerRetryAfter}
|
|
|
|
return endpoint.ServerEndpoint{}, ErrServerNotReady{RetryAfter: endpoint.ServerRetryAfter}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return endpoint.ServerEndpoint{}, errors.New("server addrPorts are not yet known")
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if discoA.Compare(s.discoPublic) == 0 || discoB.Compare(s.discoPublic) == 0 {
|
|
|
|
if discoA.Compare(s.discoPublic) == 0 || discoB.Compare(s.discoPublic) == 0 {
|
|
|
|
@ -787,7 +784,7 @@ func (s *Server) AllocateEndpoint(discoA, discoB key.DiscoPublic) (endpoint.Serv
|
|
|
|
// consider storing them (maybe interning) in the [*serverEndpoint]
|
|
|
|
// consider storing them (maybe interning) in the [*serverEndpoint]
|
|
|
|
// at allocation time.
|
|
|
|
// at allocation time.
|
|
|
|
ClientDisco: pair.Get(),
|
|
|
|
ClientDisco: pair.Get(),
|
|
|
|
AddrPorts: slices.Clone(s.addrPorts),
|
|
|
|
AddrPorts: s.getAllAddrPortsCopyLocked(),
|
|
|
|
VNI: e.vni,
|
|
|
|
VNI: e.vni,
|
|
|
|
LamportID: e.lamportID,
|
|
|
|
LamportID: e.lamportID,
|
|
|
|
BindLifetime: tstime.GoDuration{Duration: s.bindLifetime},
|
|
|
|
BindLifetime: tstime.GoDuration{Duration: s.bindLifetime},
|
|
|
|
@ -817,7 +814,7 @@ func (s *Server) AllocateEndpoint(discoA, discoB key.DiscoPublic) (endpoint.Serv
|
|
|
|
return endpoint.ServerEndpoint{
|
|
|
|
return endpoint.ServerEndpoint{
|
|
|
|
ServerDisco: s.discoPublic,
|
|
|
|
ServerDisco: s.discoPublic,
|
|
|
|
ClientDisco: pair.Get(),
|
|
|
|
ClientDisco: pair.Get(),
|
|
|
|
AddrPorts: slices.Clone(s.addrPorts),
|
|
|
|
AddrPorts: s.getAllAddrPortsCopyLocked(),
|
|
|
|
VNI: e.vni,
|
|
|
|
VNI: e.vni,
|
|
|
|
LamportID: e.lamportID,
|
|
|
|
LamportID: e.lamportID,
|
|
|
|
BindLifetime: tstime.GoDuration{Duration: s.bindLifetime},
|
|
|
|
BindLifetime: tstime.GoDuration{Duration: s.bindLifetime},
|
|
|
|
@ -880,3 +877,13 @@ func (s *Server) getDERPMap() *tailcfg.DERPMap {
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
return s.derpMap
|
|
|
|
return s.derpMap
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// SetStaticAddrPorts sets addr:port pairs the [Server] will advertise
|
|
|
|
|
|
|
|
// as candidates it is potentially reachable over, in combination with
|
|
|
|
|
|
|
|
// dynamically discovered pairs. This replaces any previously-provided static
|
|
|
|
|
|
|
|
// values.
|
|
|
|
|
|
|
|
func (s *Server) SetStaticAddrPorts(addrPorts views.Slice[netip.AddrPort]) {
|
|
|
|
|
|
|
|
s.mu.Lock()
|
|
|
|
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
|
|
|
|
s.staticAddrPorts = addrPorts
|
|
|
|
|
|
|
|
}
|
|
|
|
|