ipn/ipnlocal: extract ping implementation into its own file

There’s enough code to pull this out into its own ping.go file, since
local.go contains a lot of stuff.

The next commit will add a Pinger interface to this file, so I am
breaking out this refactor to make identifying the changes and
reviewing the actual patch easier.

Signed-off-by: Simon Law <sfllaw@tailscale.com>
sfllaw/tailscale-ping-for-client-side-reachability
Simon Law 2 months ago
parent 92f57f74a8
commit 5555bbcd22
No known key found for this signature in database
GPG Key ID: B83D1EE07548341D

@ -3700,81 +3700,6 @@ func (b *LocalBackend) StartLoginInteractiveAs(ctx context.Context, user ipnauth
return nil
}
func (b *LocalBackend) Ping(ctx context.Context, ip netip.Addr, pingType tailcfg.PingType, size int) (*ipnstate.PingResult, error) {
if pingType == tailcfg.PingPeerAPI {
t0 := b.clock.Now()
node, base, err := b.pingPeerAPI(ctx, ip)
if err != nil && ctx.Err() != nil {
return nil, ctx.Err()
}
d := b.clock.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.Valid() {
pr.NodeName = node.Name()
}
return pr, nil
}
ch := make(chan *ipnstate.PingResult, 1)
b.e.Ping(ip, pingType, size, func(pr *ipnstate.PingResult) {
select {
case ch <- pr:
default:
}
})
select {
case pr := <-ch:
return pr, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
func (b *LocalBackend) pingPeerAPI(ctx context.Context, ip netip.Addr) (peer tailcfg.NodeView, peerBase string, err error) {
if !buildfeatures.HasPeerAPIClient {
return peer, peerBase, feature.ErrUnavailable
}
var zero tailcfg.NodeView
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
nm := b.NetMap()
if nm == nil {
return zero, "", errors.New("no netmap")
}
peer, ok := nm.PeerByTailscaleIP(ip)
if !ok {
return zero, "", fmt.Errorf("no peer found with Tailscale IP %v", ip)
}
if peer.Expired() {
return zero, "", errors.New("peer's node key has expired")
}
base := peerAPIBase(nm, peer)
if base == "" {
return zero, "", fmt.Errorf("no PeerAPI base found for peer %v (%v)", peer.ID(), ip)
}
outReq, err := http.NewRequestWithContext(ctx, "HEAD", base, nil)
if err != nil {
return zero, "", err
}
tr := b.Dialer().PeerAPITransport()
res, err := tr.RoundTrip(outReq)
if err != nil {
return zero, "", err
}
defer res.Body.Close() // but unnecessary on HEAD responses
if res.StatusCode != http.StatusOK {
return zero, "", 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

@ -0,0 +1,93 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package ipnlocal
import (
"context"
"errors"
"fmt"
"net/http"
"net/netip"
"time"
"tailscale.com/feature"
"tailscale.com/feature/buildfeatures"
"tailscale.com/ipn/ipnstate"
"tailscale.com/tailcfg"
)
func (b *LocalBackend) Ping(ctx context.Context, ip netip.Addr, pingType tailcfg.PingType, size int) (*ipnstate.PingResult, error) {
if pingType == tailcfg.PingPeerAPI {
t0 := b.clock.Now()
node, base, err := b.pingPeerAPI(ctx, ip)
if err != nil && ctx.Err() != nil {
return nil, ctx.Err()
}
d := b.clock.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.Valid() {
pr.NodeName = node.Name()
}
return pr, nil
}
ch := make(chan *ipnstate.PingResult, 1)
b.e.Ping(ip, pingType, size, func(pr *ipnstate.PingResult) {
select {
case ch <- pr:
default:
}
})
select {
case pr := <-ch:
return pr, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
func (b *LocalBackend) pingPeerAPI(ctx context.Context, ip netip.Addr) (peer tailcfg.NodeView, peerBase string, err error) {
if !buildfeatures.HasPeerAPIClient {
return peer, peerBase, feature.ErrUnavailable
}
var zero tailcfg.NodeView
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
nm := b.NetMap()
if nm == nil {
return zero, "", errors.New("no netmap")
}
peer, ok := nm.PeerByTailscaleIP(ip)
if !ok {
return zero, "", fmt.Errorf("no peer found with Tailscale IP %v", ip)
}
if peer.Expired() {
return zero, "", errors.New("peer's node key has expired")
}
base := peerAPIBase(nm, peer)
if base == "" {
return zero, "", fmt.Errorf("no PeerAPI base found for peer %v (%v)", peer.ID(), ip)
}
outReq, err := http.NewRequestWithContext(ctx, "HEAD", base, nil)
if err != nil {
return zero, "", err
}
tr := b.Dialer().PeerAPITransport()
res, err := tr.RoundTrip(outReq)
if err != nil {
return zero, "", err
}
defer res.Body.Close() // but unnecessary on HEAD responses
if res.StatusCode != http.StatusOK {
return zero, "", fmt.Errorf("HTTP status %v", res.Status)
}
return peer, base, nil
}
Loading…
Cancel
Save