@@ -36,6 +36,7 @@ import (
3636 "tailscale.com/types/key"
3737 "tailscale.com/types/logger"
3838 "tailscale.com/types/nettype"
39+ "tailscale.com/types/views"
3940 "tailscale.com/util/eventbus"
4041 "tailscale.com/util/set"
4142)
@@ -72,15 +73,16 @@ type Server struct {
7273 closeCh chan struct {}
7374 netChecker * netcheck.Client
7475
75- mu sync.Mutex // guards the following fields
76- derpMap * tailcfg.DERPMap
77- addrDiscoveryOnce bool // addrDiscovery completed once (successfully or unsuccessfully)
78- addrPorts []netip.AddrPort // the ip:port pairs returned as candidate endpoints
79- closed bool
80- lamportID uint64
81- nextVNI uint32
82- byVNI map [uint32 ]* serverEndpoint
83- byDisco map [key.SortedPairOfDiscoPublic ]* serverEndpoint
76+ mu sync.Mutex // guards the following fields
77+ derpMap * tailcfg.DERPMap
78+ onlyStaticAddrPorts bool // no dynamic addr port discovery when set
79+ staticAddrPorts views.Slice [netip.AddrPort ] // static ip:port pairs set with [Server.SetStaticAddrPorts]
80+ dynamicAddrPorts []netip.AddrPort // dynamically discovered ip:port pairs
81+ closed bool
82+ lamportID uint64
83+ nextVNI uint32
84+ byVNI map [uint32 ]* serverEndpoint
85+ byDisco map [key.SortedPairOfDiscoPublic ]* serverEndpoint
8486}
8587
8688const (
@@ -278,15 +280,17 @@ func (e *serverEndpoint) isBound() bool {
278280
279281// NewServer constructs a [Server] listening on port. If port is zero, then
280282// port selection is left up to the host networking stack. If
281- // len(overrideAddrs) > 0 these will be used in place of dynamic discovery,
282- // which is useful to override in tests.
283- func NewServer (logf logger.Logf , port int , overrideAddrs []netip.Addr ) (s * Server , err error ) {
283+ // onlyStaticAddrPorts is true, then dynamic addr:port discovery will be
284+ // disabled, and only addr:port's set via [Server.SetStaticAddrPorts] will be
285+ // used.
286+ func NewServer (logf logger.Logf , port int , onlyStaticAddrPorts bool ) (s * Server , err error ) {
284287 s = & Server {
285288 logf : logf ,
286289 disco : key .NewDisco (),
287290 bindLifetime : defaultBindLifetime ,
288291 steadyStateLifetime : defaultSteadyStateLifetime ,
289292 closeCh : make (chan struct {}),
293+ onlyStaticAddrPorts : onlyStaticAddrPorts ,
290294 byDisco : make (map [key.SortedPairOfDiscoPublic ]* serverEndpoint ),
291295 nextVNI : minVNI ,
292296 byVNI : make (map [uint32 ]* serverEndpoint ),
@@ -321,19 +325,7 @@ func NewServer(logf logger.Logf, port int, overrideAddrs []netip.Addr) (s *Serve
321325 return nil , err
322326 }
323327
324- if len (overrideAddrs ) > 0 {
325- addrPorts := make (set.Set [netip.AddrPort ], len (overrideAddrs ))
326- for _ , addr := range overrideAddrs {
327- if addr .IsValid () {
328- if addr .Is4 () {
329- addrPorts .Add (netip .AddrPortFrom (addr , s .uc4Port ))
330- } else if s .uc6 != nil {
331- addrPorts .Add (netip .AddrPortFrom (addr , s .uc6Port ))
332- }
333- }
334- }
335- s .addrPorts = addrPorts .Slice ()
336- } else {
328+ if ! s .onlyStaticAddrPorts {
337329 s .wg .Add (1 )
338330 go s .addrDiscoveryLoop ()
339331 }
@@ -429,8 +421,7 @@ func (s *Server) addrDiscoveryLoop() {
429421 s .logf ("error discovering IP:port candidates: %v" , err )
430422 }
431423 s .mu .Lock ()
432- s .addrPorts = addrPorts
433- s .addrDiscoveryOnce = true
424+ s .dynamicAddrPorts = addrPorts
434425 s .mu .Unlock ()
435426 case <- s .closeCh :
436427 return
@@ -747,6 +738,15 @@ func (s *Server) getNextVNILocked() (uint32, error) {
747738 return 0 , errors .New ("VNI pool exhausted" )
748739}
749740
741+ // getAllAddrPortsCopyLocked returns a copy of the combined
742+ // [Server.staticAddrPorts] and [Server.dynamicAddrPorts] slices.
743+ func (s * Server ) getAllAddrPortsCopyLocked () []netip.AddrPort {
744+ addrPorts := make ([]netip.AddrPort , 0 , len (s .dynamicAddrPorts )+ s .staticAddrPorts .Len ())
745+ addrPorts = append (addrPorts , s .staticAddrPorts .AsSlice ()... )
746+ addrPorts = append (addrPorts , slices .Clone (s .dynamicAddrPorts )... )
747+ return addrPorts
748+ }
749+
750750// AllocateEndpoint allocates an [endpoint.ServerEndpoint] for the provided pair
751751// of [key.DiscoPublic]'s. If an allocation already exists for discoA and discoB
752752// it is returned without modification/reallocation. AllocateEndpoint returns
@@ -760,11 +760,8 @@ func (s *Server) AllocateEndpoint(discoA, discoB key.DiscoPublic) (endpoint.Serv
760760 return endpoint.ServerEndpoint {}, ErrServerClosed
761761 }
762762
763- if len (s .addrPorts ) == 0 {
764- if ! s .addrDiscoveryOnce {
765- return endpoint.ServerEndpoint {}, ErrServerNotReady {RetryAfter : endpoint .ServerRetryAfter }
766- }
767- return endpoint.ServerEndpoint {}, errors .New ("server addrPorts are not yet known" )
763+ if s .staticAddrPorts .Len () == 0 && len (s .dynamicAddrPorts ) == 0 {
764+ return endpoint.ServerEndpoint {}, ErrServerNotReady {RetryAfter : endpoint .ServerRetryAfter }
768765 }
769766
770767 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
787784 // consider storing them (maybe interning) in the [*serverEndpoint]
788785 // at allocation time.
789786 ClientDisco : pair .Get (),
790- AddrPorts : slices . Clone ( s . addrPorts ),
787+ AddrPorts : s . getAllAddrPortsCopyLocked ( ),
791788 VNI : e .vni ,
792789 LamportID : e .lamportID ,
793790 BindLifetime : tstime.GoDuration {Duration : s .bindLifetime },
@@ -817,7 +814,7 @@ func (s *Server) AllocateEndpoint(discoA, discoB key.DiscoPublic) (endpoint.Serv
817814 return endpoint.ServerEndpoint {
818815 ServerDisco : s .discoPublic ,
819816 ClientDisco : pair .Get (),
820- AddrPorts : slices . Clone ( s . addrPorts ),
817+ AddrPorts : s . getAllAddrPortsCopyLocked ( ),
821818 VNI : e .vni ,
822819 LamportID : e .lamportID ,
823820 BindLifetime : tstime.GoDuration {Duration : s .bindLifetime },
@@ -880,3 +877,13 @@ func (s *Server) getDERPMap() *tailcfg.DERPMap {
880877 defer s .mu .Unlock ()
881878 return s .derpMap
882879}
880+
881+ // SetStaticAddrPorts sets addr:port pairs the [Server] will advertise
882+ // as candidates it is potentially reachable over, in combination with
883+ // dynamically discovered pairs. This replaces any previously-provided static
884+ // values.
885+ func (s * Server ) SetStaticAddrPorts (addrPorts views.Slice [netip.AddrPort ]) {
886+ s .mu .Lock ()
887+ defer s .mu .Unlock ()
888+ s .staticAddrPorts = addrPorts
889+ }
0 commit comments