|
|
@@ -45,6 +45,7 @@ import (
|
|
|
"tailscale.com/types/netmap"
|
|
|
"tailscale.com/util/clientmetric"
|
|
|
"tailscale.com/util/deephash"
|
|
|
+ "tailscale.com/util/mak"
|
|
|
"tailscale.com/version"
|
|
|
"tailscale.com/wgengine/filter"
|
|
|
"tailscale.com/wgengine/magicsock"
|
|
|
@@ -135,8 +136,15 @@ type userspaceEngine struct {
|
|
|
endpoints []tailcfg.Endpoint
|
|
|
pendOpen map[flowtrack.Tuple]*pendingOpenFlow // see pendopen.go
|
|
|
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
|
|
|
- pongCallback map[[8]byte]func(packet.TSMPPongReply) // for TSMP pong responses
|
|
|
+ tsIPByIPPort map[netaddr.IPPort]netaddr.IP // allows registration of IP:ports as belonging to a certain Tailscale IP for whois lookups
|
|
|
+
|
|
|
+ // 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.
|
|
|
}
|
|
|
@@ -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.
|
|
|
e.logf("Creating wireguard device...")
|
|
|
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)
|
|
|
}
|
|
|
|
|
|
-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()}
|
|
|
pip, ok := e.PeerForIP(ip)
|
|
|
if !ok {
|
|
|
@@ -1284,15 +1306,14 @@ func (e *userspaceEngine) Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.Pi
|
|
|
}
|
|
|
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)
|
|
|
- if useTSMP {
|
|
|
- e.sendTSMPPing(ip, peer, res, cb)
|
|
|
- } else {
|
|
|
+ switch pingType {
|
|
|
+ case "disco":
|
|
|
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")
|
|
|
}
|
|
|
|
|
|
+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)) {
|
|
|
srcIP, err := e.mySelfIPMatchingFamily(ip)
|
|
|
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) {
|
|
|
e.mu.Lock()
|
|
|
defer e.mu.Unlock()
|