From 99b3073ab668a8056f68cf328b5ec53f3fb0fb04 Mon Sep 17 00:00:00 2001 From: Adriano Sela Aviles Date: Fri, 3 Apr 2026 00:46:35 -0700 Subject: [PATCH] tailcfg,types/netmap: expose visible services node attr --- tailcfg/tailcfg.go | 22 ++++++++++++++++++++++ types/netmap/netmap.go | 17 +++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index b976dcc47..389352527 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -2694,6 +2694,14 @@ const ( // (replace conflicting keys). NodeAttrServiceHost NodeCapability = "service-host" + // NodeAttrVisibleServiceDetails carries the full details of VIP services + // that are visible (accessible) to this node, including their names, + // assigned IP addresses, and port requirements. This is sent to consuming + // client nodes so they can discover services without relying solely on DNS + // resolution. There is exactly one value for this key in [NodeCapMap], of + // type []*[ServiceDetail]. + NodeAttrVisibleServiceDetails NodeCapability = "visible-service-details" + // NodeAttrMaxKeyDuration represents the MaxKeyDuration setting on the // tailnet. The value of this key in [NodeCapMap] will be only one entry of // type float64 representing the duration in seconds. This cap will be @@ -3318,6 +3326,20 @@ const LBHeader = "Ts-Lb" // this client is hosting can be ignored. type ServiceIPMappings map[ServiceName][]netip.Addr +// ServiceDetail describes a VIP service visible to or hosted by a node. It is +// used as the element type of the []*[ServiceDetail] value sent via +// [NodeAttrVisibleServiceDetails]. +type ServiceDetail struct { + // Name is the name of the service, of the form "svc:dns-label". + Name ServiceName + + // Addrs are the IP addresses (IPv4 and IPv6) assigned to this service. + Addrs []netip.Addr `json:",omitempty"` + + // Ports are the protocol/port combinations the service accepts. + Ports []ProtoPortRange `json:",omitempty"` +} + // ClientAuditAction represents an auditable action that a client can report to the // control plane. These actions must correspond to the supported actions // in the control plane. diff --git a/types/netmap/netmap.go b/types/netmap/netmap.go index ac95254da..27f3d178f 100644 --- a/types/netmap/netmap.go +++ b/types/netmap/netmap.go @@ -120,6 +120,23 @@ func (nm *NetworkMap) GetVIPServiceIPMap() tailcfg.ServiceIPMappings { return ipMaps[0] } +// GetVisibleServiceDetails returns the list of VIP services that are visible +// (accessible) to this node, including their names, IP addresses, and ports. +// Returns nil if no visible service details are present. +func (nm *NetworkMap) GetVisibleServiceDetails() []*tailcfg.ServiceDetail { + if nm == nil { + return nil + } + if !nm.SelfNode.Valid() { + return nil + } + details, err := tailcfg.UnmarshalNodeCapViewJSON[[]*tailcfg.ServiceDetail](nm.SelfNode.CapMap(), tailcfg.NodeAttrVisibleServiceDetails) + if len(details) != 1 || err != nil { + return nil + } + return details[0] +} + // GetIPVIPServiceMap returns a map of VIP addresses to the service // names that has the VIP address. The service names are with the // prefix "svc:".