@ -34,6 +34,7 @@ import (
"tailscale.com/types/logger"
"tailscale.com/types/ptr"
"tailscale.com/util/rands"
"tailscale.com/util/set"
)
const msgLimit = 1 << 20 // encrypted message length limit
@ -67,6 +68,10 @@ type Server struct {
// masquerade address to use for that peer.
masquerades map [ key . NodePublic ] map [ key . NodePublic ] netip . Addr // node => peer => SelfNodeV4MasqAddrForThisPeer IP
// suppressAutoMapResponses is the set of nodes that should not be sent
// automatic map responses from serveMap. (They should only get manually sent ones)
suppressAutoMapResponses set . Set [ key . NodePublic ]
noisePubKey key . MachinePublic
noisePrivKey key . ControlPrivate // not strictly needed vs. MachinePrivate, but handy to test type interactions.
@ -76,8 +81,8 @@ type Server struct {
updates map [ tailcfg . NodeID ] chan updateType
authPath map [ string ] * AuthPath
nodeKeyAuthed map [ key . NodePublic ] bool // key => true once authenticated
pingReqsToAdd map [ key . NodePublic ] * tailcfg . PingRequest
allExpired bool // All nodes will be told their node key is expired.
msgToSend map [ key . NodePublic ] any // value is *tailcfg.PingRequest or entire *tailcfg.MapResponse
allExpired bool // All nodes will be told their node key is expired.
}
// BaseURL returns the server's base URL, without trailing slash.
@ -146,13 +151,32 @@ func (s *Server) AwaitNodeInMapRequest(ctx context.Context, k key.NodePublic) er
}
}
// AddPingRequest sends the ping pr to nodeKeyDst. It reports whether it did so. That is,
// it reports whether nodeKeyDst was connected.
// AddPingRequest sends the ping pr to nodeKeyDst.
//
// It reports whether the message was enqueued. That is, it reports whether
// nodeKeyDst was connected.
func ( s * Server ) AddPingRequest ( nodeKeyDst key . NodePublic , pr * tailcfg . PingRequest ) bool {
return s . addDebugMessage ( nodeKeyDst , pr )
}
// AddRawMapResponse delivers the raw MapResponse mr to nodeKeyDst. It's meant
// for testing incremental map updates.
//
// Once AddRawMapResponse has been sent to a node, all future automatic
// MapResponses to that node will be suppressed and only explicit MapResponses
// injected via AddRawMapResponse will be sent.
//
// It reports whether the message was enqueued. That is, it reports whether
// nodeKeyDst was connected.
func ( s * Server ) AddRawMapResponse ( nodeKeyDst key . NodePublic , mr * tailcfg . MapResponse ) bool {
return s . addDebugMessage ( nodeKeyDst , mr )
}
func ( s * Server ) addDebugMessage ( nodeKeyDst key . NodePublic , msg any ) bool {
s . mu . Lock ( )
defer s . mu . Unlock ( )
if s . pingReqsToAdd == nil {
s . pingReqsToAdd = map [ key . NodePublic ] * tailcfg . PingRequest { }
if s . msgToSen d == nil {
s . msgToSend = map [ key . NodePublic ] any { }
}
// Now send the update to the channel
node := s . nodeLocked ( nodeKeyDst )
@ -160,7 +184,14 @@ func (s *Server) AddPingRequest(nodeKeyDst key.NodePublic, pr *tailcfg.PingReque
return false
}
s . pingReqsToAdd [ nodeKeyDst ] = pr
if _ , ok := msg . ( * tailcfg . MapResponse ) ; ok {
if s . suppressAutoMapResponses == nil {
s . suppressAutoMapResponses = set . Set [ key . NodePublic ] { }
}
s . suppressAutoMapResponses . Add ( nodeKeyDst )
}
s . msgToSend [ nodeKeyDst ] = msg
nodeID := node . ID
oldUpdatesCh := s . updates [ nodeID ]
return sendUpdate ( oldUpdatesCh , updateDebugInjection )
@ -602,6 +633,7 @@ const (
updateSelfChanged
// updateDebugInjection is an update used for PingRequests
// or a raw MapResponse.
updateDebugInjection
)
@ -725,33 +757,49 @@ func (s *Server) serveMap(w http.ResponseWriter, r *http.Request, mkey key.Machi
w . WriteHeader ( 200 )
for {
res , err := s . MapResponse ( req )
if err != nil {
// TODO: log
if resBytes , ok := s . takeRawMapMessage ( req . NodeKey ) ; ok {
if err := s . sendMapMsg ( w , mkey , compress , resBytes ) ; err != nil {
s . logf ( "sendMapMsg of raw message: %v" , err )
return
}
if streaming {
continue
}
return
}
if res == nil {
return // done
}
s . mu . Lock ( )
allExpired := s . allExpired
s . mu . Unlock ( )
if allExpired {
res . Node . KeyExpiry = time . Now ( ) . Add ( - 1 * time . Minute )
}
// TODO: add minner if/when needed
resBytes , err := json . Marshal ( res )
if err != nil {
s . logf ( "json.Marshal: %v" , err )
return
}
if err := s . sendMapMsg ( w , mkey , compress , resBytes ) ; err != nil {
return
if s . canGenerateAutomaticMapResponseFor ( req . NodeKey ) {
res , err := s . MapResponse ( req )
if err != nil {
// TODO: log
return
}
if res == nil {
return // done
}
s . mu . Lock ( )
allExpired := s . allExpired
s . mu . Unlock ( )
if allExpired {
res . Node . KeyExpiry = time . Now ( ) . Add ( - 1 * time . Minute )
}
// TODO: add minner if/when needed
resBytes , err := json . Marshal ( res )
if err != nil {
s . logf ( "json.Marshal: %v" , err )
return
}
if err := s . sendMapMsg ( w , mkey , compress , resBytes ) ; err != nil {
return
}
}
if ! streaming {
return
}
if s . hasPendingRawMapMessage ( req . NodeKey ) {
continue
}
keepAliveLoop :
for {
var keepAliveTimer * time . Timer
@ -874,16 +922,46 @@ func (s *Server) MapResponse(req *tailcfg.MapRequest) (res *tailcfg.MapResponse,
}
res . Node . AllowedIPs = res . Node . Addresses
// Consume the PingRequest while protected by mutex if it exists
// Consume a PingRequest while protected by mutex if it exists
s . mu . Lock ( )
if pr , ok := s . pingReqsToAdd [ nk ] ; ok {
res . PingRequest = pr
delete ( s . pingReqsToAdd , nk )
defer s . mu . Unlock ( )
switch m := s . msgToSend [ nk ] . ( type ) {
case * tailcfg . PingRequest :
res . PingRequest = m
delete ( s . msgToSend , nk )
}
s . mu . Unlock ( )
return res , nil
}
func ( s * Server ) canGenerateAutomaticMapResponseFor ( nk key . NodePublic ) bool {
s . mu . Lock ( )
defer s . mu . Unlock ( )
return ! s . suppressAutoMapResponses . Contains ( nk )
}
func ( s * Server ) hasPendingRawMapMessage ( nk key . NodePublic ) bool {
s . mu . Lock ( )
defer s . mu . Unlock ( )
_ , ok := s . msgToSend [ nk ] . ( * tailcfg . MapResponse )
return ok
}
func ( s * Server ) takeRawMapMessage ( nk key . NodePublic ) ( mapResJSON [ ] byte , ok bool ) {
s . mu . Lock ( )
defer s . mu . Unlock ( )
mr , ok := s . msgToSend [ nk ] . ( * tailcfg . MapResponse )
if ! ok {
return nil , false
}
delete ( s . msgToSend , nk )
var err error
mapResJSON , err = json . Marshal ( mr )
if err != nil {
panic ( err )
}
return mapResJSON , true
}
func ( s * Server ) sendMapMsg ( w http . ResponseWriter , mkey key . MachinePublic , compress bool , msg any ) error {
resBytes , err := s . encode ( mkey , compress , msg )
if err != nil {