WIP: rebase fork onto upstream/main (v1.103.0) #15

Closed
codinget wants to merge 670 commits from webnet into save/webnet-2026-07-29
3 changed files with 49 additions and 7 deletions
Showing only changes of commit 446ae97491 - Show all commits
+17 -1
View File
@@ -30,6 +30,7 @@ import (
"tailscale.com/tka"
"tailscale.com/tstest"
"tailscale.com/tstest/deptest"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/opt"
"tailscale.com/types/persist"
@@ -769,7 +770,22 @@ func TestPrefsFromUpArgs(t *testing.T) {
args: upArgsT{
exitNodeIP: "foo",
},
wantErr: `invalid value "foo" for --exit-node; must be IP or hostname`,
st: &ipnstate.Status{
Peer: map[key.NodePublic]*ipnstate.PeerStatus{
key.NewNode().Public(): {
DNSName: "example.com.",
TailscaleIPs: []netip.Addr{netip.MustParseAddr("1.0.0.2")},
},
},
},
wantErr: `invalid value "foo" for --exit-node; must be IP or peer hostname`,
},
{
name: "error_exit_node_not_started",
args: upArgsT{
exitNodeIP: "foo",
},
wantErr: `cannot resolve exit node by hostname while Tailscale is starting up; please use its Tailscale IP address instead`,
},
{
name: "error_exit_node_allow_lan_without_exit_node",
+12 -1
View File
@@ -872,10 +872,14 @@ func (e ExitNodeLocalIPError) Error() string {
return fmt.Sprintf("cannot use %s as an exit node as it is a local IP address to this machine", e.hostOrIP)
}
// exitNodeIPOfArg returns the IP address of the exit node based on the
// user-provided string.
func exitNodeIPOfArg(s string, st *ipnstate.Status) (ip netip.Addr, err error) {
if s == "" {
return ip, os.ErrInvalid
}
// If the string is a valid IP address, that's the exit node.
ip, err = netip.ParseAddr(s)
if err == nil {
if !isRemoteIP(st, ip) {
@@ -894,6 +898,13 @@ func exitNodeIPOfArg(s string, st *ipnstate.Status) (ip netip.Addr, err error) {
}
return ip, nil
}
// If the string is not a valid IP address, assume it's a hostname.
// Search the list of peers for a matching hostname.
if len(st.Peer) == 0 {
return ip, errors.New("cannot resolve exit node by hostname while Tailscale is starting up; " +
"please use its Tailscale IP address instead")
}
match := 0
for _, ps := range st.Peer {
// Compare to the peer name in three forms:
@@ -920,7 +931,7 @@ func exitNodeIPOfArg(s string, st *ipnstate.Status) (ip netip.Addr, err error) {
}
switch match {
case 0:
return ip, fmt.Errorf("invalid value %q for --exit-node; must be IP or hostname", s)
return ip, fmt.Errorf("invalid value %q for --exit-node; must be IP or peer hostname", s)
case 1:
if !isRemoteIP(st, ip) {
return ip, ExitNodeLocalIPError{s}
+20 -5
View File
@@ -1006,10 +1006,19 @@ func TestExitNodeIPOfArg(t *testing.T) {
want: mustIP("1.2.3.4"),
},
{
name: "no_match",
arg: "unknown",
st: &ipnstate.Status{MagicDNSSuffix: ".foo"},
wantErr: `invalid value "unknown" for --exit-node; must be IP or hostname`,
name: "no_match",
arg: "unknown",
st: &ipnstate.Status{
MagicDNSSuffix: ".foo",
Peer: map[key.NodePublic]*ipnstate.PeerStatus{
key.NewNode().Public(): {
DNSName: "skippy.foo.",
TailscaleIPs: []netip.Addr{mustIP("1.0.0.2")},
ExitNodeOption: true,
},
},
},
wantErr: `invalid value "unknown" for --exit-node; must be IP or peer hostname`,
},
{
name: "name",
@@ -1056,6 +1065,12 @@ func TestExitNodeIPOfArg(t *testing.T) {
},
want: mustIP("1.0.0.2"),
},
{
name: "hostname_no_peer",
arg: "skippy.foo",
st: &ipnstate.Status{},
wantErr: `cannot resolve exit node by hostname while Tailscale is starting up; please use its Tailscale IP address instead`,
},
{
name: "name_not_exit",
arg: "skippy",
@@ -1082,7 +1097,7 @@ func TestExitNodeIPOfArg(t *testing.T) {
},
},
},
wantErr: `invalid value "skippy.bar." for --exit-node; must be IP or hostname`,
wantErr: `invalid value "skippy.bar." for --exit-node; must be IP or peer hostname`,
},
{
name: "ambiguous",