mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 13:05:46 +00:00
wgengine/magicsock: use disco ping src as a candidate endpoint
Consider: Hard NAT (A) <---> Hard NAT w/ mapped port (B) If A sends a packet to B's mapped port, A can disco ping B directly, with low latency, without DERP. But B couldn't establish a path back to A and needed to use DERP, despite already logging about A's endpoint and adding a mapping to it for other purposes (the wireguard conn.Endpoint lookup also needed it). This adds the tracking to discoEndpoint too so it'll be used for finding a path back. Fixes tailscale/corp#556 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
0512fd89a1
commit
85c3d17b3c
@ -1839,6 +1839,7 @@ func (c *Conn) handlePingLocked(dm *disco.Ping, de *discoEndpoint, src netaddr.I
|
|||||||
|
|
||||||
// Remember this route if not present.
|
// Remember this route if not present.
|
||||||
c.setAddrToDiscoLocked(src, sender, nil)
|
c.setAddrToDiscoLocked(src, sender, nil)
|
||||||
|
de.addCandidateEndpoint(src)
|
||||||
|
|
||||||
ipDst := src
|
ipDst := src
|
||||||
discoDest := sender
|
discoDest := sender
|
||||||
@ -3126,11 +3127,44 @@ type pendingCLIPing struct {
|
|||||||
// a discoEndpoint. (The subject is the discoEndpoint.endpointState
|
// a discoEndpoint. (The subject is the discoEndpoint.endpointState
|
||||||
// map key)
|
// map key)
|
||||||
type endpointState struct {
|
type endpointState struct {
|
||||||
// all fields guarded by discoEndpoint.mu:
|
// all fields guarded by discoEndpoint.mu
|
||||||
lastPing time.Time
|
|
||||||
|
// lastPing is the last (outgoing) ping time.
|
||||||
|
lastPing time.Time
|
||||||
|
|
||||||
|
// lastGotPing, if non-zero, means that this was an endpoint
|
||||||
|
// that we learned about at runtime (from an incoming ping)
|
||||||
|
// and that is not in the network map. If so, we keep the time
|
||||||
|
// updated and use it to discard old candidates.
|
||||||
|
lastGotPing 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
|
||||||
index int16 // index in nodecfg.Node.Endpoints
|
|
||||||
|
index int16 // index in nodecfg.Node.Endpoints; meaningless if lastGotPing non-zero
|
||||||
|
}
|
||||||
|
|
||||||
|
// indexSentinelDeleted is the temporary value that endpointState.index takes while
|
||||||
|
// a discoEndpoint's endpoints are being updated from a new network map.
|
||||||
|
const indexSentinelDeleted = -1
|
||||||
|
|
||||||
|
// shouldDeleteLocked reports whether we should delete this endpoint.
|
||||||
|
func (st *endpointState) shouldDeleteLocked() bool {
|
||||||
|
switch {
|
||||||
|
case st.lastGotPing.IsZero():
|
||||||
|
// This was an endpoint from the network map. Is it still in the network map?
|
||||||
|
return st.index == indexSentinelDeleted
|
||||||
|
default:
|
||||||
|
// Thiw was an endpoint discovered at runtime.
|
||||||
|
return time.Since(st.lastGotPing) > sessionActiveTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (de *discoEndpoint) deleteEndpointLocked(ep netaddr.IPPort) {
|
||||||
|
delete(de.endpointState, ep)
|
||||||
|
if de.bestAddr == ep {
|
||||||
|
de.bestAddr = netaddr.IPPort{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pongHistoryCount is how many pongReply values we keep per endpointState
|
// pongHistoryCount is how many pongReply values we keep per endpointState
|
||||||
@ -3429,7 +3463,10 @@ func (de *discoEndpoint) sendPingsLocked(now time.Time, sendCallMeMaybe bool) {
|
|||||||
de.lastFullPing = now
|
de.lastFullPing = now
|
||||||
var sentAny bool
|
var sentAny bool
|
||||||
for ep, st := range de.endpointState {
|
for ep, st := range de.endpointState {
|
||||||
ep := ep
|
if st.shouldDeleteLocked() {
|
||||||
|
de.deleteEndpointLocked(ep)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if !st.lastPing.IsZero() && now.Sub(st.lastPing) < discoPingInterval {
|
if !st.lastPing.IsZero() && now.Sub(st.lastPing) < discoPingInterval {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -3474,7 +3511,7 @@ func (de *discoEndpoint) updateFromNode(n *tailcfg.Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, st := range de.endpointState {
|
for _, st := range de.endpointState {
|
||||||
st.index = -1 // assume deleted until updated in next loop
|
st.index = indexSentinelDeleted // assume deleted until updated in next loop
|
||||||
}
|
}
|
||||||
for i, epStr := range n.Endpoints {
|
for i, epStr := range n.Endpoints {
|
||||||
if i > math.MaxInt16 {
|
if i > math.MaxInt16 {
|
||||||
@ -3492,14 +3529,49 @@ func (de *discoEndpoint) updateFromNode(n *tailcfg.Node) {
|
|||||||
de.endpointState[ipp] = &endpointState{index: int16(i)}
|
de.endpointState[ipp] = &endpointState{index: int16(i)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Now delete anything that wasn't updated.
|
|
||||||
for ipp, st := range de.endpointState {
|
// Now delete anything unless it's still in the network map or
|
||||||
if st.index == -1 {
|
// was a recently discovered endpoint.
|
||||||
delete(de.endpointState, ipp)
|
for ep, st := range de.endpointState {
|
||||||
if de.bestAddr == ipp {
|
if st.shouldDeleteLocked() {
|
||||||
de.bestAddr = netaddr.IPPort{}
|
de.deleteEndpointLocked(ep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addCandidateEndpoint adds ep as an endpoint to which we should send
|
||||||
|
// future pings.
|
||||||
|
//
|
||||||
|
// This is called once we've already verified that we got a valid
|
||||||
|
// discovery message from de via ep.
|
||||||
|
func (de *discoEndpoint) addCandidateEndpoint(ep netaddr.IPPort) {
|
||||||
|
de.mu.Lock()
|
||||||
|
defer de.mu.Unlock()
|
||||||
|
|
||||||
|
if st, ok := de.endpointState[ep]; ok {
|
||||||
|
if st.lastGotPing.IsZero() {
|
||||||
|
// Already-known endpoint from the network map.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
st.lastGotPing = time.Now()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Newly discovered endpoint. Exciting!
|
||||||
|
de.c.logf("magicsock: disco: adding %v as candidate endpoint for %v (%s)", ep, de.discoShort, de.publicKey.ShortString())
|
||||||
|
de.endpointState[ep] = &endpointState{
|
||||||
|
lastGotPing: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// If for some reason this gets very large, do some cleanup.
|
||||||
|
if size := len(de.endpointState); size > 100 {
|
||||||
|
for ep, st := range de.endpointState {
|
||||||
|
if st.shouldDeleteLocked() {
|
||||||
|
de.deleteEndpointLocked(ep)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
size2 := len(de.endpointState)
|
||||||
|
de.c.logf("magicsock: disco: addCandidateEndpoint pruned %v candidate set from %v to %v entries", size, size2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user