From 618dfd408146a7e2df938660fed42fc60497cfa8 Mon Sep 17 00:00:00 2001 From: Adriano Sela Aviles Date: Fri, 17 Apr 2026 12:23:13 -0700 Subject: [PATCH] client/local,types/netmap: modify services format in local api Updates the format of the service map that is served over the local api to be keyed without the "svc:" prefix. This change is backwards incompatible, this is OK because there is only one tailnet with the services-in-nodecapmap feature flag enabled, and the client side changes that start showing services over local api have not been released. (These were added in 4fcce6000d3d3f79d1ac1fca571a50efb059cbf2). Updates tailscale/corp#40052 Signed-off-by: Adriano Sela Aviles --- client/local/local.go | 7 ++++--- types/netmap/netmap.go | 13 +++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/client/local/local.go b/client/local/local.go index 75fdbe5a5..7d3313063 100644 --- a/client/local/local.go +++ b/client/local/local.go @@ -1424,11 +1424,12 @@ func (lc *Client) GetAppConnectorRouteInfo(ctx context.Context) (appctype.RouteI } // GetServices returns the Services visible to this node, -// including their names, IP addresses, and ports, keyed by service name. -func (lc *Client) GetServices(ctx context.Context) (map[tailcfg.ServiceName]tailcfg.ServiceDetails, error) { +// including their names, IP addresses, and ports, keyed by service name +// without the "svc:" prefix. +func (lc *Client) GetServices(ctx context.Context) (map[string]tailcfg.ServiceDetails, error) { body, err := lc.get200(ctx, "/localapi/v0/services") if err != nil { return nil, err } - return decodeJSON[map[tailcfg.ServiceName]tailcfg.ServiceDetails](body) + return decodeJSON[map[string]tailcfg.ServiceDetails](body) } diff --git a/types/netmap/netmap.go b/types/netmap/netmap.go index b768caf4c..680bae12f 100644 --- a/types/netmap/netmap.go +++ b/types/netmap/netmap.go @@ -148,17 +148,16 @@ func (nm *NetworkMap) GetIPVIPServiceMap() IPServiceMappings { // Services returns the Services visible (accessible) to this node, // decoded from [tailcfg.NodeAttrPrefixServices] entries in the self node's -// CapMap. The returned map is keyed by [tailcfg.ServiceDetails.Name], which is -// the canonical service name; the NodeCapMap key suffix is opaque and must not -// be parsed or relied upon. It returns nil if nm is nil or SelfNode is invalid. +// CapMap. The returned map is keyed by service name without the "svc:" prefix. +// It returns nil if nm is nil or SelfNode is invalid. // // TODO(adrianosela): cache the result of decoding the capmap so // we don't have to decode it multiple times after each netmap update. -func (nm *NetworkMap) Services() map[tailcfg.ServiceName]tailcfg.ServiceDetails { +func (nm *NetworkMap) Services() map[string]tailcfg.ServiceDetails { if nm == nil || !nm.SelfNode.Valid() { return nil } - result := make(map[tailcfg.ServiceName]tailcfg.ServiceDetails) + result := make(map[string]tailcfg.ServiceDetails) for cap := range nm.SelfNode.CapMap().All() { if !strings.HasPrefix(string(cap), string(tailcfg.NodeAttrPrefixServices)) { continue @@ -167,7 +166,9 @@ func (nm *NetworkMap) Services() map[tailcfg.ServiceName]tailcfg.ServiceDetails if err != nil || len(svcs) < 1 { continue } - result[svcs[0].Name] = svcs[0] + // NOTE(adrianosela): the NodeCapMap key suffix is opaque and MUST not + // be parsed or relied upon (so we extract name from the inner field). + result[svcs[0].Name.WithoutPrefix()] = svcs[0] } return result }