mirror of
https://github.com/tailscale/tailscale.git
synced 2026-05-06 12:46:20 +02:00
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. Thanks to giacomo and Moyao for pointing this out! Updates tailscale/corp#39702 Change-Id: I78d640f11ccd92f43ddd505cbb0db8fee19f43a6 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
126 lines
3.2 KiB
Go
126 lines
3.2 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
//go:build go1.19
|
|
|
|
package local
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"tailscale.com/tstest/deptest"
|
|
"tailscale.com/tstest/nettest"
|
|
"tailscale.com/types/key"
|
|
)
|
|
|
|
func TestGetServeConfigFromJSON(t *testing.T) {
|
|
sc, err := getServeConfigFromJSON([]byte("null"))
|
|
if sc != nil {
|
|
t.Errorf("want nil for null")
|
|
}
|
|
if err != nil {
|
|
t.Errorf("reading null: %v", err)
|
|
}
|
|
|
|
sc, err = getServeConfigFromJSON([]byte(`{"TCP":{}}`))
|
|
if err != nil {
|
|
t.Errorf("reading object: %v", err)
|
|
} else if sc == nil {
|
|
t.Errorf("want non-nil for object")
|
|
} else if sc.TCP == nil {
|
|
t.Errorf("want non-nil TCP for object")
|
|
}
|
|
}
|
|
|
|
func TestWhoIsPeerNotFound(t *testing.T) {
|
|
nw := nettest.GetNetwork(t)
|
|
ts := nettest.NewHTTPServer(nw, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(404)
|
|
}))
|
|
defer ts.Close()
|
|
|
|
lc := &Client{
|
|
Dial: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
return nw.Dial(ctx, network, ts.Listener.Addr().String())
|
|
},
|
|
}
|
|
var k key.NodePublic
|
|
if err := k.UnmarshalText([]byte("nodekey:5c8f86d5fc70d924e55f02446165a5dae8f822994ad26bcf4b08fd841f9bf261")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
res, err := lc.WhoIsNodeKey(context.Background(), k)
|
|
if err != ErrPeerNotFound {
|
|
t.Errorf("got (%v, %v), want ErrPeerNotFound", res, err)
|
|
}
|
|
res, err = lc.WhoIs(context.Background(), "1.2.3.4:5678")
|
|
if err != ErrPeerNotFound {
|
|
t.Errorf("got (%v, %v), want ErrPeerNotFound", res, err)
|
|
}
|
|
}
|
|
|
|
func TestUserDialSelf(t *testing.T) {
|
|
// Start a real TCP listener that the client should dial directly
|
|
// when the server tells it to dial-self.
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer ln.Close()
|
|
go func() {
|
|
for {
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
c.Write([]byte("hello"))
|
|
c.Close()
|
|
}
|
|
}()
|
|
targetAddr := ln.Addr().(*net.TCPAddr)
|
|
|
|
// Mock LocalAPI server that returns Dial-Self response.
|
|
nw := nettest.GetNetwork(t)
|
|
ts := nettest.NewHTTPServer(nw, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Dial-Self", "true")
|
|
w.Header().Set("Dial-Addr", targetAddr.String())
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer ts.Close()
|
|
|
|
lc := &Client{
|
|
Dial: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
return nw.Dial(ctx, network, ts.Listener.Addr().String())
|
|
},
|
|
}
|
|
|
|
conn, err := lc.UserDial(context.Background(), "tcp", targetAddr.IP.String(), uint16(targetAddr.Port))
|
|
if err != nil {
|
|
t.Fatalf("UserDial: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
buf := make([]byte, 5)
|
|
n, err := conn.Read(buf)
|
|
if err != nil {
|
|
t.Fatalf("Read: %v", err)
|
|
}
|
|
if got := string(buf[:n]); got != "hello" {
|
|
t.Errorf("got %q, want %q", got, "hello")
|
|
}
|
|
}
|
|
|
|
func TestDeps(t *testing.T) {
|
|
deptest.DepChecker{
|
|
BadDeps: map[string]string{
|
|
// Make sure we don't again accidentally bring in a dependency on
|
|
// drive or its transitive dependencies
|
|
"testing": "do not use testing package in production code",
|
|
"tailscale.com/drive/driveimpl": "https://github.com/tailscale/tailscale/pull/10631",
|
|
"github.com/studio-b12/gowebdav": "https://github.com/tailscale/tailscale/pull/10631",
|
|
},
|
|
}.Check(t)
|
|
}
|