mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-13 22:47:30 +00:00
net/packet, wgengine/{filter,tstun}: add TSMP ping
Fixes #1467 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:

committed by
Brad Fitzpatrick

parent
4b77eca2de
commit
2384c112c9
@@ -423,6 +423,8 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) {
|
||||
if f.matches6.match(q) {
|
||||
return Accept, "ok"
|
||||
}
|
||||
case ipproto.TSMP:
|
||||
return Accept, "tsmp ok"
|
||||
default:
|
||||
return Drop, "Unknown proto"
|
||||
}
|
||||
|
@@ -109,6 +109,9 @@ type TUN struct {
|
||||
// PostFilterOut is the outbound filter function that runs after the main filter.
|
||||
PostFilterOut FilterFunc
|
||||
|
||||
// OnTSMPPongReceived, if non-nil, is called whenever a TSMP pong arrives.
|
||||
OnTSMPPongReceived func(data [8]byte)
|
||||
|
||||
// disableFilter disables all filtering when set. This should only be used in tests.
|
||||
disableFilter bool
|
||||
}
|
||||
@@ -323,6 +326,18 @@ func (t *TUN) filterIn(buf []byte) filter.Response {
|
||||
defer parsedPacketPool.Put(p)
|
||||
p.Decode(buf)
|
||||
|
||||
if p.IPProto == ipproto.TSMP {
|
||||
if pingReq, ok := p.AsTSMPPing(); ok {
|
||||
t.noteActivity()
|
||||
t.injectOutboundPong(p, pingReq)
|
||||
return filter.DropSilently
|
||||
} else if data, ok := p.AsTSMPPong(); ok {
|
||||
if f := t.OnTSMPPongReceived; f != nil {
|
||||
f(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if t.PreFilterIn != nil {
|
||||
if res := t.PreFilterIn(p, t); res.IsDrop() {
|
||||
return res
|
||||
@@ -440,6 +455,26 @@ func (t *TUN) InjectInboundCopy(packet []byte) error {
|
||||
return t.InjectInboundDirect(buf, PacketStartOffset)
|
||||
}
|
||||
|
||||
func (t *TUN) injectOutboundPong(pp *packet.Parsed, req packet.TSMPPingRequest) {
|
||||
pong := packet.TSMPPongReply{
|
||||
Data: req.Data,
|
||||
}
|
||||
switch pp.IPVersion {
|
||||
case 4:
|
||||
h4 := pp.IP4Header()
|
||||
h4.ToResponse()
|
||||
pong.IPHeader = h4
|
||||
case 6:
|
||||
h6 := pp.IP6Header()
|
||||
h6.ToResponse()
|
||||
pong.IPHeader = h6
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
t.InjectOutbound(packet.Generate(pong, nil))
|
||||
}
|
||||
|
||||
// InjectOutbound makes the TUN device behave as if a packet
|
||||
// with the given contents was sent to the network.
|
||||
// It does not block, but takes ownership of the packet.
|
||||
|
@@ -8,6 +8,7 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
crand "crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -128,6 +129,7 @@ type userspaceEngine struct {
|
||||
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() // for TSMP pong responses
|
||||
|
||||
// Lock ordering: magicsock.Conn.mu, wgLock, then mu.
|
||||
}
|
||||
@@ -351,6 +353,16 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_
|
||||
SkipBindUpdate: true,
|
||||
}
|
||||
|
||||
e.tundev.OnTSMPPongReceived = func(data [8]byte) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
cb := e.pongCallback[data]
|
||||
e.logf("wgengine: got TSMP pong %02x; cb=%v", data, cb != nil)
|
||||
if cb != nil {
|
||||
go cb()
|
||||
}
|
||||
}
|
||||
|
||||
// wgdev takes ownership of tundev, will close it when closed.
|
||||
e.logf("Creating wireguard device...")
|
||||
e.wgdev = device.NewDevice(e.tundev, opts)
|
||||
@@ -1342,7 +1354,7 @@ func (e *userspaceEngine) UpdateStatus(sb *ipnstate.StatusBuilder) {
|
||||
e.magicConn.UpdateStatus(sb)
|
||||
}
|
||||
|
||||
func (e *userspaceEngine) Ping(ip netaddr.IP, cb func(*ipnstate.PingResult)) {
|
||||
func (e *userspaceEngine) Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.PingResult)) {
|
||||
res := &ipnstate.PingResult{IP: ip.String()}
|
||||
peer, err := e.peerForIP(ip)
|
||||
if err != nil {
|
||||
@@ -1357,8 +1369,92 @@ func (e *userspaceEngine) Ping(ip netaddr.IP, cb func(*ipnstate.PingResult)) {
|
||||
cb(res)
|
||||
return
|
||||
}
|
||||
e.logf("ping(%v): sending ping to %v %v ...", ip, peer.Key.ShortString(), peer.ComputedName)
|
||||
e.magicConn.Ping(peer, res, cb)
|
||||
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 {
|
||||
e.magicConn.Ping(peer, res, cb)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *userspaceEngine) mySelfIPMatchingFamily(dst netaddr.IP) (src netaddr.IP, err error) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
if e.netMap == nil {
|
||||
return netaddr.IP{}, errors.New("no netmap")
|
||||
}
|
||||
for _, a := range e.netMap.Addresses {
|
||||
if a.IsSingleIP() && a.IP.BitLen() == dst.BitLen() {
|
||||
return a.IP, nil
|
||||
}
|
||||
}
|
||||
if len(e.netMap.Addresses) == 0 {
|
||||
return netaddr.IP{}, errors.New("no self address in netmap")
|
||||
}
|
||||
return netaddr.IP{}, errors.New("no self address in netmap matching address family")
|
||||
}
|
||||
|
||||
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 {
|
||||
res.Err = err.Error()
|
||||
cb(res)
|
||||
return
|
||||
}
|
||||
var iph packet.Header
|
||||
if srcIP.Is4() {
|
||||
iph = packet.IP4Header{
|
||||
IPProto: ipproto.TSMP,
|
||||
Src: srcIP,
|
||||
Dst: ip,
|
||||
}
|
||||
} else {
|
||||
iph = packet.IP6Header{
|
||||
IPProto: ipproto.TSMP,
|
||||
Src: srcIP,
|
||||
Dst: ip,
|
||||
}
|
||||
}
|
||||
|
||||
var data [8]byte
|
||||
crand.Read(data[:])
|
||||
|
||||
expireTimer := time.AfterFunc(10*time.Second, func() {
|
||||
e.setTSMPPongCallback(data, nil)
|
||||
})
|
||||
t0 := time.Now()
|
||||
e.setTSMPPongCallback(data, func() {
|
||||
expireTimer.Stop()
|
||||
d := time.Since(t0)
|
||||
res.LatencySeconds = d.Seconds()
|
||||
res.NodeIP = ip.String()
|
||||
res.NodeName = peer.ComputedName
|
||||
cb(res)
|
||||
})
|
||||
|
||||
var tsmpPayload [9]byte
|
||||
tsmpPayload[0] = byte(packet.TSMPTypePing)
|
||||
copy(tsmpPayload[1:], data[:])
|
||||
|
||||
tsmpPing := packet.Generate(iph, tsmpPayload[:])
|
||||
e.tundev.InjectOutbound(tsmpPing)
|
||||
}
|
||||
|
||||
func (e *userspaceEngine) setTSMPPongCallback(data [8]byte, cb func()) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
if e.pongCallback == nil {
|
||||
e.pongCallback = map[[8]byte]func(){}
|
||||
}
|
||||
if cb == nil {
|
||||
delete(e.pongCallback, data)
|
||||
} else {
|
||||
e.pongCallback[data] = cb
|
||||
}
|
||||
}
|
||||
|
||||
func (e *userspaceEngine) RegisterIPPortIdentity(ipport netaddr.IPPort, tsIP netaddr.IP) {
|
||||
|
@@ -117,8 +117,8 @@ func (e *watchdogEngine) DiscoPublicKey() (k tailcfg.DiscoKey) {
|
||||
e.watchdog("DiscoPublicKey", func() { k = e.wrap.DiscoPublicKey() })
|
||||
return k
|
||||
}
|
||||
func (e *watchdogEngine) Ping(ip netaddr.IP, cb func(*ipnstate.PingResult)) {
|
||||
e.watchdog("Ping", func() { e.wrap.Ping(ip, cb) })
|
||||
func (e *watchdogEngine) Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.PingResult)) {
|
||||
e.watchdog("Ping", func() { e.wrap.Ping(ip, useTSMP, cb) })
|
||||
}
|
||||
func (e *watchdogEngine) RegisterIPPortIdentity(ipp netaddr.IPPort, tsIP netaddr.IP) {
|
||||
e.watchdog("RegisterIPPortIdentity", func() { e.wrap.RegisterIPPortIdentity(ipp, tsIP) })
|
||||
|
@@ -136,7 +136,7 @@ type Engine interface {
|
||||
|
||||
// Ping is a request to start a discovery ping with the peer handling
|
||||
// the given IP and then call cb with its ping latency & method.
|
||||
Ping(ip netaddr.IP, cb func(*ipnstate.PingResult))
|
||||
Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.PingResult))
|
||||
|
||||
// RegisterIPPortIdentity registers a given node (identified by its
|
||||
// Tailscale IP) as temporarily having the given IP:port for whois lookups.
|
||||
|
Reference in New Issue
Block a user