From c9f214e503af5357c2cea77629441d8647e6402f Mon Sep 17 00:00:00 2001 From: Alex Chan Date: Wed, 3 Sep 2025 13:47:32 +0100 Subject: [PATCH] ipn: warn about self as the exit node if backend is running (#17018) Before: $ tailscale ip -4 1.2.3.4 $ tailscale set --exit-node=1.2.3.4 no node found in netmap with IP 1.2.3.4 After: $ tailscale set --exit-node=1.2.3.4 cannot use 1.2.3.4 as an exit node as it is a local IP address to this machine; did you mean --advertise-exit-node? The new error message already existed in the code, but would only be triggered if the backend wasn't running -- which means, in practice, it would almost never be triggered. The old error message is technically true, but could be confusing if you don't know the distinction between "netmap" and "tailnet" -- it could sound like the exit node isn't part of your tailnet. A node is never in its own netmap, but it is part of your tailnet. This error confused me when I was doing some local dev work, and it's confused customers before (e.g. #7513). Using the more specific error message should reduce confusion. Updates #7513 Updates https://github.com/tailscale/corp/issues/23596 Signed-off-by: Alex Chan --- ipn/prefs.go | 6 +++--- ipn/prefs_test.go | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/ipn/prefs.go b/ipn/prefs.go index 14b8078c0..88c73ead3 100644 --- a/ipn/prefs.go +++ b/ipn/prefs.go @@ -847,6 +847,9 @@ func exitNodeIPOfArg(s string, st *ipnstate.Status) (ip netip.Addr, err error) { } ip, err = netip.ParseAddr(s) if err == nil { + if !isRemoteIP(st, ip) { + return ip, ExitNodeLocalIPError{s} + } // If we're online already and have a netmap, double check that the IP // address specified is valid. if st.BackendState == "Running" { @@ -858,9 +861,6 @@ func exitNodeIPOfArg(s string, st *ipnstate.Status) (ip netip.Addr, err error) { return ip, fmt.Errorf("node %v is not advertising an exit node", ip) } } - if !isRemoteIP(st, ip) { - return ip, ExitNodeLocalIPError{s} - } return ip, nil } match := 0 diff --git a/ipn/prefs_test.go b/ipn/prefs_test.go index 7aac20c80..3339a631c 100644 --- a/ipn/prefs_test.go +++ b/ipn/prefs_test.go @@ -897,6 +897,23 @@ func TestExitNodeIPOfArg(t *testing.T) { }, wantErr: `no node found in netmap with IP 1.2.3.4`, }, + { + name: "ip_is_self", + arg: "1.2.3.4", + st: &ipnstate.Status{ + TailscaleIPs: []netip.Addr{mustIP("1.2.3.4")}, + }, + wantErr: "cannot use 1.2.3.4 as an exit node as it is a local IP address to this machine", + }, + { + name: "ip_is_self_when_backend_running", + arg: "1.2.3.4", + st: &ipnstate.Status{ + BackendState: "Running", + TailscaleIPs: []netip.Addr{mustIP("1.2.3.4")}, + }, + wantErr: "cannot use 1.2.3.4 as an exit node as it is a local IP address to this machine", + }, { name: "ip_not_exit", arg: "1.2.3.4",