cmd/tailscale, ipn/ipnlocal: add "peerapi" ping type

For debugging when stuff like #4750 isn't working.

RELNOTE=tailscale ping -peerapi

Change-Id: I9c52c90fb046e3ab7d2b121387073319fbf27b99
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2022-05-27 21:34:36 -07:00 committed by Brad Fitzpatrick
parent 575aacb1e2
commit 4d85cf586b
5 changed files with 86 additions and 0 deletions

View File

@ -51,6 +51,7 @@
fs.BoolVar(&pingArgs.untilDirect, "until-direct", true, "stop once a direct path is established")
fs.BoolVar(&pingArgs.tsmp, "tsmp", false, "do a TSMP-level ping (through WireGuard, but not either host OS stack)")
fs.BoolVar(&pingArgs.icmp, "icmp", false, "do a ICMP-level ping (through WireGuard, but not the local host OS stack)")
fs.BoolVar(&pingArgs.peerAPI, "peerapi", false, "try hitting the peer's peerapi HTTP server")
fs.IntVar(&pingArgs.num, "c", 10, "max number of pings to send")
fs.DurationVar(&pingArgs.timeout, "timeout", 5*time.Second, "timeout before giving up on a ping")
return fs
@ -63,6 +64,7 @@
verbose bool
tsmp bool
icmp bool
peerAPI bool
timeout time.Duration
}
@ -73,6 +75,9 @@ func pingType() tailcfg.PingType {
if pingArgs.icmp {
return tailcfg.PingICMP
}
if pingArgs.peerAPI {
return tailcfg.PingPeerAPI
}
return tailcfg.PingDisco
}
@ -137,6 +142,10 @@ func runPing(ctx context.Context, args []string) error {
// For now just say which protocol it used.
via = string(pingType())
}
if pingArgs.peerAPI {
printf("hit peerapi of %s (%s) at %s in %s\n", pr.NodeIP, pr.NodeName, pr.PeerAPIURL, latency)
return nil
}
anyPong = true
extra := ""
if pr.PeerAPIPort != 0 {

View File

@ -1706,6 +1706,27 @@ func (b *LocalBackend) StartLoginInteractive() {
}
func (b *LocalBackend) Ping(ctx context.Context, ip netaddr.IP, pingType tailcfg.PingType) (*ipnstate.PingResult, error) {
if pingType == tailcfg.PingPeerAPI {
t0 := time.Now()
node, base, err := b.pingPeerAPI(ctx, ip)
if err != nil && ctx.Err() != nil {
return nil, ctx.Err()
}
d := time.Since(t0)
pr := &ipnstate.PingResult{
IP: ip.String(),
NodeIP: ip.String(),
LatencySeconds: d.Seconds(),
PeerAPIURL: base,
}
if err != nil {
pr.Err = err.Error()
}
if node != nil {
pr.NodeName = node.Name
}
return pr, nil
}
ch := make(chan *ipnstate.PingResult, 1)
b.e.Ping(ip, pingType, func(pr *ipnstate.PingResult) {
select {
@ -1721,6 +1742,37 @@ func (b *LocalBackend) Ping(ctx context.Context, ip netaddr.IP, pingType tailcfg
}
}
func (b *LocalBackend) pingPeerAPI(ctx context.Context, ip netaddr.IP) (peer *tailcfg.Node, peerBase string, err error) {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
nm := b.NetMap()
if nm == nil {
return nil, "", errors.New("no netmap")
}
peer, ok := nm.PeerByTailscaleIP(ip)
if !ok {
return nil, "", fmt.Errorf("no peer found with Tailscale IP %v", ip)
}
base := peerAPIBase(nm, peer)
if base == "" {
return nil, "", fmt.Errorf("no peer API base found for peer %v (%v)", peer.ID, ip)
}
outReq, err := http.NewRequestWithContext(ctx, "HEAD", base, nil)
if err != nil {
return nil, "", err
}
tr := b.Dialer().PeerAPITransport()
res, err := tr.RoundTrip(outReq)
if err != nil {
return nil, "", err
}
defer res.Body.Close() // but unnecessary on HEAD responses
if res.StatusCode != http.StatusOK {
return nil, "", fmt.Errorf("HTTP status %v", res.Status)
}
return peer, base, nil
}
// parseWgStatusLocked returns an EngineStatus based on s.
//
// b.mu must be held; mostly because the caller is about to anyway, and doing so

View File

@ -508,6 +508,10 @@ type PingResult struct {
// running the server on.
PeerAPIPort uint16 `json:",omitempty"`
// PeerAPIURL is the URL that was hit for pings of type "peerapi" (tailcfg.PingPeerAPI).
// It's of the form "http://ip:port" (or [ip]:port for IPv6).
PeerAPIURL string `json:",omitempty"`
// IsLocalIP is whether the ping request error is due to it being
// a ping to the local node.
IsLocalIP bool `json:",omitempty"`

View File

@ -1097,6 +1097,9 @@ type DNSRecord struct {
// PingICMP performs a ping between two tailscale nodes using ICMP that is
// received by the target systems IP stack.
PingICMP PingType = "ICMP"
// PingPeerAPI performs a ping between two tailscale nodes using ICMP that is
// received by the target systems IP stack.
PingPeerAPI PingType = "peerapi"
)
// PingRequest with no IP and Types is a request to send an HTTP request to prove the

View File

@ -72,6 +72,24 @@ type NetworkMap struct {
UserProfiles map[tailcfg.UserID]tailcfg.UserProfile
}
// PeerByTailscaleIP returns a peer's Node based on its Tailscale IP.
//
// If nm is nil or no peer is found, ok is false.
func (nm *NetworkMap) PeerByTailscaleIP(ip netaddr.IP) (peer *tailcfg.Node, ok bool) {
// TODO(bradfitz):
if nm == nil {
return nil, false
}
for _, n := range nm.Peers {
for _, a := range n.Addresses {
if a.IP() == ip {
return n, true
}
}
}
return nil, false
}
// MagicDNSSuffix returns the domain's MagicDNS suffix (even if
// MagicDNS isn't necessarily in use).
//