Add to tailcfg

This commit is contained in:
Anton Tolchanov 2025-09-04 19:44:48 +01:00
parent d16b635023
commit 275345f498
2 changed files with 48 additions and 34 deletions

View File

@ -11,7 +11,6 @@ import (
"fmt"
"io"
"net/http"
"net/netip"
"os"
"os/exec"
"path"
@ -21,8 +20,6 @@ import (
"strings"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"tailscale.com/clientupdate"
"tailscale.com/control/controlclient"
"tailscale.com/envknob"
@ -30,12 +27,9 @@ import (
"tailscale.com/net/sockstats"
"tailscale.com/posture"
"tailscale.com/tailcfg"
"tailscale.com/types/ipproto"
"tailscale.com/types/key"
"tailscale.com/types/netmap"
"tailscale.com/types/views"
"tailscale.com/util/clientmetric"
"tailscale.com/util/goroutines"
"tailscale.com/util/httpm"
"tailscale.com/util/set"
"tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/ptype"
@ -54,7 +48,7 @@ var c2nHandlers = map[methodAndPath]c2nHandler{
req("/debug/metrics"): handleC2NDebugMetrics,
req("/debug/component-logging"): handleC2NDebugComponentLogging,
req("/debug/logheap"): handleC2NDebugLogHeap,
req("POST /debug/netmap"): handleC2NDebugNetMap,
req("/debug/netmap"): handleC2NDebugNetMap,
// PPROF - We only expose a subset of typical pprof endpoints for security.
req("/debug/pprof/heap"): handleC2NPprof,
@ -161,37 +155,42 @@ func handleC2NLogtailFlush(b *LocalBackend, w http.ResponseWriter, r *http.Reque
func handleC2NDebugNetMap(b *LocalBackend, w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
b.logf("c2n: POST /debug/netmap received")
var body struct {
// If set, this MapResponse is used to generate a candidate netmap
// to compare against the current one.
Candidate *tailcfg.MapResponse `json:"candidate,omitempty"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
if r.Method != httpm.POST && r.Method != httpm.GET {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
resp := struct {
// Current netmap, based on the existing map poll + all the deltas.
Current *netmap.NetworkMap `json:"current"`
// A fresh netmap generated based on the MapResponse from the request.
Candidate *netmap.NetworkMap `json:"candidate,omitempty"` // optional
Diff string
}{
Current: b.NetMapWithPeers(),
b.logf("c2n: %s /debug/netmap received", r.Method)
cur, err := json.Marshal(b.NetMapWithPeers())
if err != nil {
http.Error(w, fmt.Sprintf("failed to marshal current netmap: %v", err), http.StatusInternalServerError)
return
}
if body.Candidate != nil {
cand, err := controlclient.NetmapFromMapResponse(ctx, b.pm.CurrentPrefs(), body.Candidate)
if err != nil {
http.Error(w, fmt.Sprintf("failed to convert candidate MapResponse: %v", err), http.StatusBadRequest)
resp := &tailcfg.C2NDebugNetmapResponse{Current: cur}
if r.Method == httpm.POST {
var req tailcfg.C2NDebugNetmapRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
resp.Candidate = cand
resp.Diff = cmp.Diff(resp.Candidate, resp.Current,
cmp.AllowUnexported(netmap.NetworkMap{}),
cmpopts.IgnoreUnexported(views.Slice[ipproto.Proto]{}, views.Slice[tailcfg.FilterRule]{}),
cmpopts.EquateComparable(key.NodePublic{}, key.MachinePublic{}, ipproto.Proto(0), netip.Prefix{}))
if req.Candidate != nil {
// cand, err := b.ccAuto.DirectForTest().NetmapFromMapResponse(ctx, b.pm.CurrentPrefs(), body.Candidate)
cand, err := controlclient.NetmapFromMapResponse(ctx, b.pm.CurrentPrefs(), req.Candidate)
if err != nil {
http.Error(w, fmt.Sprintf("failed to convert candidate MapResponse: %v", err), http.StatusBadRequest)
return
}
candJSON, err := json.Marshal(cand)
if err != nil {
http.Error(w, fmt.Sprintf("failed to marshal candidate netmap: %v", err), http.StatusInternalServerError)
return
}
resp.Candidate = candJSON
}
}
writeJSON(w, resp)
}

View File

@ -5,7 +5,10 @@
package tailcfg
import "net/netip"
import (
"encoding/json"
"net/netip"
)
// C2NSSHUsernamesRequest is the request for the /ssh/usernames.
// A GET request without a request body is equivalent to the zero value of this type.
@ -117,3 +120,15 @@ type C2NVIPServicesResponse struct {
// changes. This value matches what is reported in latest [Hostinfo.ServicesHash].
ServicesHash string
}
type C2NDebugNetmapRequest struct {
Candidate *MapResponse `json:"candidate,omitempty"`
}
type C2NDebugNetmapResponse struct {
// Current is the current network map (netmap.NetworkMap).
Current json.RawMessage `json:"current"`
// Candidate is a network map produced based on the candidate MapResponse.
Candidate json.RawMessage `json:"candidate,omitempty"`
}