mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
wgengine/magicsock: send, use endpoints in CallMeMaybe messages
Fixes #1172 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
ec77b80c53
commit
f4f236c777
@ -124,13 +124,21 @@ func parsePing(ver uint8, p []byte) (m *Ping, err error) {
|
|||||||
// happy with its path. But usually it will.
|
// happy with its path. But usually it will.
|
||||||
type CallMeMaybe struct {
|
type CallMeMaybe struct {
|
||||||
// MyNumber is what the peer believes its endpoints are.
|
// MyNumber is what the peer believes its endpoints are.
|
||||||
// Tailscale clients before 1.4 did not populate this
|
//
|
||||||
// so these values should merely augment whetever the control
|
// Prior to Tailscale 1.4, the endpoints were exchanged purely
|
||||||
// server sends. But because the client could've been idle
|
// between nodes and the control server.
|
||||||
// before it reached out to us, the control plane might
|
//
|
||||||
// have stale info and these endpoints in CallMeMaybe
|
// Starting with Tailscale 1.4, clients advertise their endpoints.
|
||||||
// might contain the just-obtained-milliseconds-ago
|
// Older clients won't use this, but newer clients should
|
||||||
// STUN endpoint.
|
// use any endpoints in here that aren't included from control.
|
||||||
|
//
|
||||||
|
// Control might have sent stale endpoints if the client was idle
|
||||||
|
// before contacting us. In that case, the client likely did a STUN
|
||||||
|
// request immediately before sending the CallMeMaybe to recreate
|
||||||
|
// their NAT port mapping, and that new good endpoint is included
|
||||||
|
// in this field, but might not yet be in control's endpoints.
|
||||||
|
// (And in the future, control will stop distributing endpoints
|
||||||
|
// when clients are suitably new.)
|
||||||
MyNumber []netaddr.IPPort
|
MyNumber []netaddr.IPPort
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1851,7 +1851,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool {
|
|||||||
}
|
}
|
||||||
if de != nil {
|
if de != nil {
|
||||||
c.logf("magicsock: disco: %v<-%v (%v, %v) got call-me-maybe", c.discoShort, de.discoShort, de.publicKey.ShortString(), derpStr(src.String()))
|
c.logf("magicsock: disco: %v<-%v (%v, %v) got call-me-maybe", c.discoShort, de.discoShort, de.publicKey.ShortString(), derpStr(src.String()))
|
||||||
go de.handleCallMeMaybe()
|
go de.handleCallMeMaybe(dm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1882,6 +1882,29 @@ func (c *Conn) handlePingLocked(dm *disco.Ping, de *discoEndpoint, src netaddr.I
|
|||||||
}, discoVerboseLog)
|
}, discoVerboseLog)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enqueueCallMeMaybe schedules a send of disco.CallMeMaybe to de via derpAddr
|
||||||
|
// once we know that our STUN endpoint is fresh.
|
||||||
|
//
|
||||||
|
// derpAddr is de.derpAddr at the time of send. It's assumed the peer won't be
|
||||||
|
// flipping primary DERPs in the 0-30ms it takes to confirm our STUN endpoint.
|
||||||
|
// If they do, traffic will just go over DERP for a bit longer until the next
|
||||||
|
// discovery round.
|
||||||
|
func (c *Conn) enqueueCallMeMaybe(derpAddr netaddr.IPPort, de *discoEndpoint) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
// TODO(bradfitz): do a fast/lite re-STUN if our STUN info is too old
|
||||||
|
// Currently there's no "queue" despite the method name. There will be.
|
||||||
|
|
||||||
|
eps := make([]netaddr.IPPort, 0, len(c.lastEndpoints))
|
||||||
|
for _, ep := range c.lastEndpoints {
|
||||||
|
if ipp, err := netaddr.ParseIPPort(ep); err == nil {
|
||||||
|
eps = append(eps, ipp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go de.sendDiscoMessage(derpAddr, &disco.CallMeMaybe{MyNumber: eps}, discoLog)
|
||||||
|
}
|
||||||
|
|
||||||
// setAddrToDiscoLocked records that newk is at src.
|
// setAddrToDiscoLocked records that newk is at src.
|
||||||
//
|
//
|
||||||
// c.mu must be held.
|
// c.mu must be held.
|
||||||
@ -2841,6 +2864,7 @@ type discoEndpoint struct {
|
|||||||
trustBestAddrUntil time.Time // time when bestAddr expires
|
trustBestAddrUntil time.Time // time when bestAddr expires
|
||||||
sentPing map[stun.TxID]sentPing
|
sentPing map[stun.TxID]sentPing
|
||||||
endpointState map[netaddr.IPPort]*endpointState
|
endpointState map[netaddr.IPPort]*endpointState
|
||||||
|
isCallMeMaybeEP map[netaddr.IPPort]bool
|
||||||
|
|
||||||
pendingCLIPings []pendingCLIPing // any outstanding "tailscale ping" commands running
|
pendingCLIPings []pendingCLIPing // any outstanding "tailscale ping" commands running
|
||||||
}
|
}
|
||||||
@ -2905,6 +2929,10 @@ type endpointState struct {
|
|||||||
// updated and use it to discard old candidates.
|
// updated and use it to discard old candidates.
|
||||||
lastGotPing time.Time
|
lastGotPing time.Time
|
||||||
|
|
||||||
|
// callMeMaybeTime, if non-zero, is the time this endpoint
|
||||||
|
// was advertised last via a call-me-maybe disco message.
|
||||||
|
callMeMaybeTime time.Time
|
||||||
|
|
||||||
recentPongs []pongReply // ring buffer up to pongHistoryCount entries
|
recentPongs []pongReply // ring buffer up to pongHistoryCount entries
|
||||||
recentPong uint16 // index into recentPongs of most recent; older before, wrapped
|
recentPong uint16 // index into recentPongs of most recent; older before, wrapped
|
||||||
|
|
||||||
@ -2918,11 +2946,13 @@ type endpointState struct {
|
|||||||
// shouldDeleteLocked reports whether we should delete this endpoint.
|
// shouldDeleteLocked reports whether we should delete this endpoint.
|
||||||
func (st *endpointState) shouldDeleteLocked() bool {
|
func (st *endpointState) shouldDeleteLocked() bool {
|
||||||
switch {
|
switch {
|
||||||
|
case !st.callMeMaybeTime.IsZero():
|
||||||
|
return false
|
||||||
case st.lastGotPing.IsZero():
|
case st.lastGotPing.IsZero():
|
||||||
// This was an endpoint from the network map. Is it still in the network map?
|
// This was an endpoint from the network map. Is it still in the network map?
|
||||||
return st.index == indexSentinelDeleted
|
return st.index == indexSentinelDeleted
|
||||||
default:
|
default:
|
||||||
// Thiw was an endpoint discovered at runtime.
|
// This was an endpoint discovered at runtime.
|
||||||
return time.Since(st.lastGotPing) > sessionActiveTimeout
|
return time.Since(st.lastGotPing) > sessionActiveTimeout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3236,13 +3266,12 @@ func (de *discoEndpoint) sendPingsLocked(now time.Time, sendCallMeMaybe bool) {
|
|||||||
}
|
}
|
||||||
derpAddr := de.derpAddr
|
derpAddr := de.derpAddr
|
||||||
if sentAny && sendCallMeMaybe && !derpAddr.IsZero() {
|
if sentAny && sendCallMeMaybe && !derpAddr.IsZero() {
|
||||||
// In just a bit of a time (for goroutines above to schedule and run),
|
// Have our magicsock.Conn figure out its STUN endpoint (if
|
||||||
// send a message to peer via DERP informing them that we've sent
|
// it doesn't know already) and then send a CallMeMaybe
|
||||||
// so our firewall ports are probably open and now would be a good time
|
// message to our peer via DERP informing them that we've
|
||||||
// for them to connect.
|
// sent so our firewall ports are probably open and now
|
||||||
time.AfterFunc(5*time.Millisecond, func() {
|
// would be a good time for them to connect.
|
||||||
de.sendDiscoMessage(derpAddr, &disco.CallMeMaybe{}, discoLog)
|
go de.c.enqueueCallMeMaybe(derpAddr, de)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3424,10 +3453,34 @@ func (st *endpointState) addPongReplyLocked(r pongReply) {
|
|||||||
// DERP. The contract for use of this message is that the peer has
|
// DERP. The contract for use of this message is that the peer has
|
||||||
// already sent to us via UDP, so their stateful firewall should be
|
// already sent to us via UDP, so their stateful firewall should be
|
||||||
// open. Now we can Ping back and make it through.
|
// open. Now we can Ping back and make it through.
|
||||||
func (de *discoEndpoint) handleCallMeMaybe() {
|
func (de *discoEndpoint) handleCallMeMaybe(m *disco.CallMeMaybe) {
|
||||||
de.mu.Lock()
|
de.mu.Lock()
|
||||||
defer de.mu.Unlock()
|
defer de.mu.Unlock()
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
for ep := range de.isCallMeMaybeEP {
|
||||||
|
de.isCallMeMaybeEP[ep] = false // mark for deletion
|
||||||
|
}
|
||||||
|
if de.isCallMeMaybeEP == nil {
|
||||||
|
de.isCallMeMaybeEP = map[netaddr.IPPort]bool{}
|
||||||
|
}
|
||||||
|
for _, ep := range m.MyNumber {
|
||||||
|
de.isCallMeMaybeEP[ep] = true
|
||||||
|
if es, ok := de.endpointState[ep]; ok {
|
||||||
|
es.callMeMaybeTime = now
|
||||||
|
} else {
|
||||||
|
de.endpointState[ep] = &endpointState{callMeMaybeTime: now}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Delete any prior CalllMeMaybe endpoints that weren't included
|
||||||
|
// in this message.
|
||||||
|
for ep, want := range de.isCallMeMaybeEP {
|
||||||
|
if !want {
|
||||||
|
delete(de.isCallMeMaybeEP, ep)
|
||||||
|
de.deleteEndpointLocked(ep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Zero out all the lastPing times to force sendPingsLocked to send new ones,
|
// Zero out all the lastPing times to force sendPingsLocked to send new ones,
|
||||||
// even if it's been less than 5 seconds ago.
|
// even if it's been less than 5 seconds ago.
|
||||||
for _, st := range de.endpointState {
|
for _, st := range de.endpointState {
|
||||||
|
Loading…
Reference in New Issue
Block a user