@ -35,6 +35,16 @@ import (
"tailscale.com/util/ringbuffer"
)
var mtuProbePingSizesV4 [ ] int
var mtuProbePingSizesV6 [ ] int
func init ( ) {
for _ , m := range tstun . WireMTUsToProbe {
mtuProbePingSizesV4 = append ( mtuProbePingSizesV4 , pktLenToPingSize ( m , false ) )
mtuProbePingSizesV6 = append ( mtuProbePingSizesV6 , pktLenToPingSize ( m , true ) )
}
}
// endpoint is a wireguard/conn.Endpoint. In wireguard-go and kernel WireGuard
// there is only one endpoint for a peer, but in Tailscale we distribute a
// number of possible endpoints for a peer which would include the all the
@ -374,7 +384,7 @@ func (de *endpoint) addrForPingSizeLocked(now mono.Time, size int) (udpAddr, der
udpAddr = de . bestAddr . AddrPort
pathMTU := de . bestAddr . wireMTU
requestedMTU := pingSizeToPktLen ( size , udpAddr . Addr ( ) )
requestedMTU := pingSizeToPktLen ( size , udpAddr . Addr ( ) .Is6 ( ) )
mtuOk := requestedMTU <= pathMTU
if udpAddr . IsValid ( ) && mtuOk {
@ -666,21 +676,41 @@ func (de *endpoint) startDiscoPingLocked(ep netip.AddrPort, now mono.Time, purpo
st . lastPing = now
}
txid := stun . NewTxID ( )
de . sentPing [ txid ] = sentPing {
to : ep ,
at : now ,
timer : time . AfterFunc ( pingTimeoutDuration , func ( ) { de . discoPingTimeout ( txid ) } ) ,
purpose : purpose ,
res : res ,
cb : cb ,
// If we are doing a discovery ping or a CLI ping with no specified size
// to a non DERP address, then probe the MTU. Otherwise just send the
// one specified ping.
// Default to sending a single ping of the specified size
sizes := [ ] int { size }
if de . c . PeerMTUEnabled ( ) {
isDerp := ep . Addr ( ) == tailcfg . DerpMagicIPAddr
if ! isDerp && ( ( purpose == pingDiscovery ) || ( purpose == pingCLI && size == 0 ) ) {
de . c . dlogf ( "[v1] magicsock: starting MTU probe" )
sizes = mtuProbePingSizesV4
if ep . Addr ( ) . Is6 ( ) {
sizes = mtuProbePingSizesV6
}
}
}
logLevel := discoLog
if purpose == pingHeartbeat {
logLevel = discoVerboseLog
}
go de . sendDiscoPing ( ep , epDisco . key , txid , size , logLevel )
for _ , s := range sizes {
txid := stun . NewTxID ( )
de . sentPing [ txid ] = sentPing {
to : ep ,
at : now ,
timer : time . AfterFunc ( pingTimeoutDuration , func ( ) { de . discoPingTimeout ( txid ) } ) ,
purpose : purpose ,
res : res ,
cb : cb ,
size : s ,
}
go de . sendDiscoPing ( ep , epDisco . key , txid , s , logLevel )
}
}
// sendDiscoPingsLocked starts pinging all of ep's endpoints.
@ -982,13 +1012,13 @@ func (de *endpoint) noteConnectivityChange() {
// pingSizeToPktLen calculates the minimum path MTU that would permit
// a disco ping message of length size to reach its target at
// addr. size is the length of the entire disco message including
// disco headers. If size is zero, assume it is the default MTU.
func pingSizeToPktLen ( size int , addr netip . Addr ) tstun . WireMTU {
// disco headers. If size is zero, assume it is the safe wire MTU.
func pingSizeToPktLen ( size int , is6 bool ) tstun . WireMTU {
if size == 0 {
return tstun . Default WireMTU( )
return tstun . Safe WireMTU( )
}
headerLen := ipv4 . HeaderLen
if addr. Is6 ( ) {
if is6 {
headerLen = ipv6 . HeaderLen
}
headerLen += 8 // UDP header length
@ -999,12 +1029,12 @@ func pingSizeToPktLen(size int, addr netip.Addr) tstun.WireMTU {
// create a disco ping message whose on-the-wire length is exactly mtu
// bytes long. If mtu is zero or less than the minimum ping size, then
// no MTU probe is desired and return zero for an unpadded ping.
func pktLenToPingSize ( mtu tstun . WireMTU , addr netip . Addr ) int {
func pktLenToPingSize ( mtu tstun . WireMTU , is6 bool ) int {
if mtu == 0 {
return 0
}
headerLen := ipv4 . HeaderLen
if addr. Is6 ( ) {
if is6 {
headerLen = ipv6 . HeaderLen
}
headerLen += 8 // UDP header length
@ -1053,7 +1083,7 @@ func (de *endpoint) handlePongConnLocked(m *disco.Pong, di *discoInfo, src netip
}
if sp . purpose != pingHeartbeat {
de . c . dlogf ( "[v1] magicsock: disco: %v<-%v (%v, %v) got pong tx=%x latency=%v pktlen=%v pong.src=%v%v" , de . c . discoShort , de . discoShort ( ) , de . publicKey . ShortString ( ) , src , m . TxID [ : 6 ] , latency . Round ( time . Millisecond ) , pingSizeToPktLen ( sp . size , sp . to . Addr ( ) ), m . Src , logger . ArgWriter ( func ( bw * bufio . Writer ) {
de . c . dlogf ( "[v1] magicsock: disco: %v<-%v (%v, %v) got pong tx=%x latency=%v pktlen=%v pong.src=%v%v" , de . c . discoShort , de . discoShort ( ) , de . publicKey . ShortString ( ) , src , m . TxID [ : 6 ] , latency . Round ( time . Millisecond ) , pingSizeToPktLen ( sp . size , sp . to . Addr ( ) .Is6 ( ) ), m . Src , logger . ArgWriter ( func ( bw * bufio . Writer ) {
if sp . to != src {
fmt . Fprintf ( bw , " ping.to=%v" , sp . to )
}
@ -1071,9 +1101,9 @@ func (de *endpoint) handlePongConnLocked(m *disco.Pong, di *discoInfo, src netip
// Promote this pong response to our current best address if it's lower latency.
// TODO(bradfitz): decide how latency vs. preference order affects decision
if ! isDerp {
thisPong := addrQuality { sp . to , latency , pingSizeToPktLen ( sp . size , sp . to . Addr ( ) ) }
thisPong := addrQuality { sp . to , latency , tstun . WireMTU ( pingSizeToPktLen ( sp . size , sp . to . Addr ( ) . Is6 ( ) ) ) }
if betterAddr ( thisPong , de . bestAddr ) {
de . c . logf ( "magicsock: disco: node %v %v now using %v mtu %v ", de . publicKey . ShortString ( ) , de . discoShort ( ) , sp . to , thisPong . wireMTU )
de . c . logf ( "magicsock: disco: node %v %v now using %v mtu =%v tx=%x ", de . publicKey . ShortString ( ) , de . discoShort ( ) , sp . to , thisPong . wireMTU , m . TxID [ : 6 ] )
de . debugUpdates . Add ( EndpointChange {
When : time . Now ( ) ,
What : "handlePingLocked-bestAddr-update" ,