ipn/ipnlocal: add a C2N endpoint for fetching a netmap

For debugging purposes, add a new C2N endpoint returning the current
netmap. Optionally, coordination server can send a new "candidate" map
response, which the client will generate a separate netmap for.
Coordination server can later compare two netmaps, detecting unexpected
changes to the client state.

Updates tailscale/corp#32095

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
This commit is contained in:
Anton Tolchanov
2025-08-13 15:00:35 +01:00
committed by Anton Tolchanov
parent 394718a4ca
commit 4a04161828
9 changed files with 506 additions and 9 deletions

View File

@@ -336,7 +336,7 @@ func (s *Server) serveUnhandled(w http.ResponseWriter, r *http.Request) {
func (s *Server) serveC2N(w http.ResponseWriter, r *http.Request) {
if err := func() error {
if r.Method != httpm.POST {
return fmt.Errorf("POST required")
return errors.New("POST required")
}
token, ok := strings.CutPrefix(r.URL.Path, "/c2n/")
if !ok {
@@ -1148,18 +1148,25 @@ func (s *Server) canGenerateAutomaticMapResponseFor(nk key.NodePublic) bool {
func (s *Server) hasPendingRawMapMessage(nk key.NodePublic) bool {
s.mu.Lock()
defer s.mu.Unlock()
_, ok := s.msgToSend[nk].(*tailcfg.MapResponse)
_, ok := s.msgToSend[nk]
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)
mr, ok := s.msgToSend[nk]
if !ok {
return nil, false
}
delete(s.msgToSend, nk)
// If it's a bare PingRequest, wrap it in a MapResponse.
switch pr := mr.(type) {
case *tailcfg.PingRequest:
mr = &tailcfg.MapResponse{PingRequest: pr}
}
var err error
mapResJSON, err = json.Marshal(mr)
if err != nil {