mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 13:05:46 +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,
|
Exec: runPeerEndpointChanges,
|
||||||
ShortHelp: "prints debug information about a peer's endpoint changes",
|
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)
|
e.Encode(v)
|
||||||
return nil
|
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,
|
"component-debug-logging": (*Handler).serveComponentDebugLogging,
|
||||||
"debug": (*Handler).serveDebug,
|
"debug": (*Handler).serveDebug,
|
||||||
"debug-derp-region": (*Handler).serveDebugDERPRegion,
|
"debug-derp-region": (*Handler).serveDebugDERPRegion,
|
||||||
|
"debug-dial-types": (*Handler).serveDebugDialTypes,
|
||||||
"debug-packet-filter-matches": (*Handler).serveDebugPacketFilterMatches,
|
"debug-packet-filter-matches": (*Handler).serveDebugPacketFilterMatches,
|
||||||
"debug-packet-filter-rules": (*Handler).serveDebugPacketFilterRules,
|
"debug-packet-filter-rules": (*Handler).serveDebugPacketFilterRules,
|
||||||
"debug-portmap": (*Handler).serveDebugPortmap,
|
"debug-portmap": (*Handler).serveDebugPortmap,
|
||||||
@ -840,6 +841,76 @@ func (h *Handler) serveComponentDebugLogging(w http.ResponseWriter, r *http.Requ
|
|||||||
json.NewEncoder(w).Encode(res)
|
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,
|
// servePprofFunc is the implementation of Handler.servePprof, after auth,
|
||||||
// for platforms where we want to link it in.
|
// for platforms where we want to link it in.
|
||||||
var servePprofFunc func(http.ResponseWriter, *http.Request)
|
var servePprofFunc func(http.ResponseWriter, *http.Request)
|
||||||
|
Loading…
Reference in New Issue
Block a user