wgengine, net/packet, cmd/tailscale: add ICMP echo

Updates tailscale/corp#754

Signed-off-by: James Tucker <james@tailscale.com>
pull/4602/head
James Tucker 2 years ago committed by James Tucker
parent 66f9292835
commit ae483d3446

@ -18,6 +18,7 @@ import (
"github.com/peterbourgon/ff/v3/ffcli" "github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/ipn/ipnstate" "tailscale.com/ipn/ipnstate"
"tailscale.com/tailcfg"
) )
var pingCmd = &ffcli.Command{ var pingCmd = &ffcli.Command{
@ -26,7 +27,7 @@ var pingCmd = &ffcli.Command{
ShortHelp: "Ping a host at the Tailscale layer, see how it routed", ShortHelp: "Ping a host at the Tailscale layer, see how it routed",
LongHelp: strings.TrimSpace(` LongHelp: strings.TrimSpace(`
The 'tailscale ping' command pings a peer node at the Tailscale layer The 'tailscale ping' command pings a peer node from the Tailscale layer
and reports which route it took for each response. The first ping or and reports which route it took for each response. The first ping or
so will likely go over DERP (Tailscale's TCP relay protocol) while NAT so will likely go over DERP (Tailscale's TCP relay protocol) while NAT
traversal finds a direct path through. traversal finds a direct path through.
@ -48,7 +49,8 @@ relay node.
fs := newFlagSet("ping") fs := newFlagSet("ping")
fs.BoolVar(&pingArgs.verbose, "verbose", false, "verbose output") fs.BoolVar(&pingArgs.verbose, "verbose", false, "verbose output")
fs.BoolVar(&pingArgs.untilDirect, "until-direct", true, "stop once a direct path is established") 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 IP + wireguard, but not involving host OS stack)") 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.IntVar(&pingArgs.num, "c", 10, "max number of pings to send") 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") fs.DurationVar(&pingArgs.timeout, "timeout", 5*time.Second, "timeout before giving up on a ping")
return fs return fs
@ -60,9 +62,20 @@ var pingArgs struct {
untilDirect bool untilDirect bool
verbose bool verbose bool
tsmp bool tsmp bool
icmp bool
timeout time.Duration timeout time.Duration
} }
func pingType() tailcfg.PingType {
if pingArgs.tsmp {
return tailcfg.PingTSMP
}
if pingArgs.icmp {
return tailcfg.PingICMP
}
return tailcfg.PingDisco
}
func runPing(ctx context.Context, args []string) error { func runPing(ctx context.Context, args []string) error {
st, err := localClient.Status(ctx) st, err := localClient.Status(ctx)
if err != nil { if err != nil {
@ -111,7 +124,7 @@ func runPing(ctx context.Context, args []string) error {
anyPong := false anyPong := false
for { for {
n++ n++
bc.Ping(ip, pingArgs.tsmp) bc.Ping(ip, pingType())
timer := time.NewTimer(pingArgs.timeout) timer := time.NewTimer(pingArgs.timeout)
select { select {
case <-timer.C: case <-timer.C:
@ -132,10 +145,10 @@ func runPing(ctx context.Context, args []string) error {
if pr.DERPRegionID != 0 { if pr.DERPRegionID != 0 {
via = fmt.Sprintf("DERP(%s)", pr.DERPRegionCode) via = fmt.Sprintf("DERP(%s)", pr.DERPRegionCode)
} }
if pingArgs.tsmp { if via == "" {
// TODO(bradfitz): populate the rest of ipnstate.PingResult for TSMP queries? // TODO(bradfitz): populate the rest of ipnstate.PingResult for TSMP queries?
// For now just say it came via TSMP. // For now just say which protocol it used.
via = "TSMP" via = string(pingType())
} }
anyPong = true anyPong = true
extra := "" extra := ""
@ -143,7 +156,7 @@ func runPing(ctx context.Context, args []string) error {
extra = fmt.Sprintf(", %d", pr.PeerAPIPort) extra = fmt.Sprintf(", %d", pr.PeerAPIPort)
} }
printf("pong from %s (%s%s) via %v in %v\n", pr.NodeName, pr.NodeIP, extra, via, latency) printf("pong from %s (%s%s) via %v in %v\n", pr.NodeName, pr.NodeIP, extra, via, latency)
if pingArgs.tsmp { if pingArgs.tsmp || pingArgs.icmp {
return nil return nil
} }
if pr.Endpoint != "" && pingArgs.untilDirect { if pr.Endpoint != "" && pingArgs.untilDirect {

@ -126,9 +126,9 @@ type Options struct {
// Pinger is a subset of the wgengine.Engine interface, containing just the Ping method. // Pinger is a subset of the wgengine.Engine interface, containing just the Ping method.
type Pinger interface { type Pinger interface {
// Ping is a request to start a discovery or TSMP ping with the peer handling // Ping is a request to start a ping with the peer handling the given IP and
// the given IP and then call cb with its ping latency & method. // then call cb with its ping latency & method.
Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.PingResult)) Ping(ip netaddr.IP, pingType tailcfg.PingType, cb func(*ipnstate.PingResult))
} }
type Decompressor interface { type Decompressor interface {
@ -1197,11 +1197,10 @@ func answerPing(logf logger.Logf, c *http.Client, pr *tailcfg.PingRequest, pinge
return return
} }
for _, t := range strings.Split(pr.Types, ",") { for _, t := range strings.Split(pr.Types, ",") {
switch t { switch pt := tailcfg.PingType(t); pt {
case "TSMP", "disco": case tailcfg.PingTSMP, tailcfg.PingDisco, tailcfg.PingICMP:
go doPingerPing(logf, c, pr, pinger, t) go doPingerPing(logf, c, pr, pinger, pt)
// TODO(tailscale/corp#754) // TODO(tailscale/corp#754)
// case "host":
// case "peerapi": // case "peerapi":
default: default:
logf("unsupported ping request type: %q", t) logf("unsupported ping request type: %q", t)
@ -1402,13 +1401,13 @@ func (c *Direct) DoNoiseRequest(req *http.Request) (*http.Response, error) {
// doPingerPing sends a Ping to pr.IP using pinger, and sends an http request back to // doPingerPing sends a Ping to pr.IP using pinger, and sends an http request back to
// pr.URL with ping response data. // pr.URL with ping response data.
func doPingerPing(logf logger.Logf, c *http.Client, pr *tailcfg.PingRequest, pinger Pinger, pingType string) { func doPingerPing(logf logger.Logf, c *http.Client, pr *tailcfg.PingRequest, pinger Pinger, pingType tailcfg.PingType) {
if pr.URL == "" || pr.IP.IsZero() || pinger == nil { if pr.URL == "" || pr.IP.IsZero() || pinger == nil {
logf("invalid ping request: missing url, ip or pinger") logf("invalid ping request: missing url, ip or pinger")
return return
} }
start := time.Now() start := time.Now()
pinger.Ping(pr.IP, pingType == "TSMP", func(res *ipnstate.PingResult) { pinger.Ping(pr.IP, pingType, func(res *ipnstate.PingResult) {
// Currently does not check for error since we just return if it fails. // Currently does not check for error since we just return if it fails.
postPingResult(start, logf, c, pr, res.ToPingResponse(pingType)) postPingResult(start, logf, c, pr, res.ToPingResponse(pingType))
}) })

@ -248,5 +248,5 @@ type Backend interface {
// Ping attempts to start connecting to the given IP and sends a Notify // Ping attempts to start connecting to the given IP and sends a Notify
// with its PingResult. If the host is down, there might never // with its PingResult. If the host is down, there might never
// be a PingResult sent. The cmd/tailscale CLI client adds a timeout. // be a PingResult sent. The cmd/tailscale CLI client adds a timeout.
Ping(ip string, useTSMP bool) Ping(ip string, pingType tailcfg.PingType)
} }

@ -100,7 +100,7 @@ func (b *FakeBackend) RequestEngineStatus() {
} }
} }
func (b *FakeBackend) Ping(ip string, useTSMP bool) { func (b *FakeBackend) Ping(ip string, pingType tailcfg.PingType) {
if b.notify != nil { if b.notify != nil {
b.notify(Notify{PingResult: &ipnstate.PingResult{}}) b.notify(Notify{PingResult: &ipnstate.PingResult{}})
} }

@ -1699,13 +1699,13 @@ func (b *LocalBackend) StartLoginInteractive() {
} }
} }
func (b *LocalBackend) Ping(ipStr string, useTSMP bool) { func (b *LocalBackend) Ping(ipStr string, pingType tailcfg.PingType) {
ip, err := netaddr.ParseIP(ipStr) ip, err := netaddr.ParseIP(ipStr)
if err != nil { if err != nil {
b.logf("ignoring Ping request to invalid IP %q", ipStr) b.logf("ignoring Ping request to invalid IP %q", ipStr)
return return
} }
b.e.Ping(ip, useTSMP, func(pr *ipnstate.PingResult) { b.e.Ping(ip, pingType, func(pr *ipnstate.PingResult) {
b.send(ipn.Notify{PingResult: pr}) b.send(ipn.Notify{PingResult: pr})
}) })
} }

@ -515,7 +515,7 @@ type PingResult struct {
// TODO(bradfitz): details like whether port mapping was used on either side? (Once supported) // TODO(bradfitz): details like whether port mapping was used on either side? (Once supported)
} }
func (pr *PingResult) ToPingResponse(pingType string) *tailcfg.PingResponse { func (pr *PingResult) ToPingResponse(pingType tailcfg.PingType) *tailcfg.PingResponse {
return &tailcfg.PingResponse{ return &tailcfg.PingResponse{
Type: pingType, Type: pingType,
IP: pr.IP, IP: pr.IP,

@ -52,8 +52,8 @@ type SetPrefsArgs struct {
} }
type PingArgs struct { type PingArgs struct {
IP string IP string
UseTSMP bool Type tailcfg.PingType
} }
// Command is a command message that is JSON encoded and sent by a // Command is a command message that is JSON encoded and sent by a
@ -171,7 +171,7 @@ func (bs *BackendServer) GotCommand(ctx context.Context, cmd *Command) error {
bs.b.RequestEngineStatus() bs.b.RequestEngineStatus()
return nil return nil
} else if c := cmd.Ping; c != nil { } else if c := cmd.Ping; c != nil {
bs.b.Ping(c.IP, c.UseTSMP) bs.b.Ping(c.IP, tailcfg.PingType(c.Type))
return nil return nil
} }
@ -311,10 +311,10 @@ func (bc *BackendClient) RequestStatus() {
bc.send(Command{AllowVersionSkew: true, RequestStatus: &NoArgs{}}) bc.send(Command{AllowVersionSkew: true, RequestStatus: &NoArgs{}})
} }
func (bc *BackendClient) Ping(ip string, useTSMP bool) { func (bc *BackendClient) Ping(ip string, pingType tailcfg.PingType) {
bc.send(Command{Ping: &PingArgs{ bc.send(Command{Ping: &PingArgs{
IP: ip, IP: ip,
UseTSMP: useTSMP, Type: pingType,
}}) }})
} }

@ -0,0 +1,29 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package packet
import (
crand "crypto/rand"
"encoding/binary"
)
// ICMPEchoPayload generates a new random ID/Sequence pair, and returns a uint32
// derived from them, along with the id, sequence and given payload in a buffer.
// It returns an error if the random source could not be read.
func ICMPEchoPayload(payload []byte) (idSeq uint32, buf []byte) {
buf = make([]byte, len(payload)+4)
// make a completely random id/sequence combo, which is very unlikely to
// collide with a running ping sequence on the host system. Errors are
// ignored, that would result in collisions, but errors reading from the
// random device are rare, and will cause this process universe to soon end.
crand.Read(buf[:4])
idSeq = binary.LittleEndian.Uint32(buf)
copy(buf[4:], payload)
return
}

@ -434,6 +434,29 @@ func (q *Parsed) IsEchoResponse() bool {
} }
} }
// EchoIDSeq extracts the identifier/sequence bytes from an ICMP Echo response,
// and returns them as a uint32, used to lookup internally routed ICMP echo
// responses. This function is intentionally lightweight as it is called on
// every incoming ICMP packet.
func (q *Parsed) EchoIDSeq() uint32 {
switch q.IPProto {
case ipproto.ICMPv4:
offset := ip4HeaderLength + icmp4HeaderLength
if len(q.b) < offset+4 {
return 0
}
return binary.LittleEndian.Uint32(q.b[offset:])
case ipproto.ICMPv6:
offset := ip6HeaderLength + icmp6HeaderLength
if len(q.b) < offset+4 {
return 0
}
return binary.LittleEndian.Uint32(q.b[offset:])
default:
return 0
}
}
func Hexdump(b []byte) string { func Hexdump(b []byte) string {
out := new(strings.Builder) out := new(strings.Builder)
for i := 0; i < len(b); i += 16 { for i := 0; i < len(b); i += 16 {

@ -151,6 +151,11 @@ type Wrapper struct {
// OnTSMPPongReceived, if non-nil, is called whenever a TSMP pong arrives. // OnTSMPPongReceived, if non-nil, is called whenever a TSMP pong arrives.
OnTSMPPongReceived func(packet.TSMPPongReply) OnTSMPPongReceived func(packet.TSMPPongReply)
// OnICMPEchoResponseReceived, if non-nil, is called whenever a ICMP echo response
// arrives. If the packet is to be handled internally this returns true,
// false otherwise.
OnICMPEchoResponseReceived func(*packet.Parsed) bool
// PeerAPIPort, if non-nil, returns the peerapi port that's // PeerAPIPort, if non-nil, returns the peerapi port that's
// running for the given IP address. // running for the given IP address.
PeerAPIPort func(netaddr.IP) (port uint16, ok bool) PeerAPIPort func(netaddr.IP) (port uint16, ok bool)
@ -575,6 +580,14 @@ func (t *Wrapper) filterIn(buf []byte) filter.Response {
} }
} }
if p.IsEchoResponse() {
if f := t.OnICMPEchoResponseReceived; f != nil && f(p) {
// Note: this looks dropped in metrics, even though it was
// handled internally.
return filter.DropSilently
}
}
// Issue 1526 workaround: if we see disco packets over // Issue 1526 workaround: if we see disco packets over
// Tailscale from ourselves, then drop them, as that shouldn't // Tailscale from ourselves, then drop them, as that shouldn't
// happen unless a networking stack is confused, as it seems // happen unless a networking stack is confused, as it seems

@ -1217,6 +1217,19 @@ type DNSRecord struct {
Value string Value string
} }
// PingType is a string representing the kind of ping to perform.
type PingType string
const (
// PingDisco performs a ping, without involving IP at either end.
PingDisco PingType = "disco"
// PingTSMP performs a ping, using the IP layer, but avoiding the OS IP stack.
PingTSMP PingType = "TSMP"
// PingICMP performs a ping between two tailscale nodes using ICMP that is
// received by the target systems IP stack.
PingICMP PingType = "ICMP"
)
// PingRequest with no IP and Types is a request to send an HTTP request to prove the // PingRequest with no IP and Types is a request to send an HTTP request to prove the
// long-polling client is still connected. // long-polling client is still connected.
// PingRequest with Types and IP, will send a ping to the IP and send a POST // PingRequest with Types and IP, will send a ping to the IP and send a POST
@ -1234,8 +1247,8 @@ type PingRequest struct {
// For failure cases, the client will log regardless. // For failure cases, the client will log regardless.
Log bool `json:",omitempty"` Log bool `json:",omitempty"`
// Types is the types of ping that is initiated. Can be TSMP, ICMP or disco. // Types is the types of ping that are initiated. Can be any PingType, comma
// Types will be comma separated, such as TSMP,disco. // separated, e.g. "disco,TSMP"
Types string Types string
// IP is the ping target. // IP is the ping target.
@ -1246,7 +1259,7 @@ type PingRequest struct {
// PingResponse provides result information for a TSMP or Disco PingRequest. // PingResponse provides result information for a TSMP or Disco PingRequest.
// Typically populated from an ipnstate.PingResult used in `tailscale ping`. // Typically populated from an ipnstate.PingResult used in `tailscale ping`.
type PingResponse struct { type PingResponse struct {
Type string // ping type, such as TSMP or disco. Type PingType // ping type, such as TSMP or disco.
IP string `json:",omitempty"` // ping destination IP string `json:",omitempty"` // ping destination
NodeIP string `json:",omitempty"` // Tailscale IP of node handling IP (different for subnet routers) NodeIP string `json:",omitempty"` // Tailscale IP of node handling IP (different for subnet routers)

@ -45,6 +45,7 @@ import (
"tailscale.com/types/netmap" "tailscale.com/types/netmap"
"tailscale.com/util/clientmetric" "tailscale.com/util/clientmetric"
"tailscale.com/util/deephash" "tailscale.com/util/deephash"
"tailscale.com/util/mak"
"tailscale.com/version" "tailscale.com/version"
"tailscale.com/wgengine/filter" "tailscale.com/wgengine/filter"
"tailscale.com/wgengine/magicsock" "tailscale.com/wgengine/magicsock"
@ -135,8 +136,15 @@ type userspaceEngine struct {
endpoints []tailcfg.Endpoint endpoints []tailcfg.Endpoint
pendOpen map[flowtrack.Tuple]*pendingOpenFlow // see pendopen.go pendOpen map[flowtrack.Tuple]*pendingOpenFlow // see pendopen.go
networkMapCallbacks map[*someHandle]NetworkMapCallback networkMapCallbacks map[*someHandle]NetworkMapCallback
tsIPByIPPort map[netaddr.IPPort]netaddr.IP // allows registration of IP:ports as belonging to a certain Tailscale IP for whois lookups tsIPByIPPort map[netaddr.IPPort]netaddr.IP // allows registration of IP:ports as belonging to a certain Tailscale IP for whois lookups
pongCallback map[[8]byte]func(packet.TSMPPongReply) // for TSMP pong responses
// pongCallback is the map of response handlers waiting for disco or TSMP
// pong callbacks. The map key is a random slice of bytes.
pongCallback map[[8]byte]func(packet.TSMPPongReply)
// icmpEchoResponseCallback is the map of reponse handlers waiting for ICMP
// echo responses. The map key is a random uint32 that is the little endian
// value of the ICMP identifer and sequence number concatenated.
icmpEchoResponseCallback map[uint32]func()
// Lock ordering: magicsock.Conn.mu, wgLock, then mu. // Lock ordering: magicsock.Conn.mu, wgLock, then mu.
} }
@ -387,6 +395,20 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
} }
} }
e.tundev.OnICMPEchoResponseReceived = func(p *packet.Parsed) bool {
idSeq := p.EchoIDSeq()
e.mu.Lock()
defer e.mu.Unlock()
cb := e.icmpEchoResponseCallback[idSeq]
if cb == nil {
// We didn't swallow it, so let it flow to the host.
return false
}
e.logf("wgengine: got diagnostic ICMP response %02x", idSeq)
go cb()
return true
}
// wgdev takes ownership of tundev, will close it when closed. // wgdev takes ownership of tundev, will close it when closed.
e.logf("Creating wireguard device...") e.logf("Creating wireguard device...")
e.wgdev = wgcfg.NewDevice(e.tundev, e.magicConn.Bind(), e.wgLogger.DeviceLogger) e.wgdev = wgcfg.NewDevice(e.tundev, e.magicConn.Bind(), e.wgLogger.DeviceLogger)
@ -1267,7 +1289,7 @@ func (e *userspaceEngine) UpdateStatus(sb *ipnstate.StatusBuilder) {
e.magicConn.UpdateStatus(sb) e.magicConn.UpdateStatus(sb)
} }
func (e *userspaceEngine) Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.PingResult)) { func (e *userspaceEngine) Ping(ip netaddr.IP, pingType tailcfg.PingType, cb func(*ipnstate.PingResult)) {
res := &ipnstate.PingResult{IP: ip.String()} res := &ipnstate.PingResult{IP: ip.String()}
pip, ok := e.PeerForIP(ip) pip, ok := e.PeerForIP(ip)
if !ok { if !ok {
@ -1284,15 +1306,14 @@ func (e *userspaceEngine) Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.Pi
} }
peer := pip.Node peer := pip.Node
pingType := "disco"
if useTSMP {
pingType = "TSMP"
}
e.logf("ping(%v): sending %v ping to %v %v ...", ip, pingType, peer.Key.ShortString(), peer.ComputedName) e.logf("ping(%v): sending %v ping to %v %v ...", ip, pingType, peer.Key.ShortString(), peer.ComputedName)
if useTSMP { switch pingType {
e.sendTSMPPing(ip, peer, res, cb) case "disco":
} else {
e.magicConn.Ping(peer, res, cb) e.magicConn.Ping(peer, res, cb)
case "TSMP":
e.sendTSMPPing(ip, peer, res, cb)
case "ICMP":
e.sendICMPEchoRequest(ip, peer, res, cb)
} }
} }
@ -1313,6 +1334,55 @@ func (e *userspaceEngine) mySelfIPMatchingFamily(dst netaddr.IP) (src netaddr.IP
return netaddr.IP{}, errors.New("no self address in netmap matching address family") return netaddr.IP{}, errors.New("no self address in netmap matching address family")
} }
func (e *userspaceEngine) sendICMPEchoRequest(destIP netaddr.IP, peer *tailcfg.Node, res *ipnstate.PingResult, cb func(*ipnstate.PingResult)) {
srcIP, err := e.mySelfIPMatchingFamily(destIP)
if err != nil {
res.Err = err.Error()
cb(res)
return
}
var icmph packet.Header
if srcIP.Is4() {
icmph = packet.ICMP4Header{
IP4Header: packet.IP4Header{
IPProto: ipproto.ICMPv4,
Src: srcIP,
Dst: destIP,
},
Type: packet.ICMP4EchoRequest,
Code: packet.ICMP4NoCode,
}
} else {
icmph = packet.ICMP6Header{
IP6Header: packet.IP6Header{
IPProto: ipproto.ICMPv6,
Src: srcIP,
Dst: destIP,
},
Type: packet.ICMP6EchoRequest,
Code: packet.ICMP6NoCode,
}
}
idSeq, payload := packet.ICMPEchoPayload(nil)
expireTimer := time.AfterFunc(10*time.Second, func() {
e.setICMPEchoResponseCallback(idSeq, nil)
})
t0 := time.Now()
e.setICMPEchoResponseCallback(idSeq, func() {
expireTimer.Stop()
d := time.Since(t0)
res.LatencySeconds = d.Seconds()
res.NodeIP = destIP.String()
res.NodeName = peer.ComputedName
cb(res)
})
icmpPing := packet.Generate(icmph, payload)
e.tundev.InjectOutbound(icmpPing)
}
func (e *userspaceEngine) sendTSMPPing(ip netaddr.IP, peer *tailcfg.Node, res *ipnstate.PingResult, cb func(*ipnstate.PingResult)) { func (e *userspaceEngine) sendTSMPPing(ip netaddr.IP, peer *tailcfg.Node, res *ipnstate.PingResult, cb func(*ipnstate.PingResult)) {
srcIP, err := e.mySelfIPMatchingFamily(ip) srcIP, err := e.mySelfIPMatchingFamily(ip)
if err != nil { if err != nil {
@ -1373,6 +1443,16 @@ func (e *userspaceEngine) setTSMPPongCallback(data [8]byte, cb func(packet.TSMPP
} }
} }
func (e *userspaceEngine) setICMPEchoResponseCallback(idSeq uint32, cb func()) {
e.mu.Lock()
defer e.mu.Unlock()
if cb == nil {
delete(e.icmpEchoResponseCallback, idSeq)
} else {
mak.Set(&e.icmpEchoResponseCallback, idSeq, cb)
}
}
func (e *userspaceEngine) RegisterIPPortIdentity(ipport netaddr.IPPort, tsIP netaddr.IP) { func (e *userspaceEngine) RegisterIPPortIdentity(ipport netaddr.IPPort, tsIP netaddr.IP) {
e.mu.Lock() e.mu.Lock()
defer e.mu.Unlock() defer e.mu.Unlock()

@ -117,8 +117,8 @@ func (e *watchdogEngine) DiscoPublicKey() (k key.DiscoPublic) {
e.watchdog("DiscoPublicKey", func() { k = e.wrap.DiscoPublicKey() }) e.watchdog("DiscoPublicKey", func() { k = e.wrap.DiscoPublicKey() })
return k return k
} }
func (e *watchdogEngine) Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.PingResult)) { func (e *watchdogEngine) Ping(ip netaddr.IP, pingType tailcfg.PingType, cb func(*ipnstate.PingResult)) {
e.watchdog("Ping", func() { e.wrap.Ping(ip, useTSMP, cb) }) e.watchdog("Ping", func() { e.wrap.Ping(ip, pingType, cb) })
} }
func (e *watchdogEngine) RegisterIPPortIdentity(ipp netaddr.IPPort, tsIP netaddr.IP) { func (e *watchdogEngine) RegisterIPPortIdentity(ipp netaddr.IPPort, tsIP netaddr.IP) {
e.watchdog("RegisterIPPortIdentity", func() { e.wrap.RegisterIPPortIdentity(ipp, tsIP) }) e.watchdog("RegisterIPPortIdentity", func() { e.wrap.RegisterIPPortIdentity(ipp, tsIP) })

@ -154,9 +154,9 @@ type Engine interface {
// status builder. // status builder.
UpdateStatus(*ipnstate.StatusBuilder) UpdateStatus(*ipnstate.StatusBuilder)
// Ping is a request to start a discovery ping with the peer handling // Ping is a request to start a ping with the peer handling the given IP and
// the given IP and then call cb with its ping latency & method. // then call cb with its ping latency & method.
Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.PingResult)) Ping(ip netaddr.IP, pingType tailcfg.PingType, cb func(*ipnstate.PingResult))
// RegisterIPPortIdentity registers a given node (identified by its // RegisterIPPortIdentity registers a given node (identified by its
// Tailscale IP) as temporarily having the given IP:port for whois lookups. // Tailscale IP) as temporarily having the given IP:port for whois lookups.

Loading…
Cancel
Save