@ -19,10 +19,12 @@ import (
"sync/atomic"
"sync/atomic"
"time"
"time"
"golang.org/x/crypto/poly1305"
"golang.org/x/exp/maps"
"golang.org/x/exp/maps"
"tailscale.com/disco"
"tailscale.com/disco"
"tailscale.com/ipn/ipnstate"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/stun"
"tailscale.com/net/stun"
"tailscale.com/net/tstun"
"tailscale.com/tailcfg"
"tailscale.com/tailcfg"
"tailscale.com/tstime/mono"
"tailscale.com/tstime/mono"
"tailscale.com/types/key"
"tailscale.com/types/key"
@ -361,7 +363,7 @@ func (de *endpoint) heartbeat() {
udpAddr , _ , _ := de . addrForSendLocked ( now )
udpAddr , _ , _ := de . addrForSendLocked ( now )
if udpAddr . IsValid ( ) {
if udpAddr . IsValid ( ) {
// We have a preferred path. Ping that every 2 seconds.
// We have a preferred path. Ping that every 2 seconds.
de . startDiscoPingLocked ( udpAddr , now , pingHeartbeat )
de . startDiscoPingLocked ( udpAddr , now , pingHeartbeat , 0 )
}
}
if de . wantFullPingLocked ( now ) {
if de . wantFullPingLocked ( now ) {
@ -403,7 +405,7 @@ func (de *endpoint) noteActiveLocked() {
// cliPing starts a ping for the "tailscale ping" command. res is value to call cb with,
// cliPing starts a ping for the "tailscale ping" command. res is value to call cb with,
// already partially filled.
// already partially filled.
func ( de * endpoint ) cliPing ( res * ipnstate . PingResult , cb func ( * ipnstate . PingResult ) ) {
func ( de * endpoint ) cliPing ( res * ipnstate . PingResult , size int , cb func ( * ipnstate . PingResult ) ) {
de . mu . Lock ( )
de . mu . Lock ( )
defer de . mu . Unlock ( )
defer de . mu . Unlock ( )
@ -418,17 +420,17 @@ func (de *endpoint) cliPing(res *ipnstate.PingResult, cb func(*ipnstate.PingResu
now := mono . Now ( )
now := mono . Now ( )
udpAddr , derpAddr , _ := de . addrForSendLocked ( now )
udpAddr , derpAddr , _ := de . addrForSendLocked ( now )
if derpAddr . IsValid ( ) {
if derpAddr . IsValid ( ) {
de . startDiscoPingLocked ( derpAddr , now , pingCLI )
de . startDiscoPingLocked ( derpAddr , now , pingCLI , size )
}
}
if udpAddr . IsValid ( ) && now . Before ( de . trustBestAddrUntil ) {
if udpAddr . IsValid ( ) && now . Before ( de . trustBestAddrUntil ) {
// Already have an active session, so just ping the address we're using.
// Already have an active session, so just ping the address we're using.
// Otherwise "tailscale ping" results to a node on the local network
// Otherwise "tailscale ping" results to a node on the local network
// can look like they're bouncing between, say 10.0.0.0/9 and the peer's
// can look like they're bouncing between, say 10.0.0.0/9 and the peer's
// IPv6 address, both 1ms away, and it's random who replies first.
// IPv6 address, both 1ms away, and it's random who replies first.
de . startDiscoPingLocked ( udpAddr , now , pingCLI )
de . startDiscoPingLocked ( udpAddr , now , pingCLI , size )
} else {
} else {
for ep := range de . endpointState {
for ep := range de . endpointState {
de . startDiscoPingLocked ( ep , now , pingCLI )
de . startDiscoPingLocked ( ep , now , pingCLI , size )
}
}
}
}
de . noteActiveLocked ( )
de . noteActiveLocked ( )
@ -522,17 +524,31 @@ func (de *endpoint) removeSentDiscoPingLocked(txid stun.TxID, sp sentPing) {
delete ( de . sentPing , txid )
delete ( de . sentPing , txid )
}
}
// sendDiscoPing sends a ping with the provided txid to ep using de's discoKey.
// discoPingSize is the size of a complete disco ping packet, without any padding.
const discoPingSize = len ( disco . Magic ) + key . DiscoPublicRawLen + disco . NonceLen +
poly1305 . TagSize + disco . MessageHeaderLen + disco . PingLen
// sendDiscoPing sends a ping with the provided txid to ep using de's discoKey. size
// is the desired disco message size, including all disco headers but excluding IP/UDP
// headers.
//
//
// The caller (startPingLocked) should've already recorded the ping in
// The caller (startPingLocked) should've already recorded the ping in
// sentPing and set up the timer.
// sentPing and set up the timer.
//
//
// The caller should use de.discoKey as the discoKey argument.
// The caller should use de.discoKey as the discoKey argument.
// It is passed in so that sendDiscoPing doesn't need to lock de.mu.
// It is passed in so that sendDiscoPing doesn't need to lock de.mu.
func ( de * endpoint ) sendDiscoPing ( ep netip . AddrPort , discoKey key . DiscoPublic , txid stun . TxID , logLevel discoLogLevel ) {
func ( de * endpoint ) sendDiscoPing ( ep netip . AddrPort , discoKey key . DiscoPublic , txid stun . TxID , size int , logLevel discoLogLevel ) {
padding := 0
if size > int ( tstun . DefaultMTU ( ) ) {
size = int ( tstun . DefaultMTU ( ) )
}
if size - discoPingSize > 0 {
padding = size - discoPingSize
}
sent , _ := de . c . sendDiscoMessage ( ep , de . publicKey , discoKey , & disco . Ping {
sent , _ := de . c . sendDiscoMessage ( ep , de . publicKey , discoKey , & disco . Ping {
TxID : [ 12 ] byte ( txid ) ,
TxID : [ 12 ] byte ( txid ) ,
NodeKey : de . c . publicKeyAtomic . Load ( ) ,
NodeKey : de . c . publicKeyAtomic . Load ( ) ,
Padding : padding ,
} , logLevel )
} , logLevel )
if ! sent {
if ! sent {
de . forgetDiscoPing ( txid )
de . forgetDiscoPing ( txid )
@ -557,7 +573,8 @@ const (
pingCLI
pingCLI
)
)
func ( de * endpoint ) startDiscoPingLocked ( ep netip . AddrPort , now mono . Time , purpose discoPingPurpose ) {
// startDiscoPingLocked sends a disco ping to ep in a separate goroutine.
func ( de * endpoint ) startDiscoPingLocked ( ep netip . AddrPort , now mono . Time , purpose discoPingPurpose , size int ) {
if runtime . GOOS == "js" {
if runtime . GOOS == "js" {
return
return
}
}
@ -587,9 +604,10 @@ func (de *endpoint) startDiscoPingLocked(ep netip.AddrPort, now mono.Time, purpo
if purpose == pingHeartbeat {
if purpose == pingHeartbeat {
logLevel = discoVerboseLog
logLevel = discoVerboseLog
}
}
go de . sendDiscoPing ( ep , epDisco . key , txid , logLevel)
go de . sendDiscoPing ( ep , epDisco . key , txid , size, logLevel)
}
}
// sendDiscoPingsLocked starts pinging all of ep's endpoints.
func ( de * endpoint ) sendDiscoPingsLocked ( now mono . Time , sendCallMeMaybe bool ) {
func ( de * endpoint ) sendDiscoPingsLocked ( now mono . Time , sendCallMeMaybe bool ) {
de . lastFullPing = now
de . lastFullPing = now
var sentAny bool
var sentAny bool
@ -612,7 +630,7 @@ func (de *endpoint) sendDiscoPingsLocked(now mono.Time, sendCallMeMaybe bool) {
de . c . dlogf ( "[v1] magicsock: disco: send, starting discovery for %v (%v)" , de . publicKey . ShortString ( ) , de . discoShort ( ) )
de . c . dlogf ( "[v1] magicsock: disco: send, starting discovery for %v (%v)" , de . publicKey . ShortString ( ) , de . discoShort ( ) )
}
}
de . startDiscoPingLocked ( ep , now , pingDiscovery )
de . startDiscoPingLocked ( ep , now , pingDiscovery , 0 )
}
}
derpAddr := de . derpAddr
derpAddr := de . derpAddr
if sentAny && sendCallMeMaybe && derpAddr . IsValid ( ) {
if sentAny && sendCallMeMaybe && derpAddr . IsValid ( ) {