mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
cmd/tailscale, ipn/ipnlocal: add 'debug dial-types' command
This command allows observing whether a given dialer ("SystemDial", "UserDial", etc.) will successfully obtain a connection to a provided host, from inside tailscaled itself. This is intended to help debug a variety of issues from subnet routers to split DNS setups. Updates #9619 Signed-off-by: Andrew Dunham <andrew@du.nham.ca> Change-Id: Ie01ebb5469d3e287eac633ff656783960f697b84
This commit is contained in:
parent
aed2cfec4e
commit
d3574a350f
@ -274,6 +274,16 @@
|
||||
Exec: runPeerEndpointChanges,
|
||||
ShortHelp: "prints debug information about a peer's endpoint changes",
|
||||
},
|
||||
{
|
||||
Name: "dial-types",
|
||||
Exec: runDebugDialTypes,
|
||||
ShortHelp: "prints debug information about connecting to a given host or IP",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("dial-types")
|
||||
fs.StringVar(&debugDialTypesArgs.network, "network", "tcp", `network type to dial ("tcp", "udp", etc.)`)
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -1015,3 +1025,61 @@ func debugControlKnobs(ctx context.Context, args []string) error {
|
||||
e.Encode(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
var debugDialTypesArgs struct {
|
||||
network string
|
||||
}
|
||||
|
||||
func runDebugDialTypes(ctx context.Context, args []string) error {
|
||||
st, err := localClient.Status(ctx)
|
||||
if err != nil {
|
||||
return fixTailscaledConnectError(err)
|
||||
}
|
||||
description, ok := isRunningOrStarting(st)
|
||||
if !ok {
|
||||
printf("%s\n", description)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(args) != 2 || args[0] == "" || args[1] == "" {
|
||||
return errors.New("usage: dial-types <hostname-or-IP> <port>")
|
||||
}
|
||||
|
||||
port, err := strconv.ParseUint(args[1], 10, 16)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid port %q: %w", args[1], err)
|
||||
}
|
||||
|
||||
hostOrIP := args[0]
|
||||
ip, _, err := tailscaleIPFromArg(ctx, hostOrIP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ip != hostOrIP {
|
||||
log.Printf("lookup %q => %q", hostOrIP, ip)
|
||||
}
|
||||
|
||||
qparams := make(url.Values)
|
||||
qparams.Set("ip", ip)
|
||||
qparams.Set("port", strconv.FormatUint(port, 10))
|
||||
qparams.Set("network", debugDialTypesArgs.network)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", "http://local-tailscaled.sock/localapi/v0/debug-dial-types?"+qparams.Encode(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := localClient.DoLocalRequest(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s", body)
|
||||
return nil
|
||||
}
|
||||
|
@ -80,6 +80,7 @@
|
||||
"component-debug-logging": (*Handler).serveComponentDebugLogging,
|
||||
"debug": (*Handler).serveDebug,
|
||||
"debug-derp-region": (*Handler).serveDebugDERPRegion,
|
||||
"debug-dial-types": (*Handler).serveDebugDialTypes,
|
||||
"debug-packet-filter-matches": (*Handler).serveDebugPacketFilterMatches,
|
||||
"debug-packet-filter-rules": (*Handler).serveDebugPacketFilterRules,
|
||||
"debug-portmap": (*Handler).serveDebugPortmap,
|
||||
@ -840,6 +841,76 @@ func (h *Handler) serveComponentDebugLogging(w http.ResponseWriter, r *http.Requ
|
||||
json.NewEncoder(w).Encode(res)
|
||||
}
|
||||
|
||||
func (h *Handler) serveDebugDialTypes(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "debug-dial-types access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if r.Method != httpm.POST {
|
||||
http.Error(w, "only POST allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
ip := r.FormValue("ip")
|
||||
port := r.FormValue("port")
|
||||
network := r.FormValue("network")
|
||||
|
||||
addr := ip + ":" + port
|
||||
if _, err := netip.ParseAddrPort(addr); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprintf(w, "invalid address %q: %v", addr, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var bareDialer net.Dialer
|
||||
|
||||
dialer := h.b.Dialer()
|
||||
|
||||
var peerDialer net.Dialer
|
||||
peerDialer.Control = dialer.PeerDialControlFunc()
|
||||
|
||||
// Kick off a dial with each available dialer in parallel.
|
||||
dialers := []struct {
|
||||
name string
|
||||
dial func(context.Context, string, string) (net.Conn, error)
|
||||
}{
|
||||
{"SystemDial", dialer.SystemDial},
|
||||
{"UserDial", dialer.UserDial},
|
||||
{"PeerDial", peerDialer.DialContext},
|
||||
{"BareDial", bareDialer.DialContext},
|
||||
}
|
||||
type result struct {
|
||||
name string
|
||||
conn net.Conn
|
||||
err error
|
||||
}
|
||||
results := make(chan result, len(dialers))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, dialer := range dialers {
|
||||
dialer := dialer // loop capture
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
conn, err := dialer.dial(ctx, network, addr)
|
||||
results <- result{dialer.name, conn, err}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
for i := 0; i < len(dialers); i++ {
|
||||
res := <-results
|
||||
fmt.Fprintf(w, "[%s] connected=%v err=%v\n", res.name, res.conn != nil, res.err)
|
||||
if res.conn != nil {
|
||||
res.conn.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// servePprofFunc is the implementation of Handler.servePprof, after auth,
|
||||
// for platforms where we want to link it in.
|
||||
var servePprofFunc func(http.ResponseWriter, *http.Request)
|
||||
|
Loading…
Reference in New Issue
Block a user