summaryrefslogtreecommitdiffhomepage
path: root/ipn/localapi/localapi_test.go
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@tailscale.com>2026-04-06 23:45:17 +0000
committerBrad Fitzpatrick <bradfitz@tailscale.com>2026-04-06 23:45:17 +0000
commit07869784472492e9420632a8c10a86bbdf888b5f (patch)
tree9ce1dc200c51781c44446906b1c1cbc957533a9f /ipn/localapi/localapi_test.go
parentd0cd0906d5d40567d10aebfab1b8ebe14ca48f64 (diff)
downloadtailscale-bradfitz/dial_local.tar.xz
tailscale-bradfitz/dial_local.zip
net/tsdial, ipn/localapi, client/local: let clients dial non-Tailscale addresses directlybradfitz/dial_local
Add a tsdial.Dialer.UserDialPlan method that resolves an address and reports whether the dialer would route it via Tailscale. The LocalAPI /dial handler now uses this to skip proxying for addresses that aren't Tailscale routes (e.g. localhost), returning a Dial-Self response with the resolved address so the client can dial it directly. This avoids an unnecessary round-trip through the daemon for local connections. The client's UserDial handles the new response by dialing the resolved address itself, and the server passes the pre-resolved IP:port for Tailscale dials to avoid redundant DNS lookups. Updates tailscale/corp#39702 Change-Id: I78d640f11ccd92f43ddd505cbb0db8fee19f43a6 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Diffstat (limited to 'ipn/localapi/localapi_test.go')
-rw-r--r--ipn/localapi/localapi_test.go66
1 files changed, 66 insertions, 0 deletions
diff --git a/ipn/localapi/localapi_test.go b/ipn/localapi/localapi_test.go
index 47e334571..a755221bf 100644
--- a/ipn/localapi/localapi_test.go
+++ b/ipn/localapi/localapi_test.go
@@ -500,3 +500,69 @@ func TestServeWithUnhealthyState(t *testing.T) {
})
}
}
+
+func TestServeDialSelf(t *testing.T) {
+ h := handlerForTest(t, &Handler{
+ PermitRead: true,
+ PermitWrite: true,
+ b: newTestLocalBackend(t),
+ })
+
+ tests := []struct {
+ name string
+ host string
+ port string
+ wantSelf bool
+ wantAddr string
+ wantStatus int
+ }{
+ {
+ name: "loopback_v4",
+ host: "127.0.0.1",
+ port: "8080",
+ wantSelf: true,
+ wantAddr: "127.0.0.1:8080",
+ wantStatus: http.StatusOK,
+ },
+ {
+ name: "loopback_v6",
+ host: "::1",
+ port: "8080",
+ wantSelf: true,
+ wantAddr: "[::1]:8080",
+ wantStatus: http.StatusOK,
+ },
+ {
+ name: "localhost",
+ host: "localhost",
+ port: "3000",
+ wantSelf: true,
+ wantStatus: http.StatusOK,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ req := httptest.NewRequest("POST", "http://local-tailscaled.sock/localapi/v0/dial", nil)
+ req.Header.Set("Connection", "upgrade")
+ req.Header.Set("Upgrade", "ts-dial")
+ req.Header.Set("Dial-Host", tt.host)
+ req.Header.Set("Dial-Port", tt.port)
+ resp := httptest.NewRecorder()
+ h.serveDial(resp, req)
+
+ if resp.Code != tt.wantStatus {
+ t.Fatalf("status = %d, want %d; body: %s", resp.Code, tt.wantStatus, resp.Body.String())
+ }
+ gotSelf := resp.Header().Get("Dial-Self") == "true"
+ if gotSelf != tt.wantSelf {
+ t.Errorf("Dial-Self = %v, want %v", gotSelf, tt.wantSelf)
+ }
+ if tt.wantAddr != "" {
+ if got := resp.Header().Get("Dial-Addr"); got != tt.wantAddr {
+ t.Errorf("Dial-Addr = %q, want %q", got, tt.wantAddr)
+ }
+ }
+ })
+ }
+}