|
|
@@ -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.msgToSend == 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 {
|