From 83c8440834313e4e779aec9acf3b18891502d0a8 Mon Sep 17 00:00:00 2001 From: Adriano Sela Aviles Date: Fri, 5 Jun 2026 14:57:52 -0700 Subject: [PATCH] cmd/tailscale/cli: add service support to tailscale ip Fixes #20035 Signed-off-by: Adriano Sela Aviles --- cmd/tailscale/cli/ip.go | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/cmd/tailscale/cli/ip.go b/cmd/tailscale/cli/ip.go index b76ef0a70..4b24727dd 100644 --- a/cmd/tailscale/cli/ip.go +++ b/cmd/tailscale/cli/ip.go @@ -17,9 +17,9 @@ var ipCmd = &ffcli.Command{ Name: "ip", - ShortUsage: "tailscale ip [-1] [-4] [-6] [peer hostname or ip address]", + ShortUsage: "tailscale ip [-1] [-4] [-6] [peer or service hostname or ip address]", ShortHelp: "Show Tailscale IP addresses", - LongHelp: "Show Tailscale IP addresses for peer. Peer defaults to the current machine.", + LongHelp: "Show Tailscale IP addresses for peer or service. Peer defaults to the current machine.", Exec: runIP, FlagSet: (func() *flag.FlagSet { fs := newFlagSet("ip") @@ -79,10 +79,20 @@ func runIP(ctx context.Context, args []string) error { return err } peer, ok := peerMatchingIP(st, ip) - if !ok { - return fmt.Errorf("no peer found with IP %v", ip) + if ok { + ips = peer.TailscaleIPs + } else { + // No peer matched; check if the IP belongs to a service. + serviceIPs, err := serviceAddrsMatchingIP(ctx, ip) + if err != nil { + return err + } + if serviceIPs != nil { + ips = serviceIPs + } else { + return fmt.Errorf("no peer or service found with IP %v", ip) + } } - ips = peer.TailscaleIPs } if len(ips) == 0 { return fmt.Errorf("no current Tailscale IPs; state: %v", st.BackendState) @@ -109,6 +119,25 @@ func runIP(ctx context.Context, args []string) error { return nil } +// serviceAddrsMatchingIP checks whether ipStr matches a service's VIP address +// and returns the service's addresses if so. +func serviceAddrsMatchingIP(ctx context.Context, ipStr string) ([]netip.Addr, error) { + ip, err := netip.ParseAddr(ipStr) + if err != nil { + return nil, nil + } + services, err := localClient.GetServices(ctx) + if err != nil { + return nil, err + } + for _, svc := range services { + if slices.Contains(svc.Addrs, ip) { + return svc.Addrs, nil + } + } + return nil, nil +} + func peerMatchingIP(st *ipnstate.Status, ipStr string) (ps *ipnstate.PeerStatus, ok bool) { ip, err := netip.ParseAddr(ipStr) if err != nil {