|
|
|
@ -4,23 +4,18 @@
|
|
|
|
package magicsock
|
|
|
|
package magicsock
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
|
|
|
|
"context"
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
|
|
|
|
"errors"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"io"
|
|
|
|
|
|
|
|
"net/http"
|
|
|
|
|
|
|
|
"net/netip"
|
|
|
|
"net/netip"
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
"sync"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"tailscale.com/disco"
|
|
|
|
"tailscale.com/disco"
|
|
|
|
"tailscale.com/net/stun"
|
|
|
|
"tailscale.com/net/stun"
|
|
|
|
udprelay "tailscale.com/net/udprelay/endpoint"
|
|
|
|
udprelay "tailscale.com/net/udprelay/endpoint"
|
|
|
|
|
|
|
|
"tailscale.com/tailcfg"
|
|
|
|
|
|
|
|
"tailscale.com/tstime"
|
|
|
|
"tailscale.com/types/key"
|
|
|
|
"tailscale.com/types/key"
|
|
|
|
"tailscale.com/util/httpm"
|
|
|
|
|
|
|
|
"tailscale.com/util/set"
|
|
|
|
"tailscale.com/util/set"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@ -38,14 +33,15 @@ type relayManager struct {
|
|
|
|
|
|
|
|
|
|
|
|
// ===================================================================
|
|
|
|
// ===================================================================
|
|
|
|
// The following fields are owned by a single goroutine, runLoop().
|
|
|
|
// The following fields are owned by a single goroutine, runLoop().
|
|
|
|
serversByAddrPort map[netip.AddrPort]key.DiscoPublic
|
|
|
|
serversByNodeKey map[key.NodePublic]candidatePeerRelay
|
|
|
|
serversByDisco map[key.DiscoPublic]netip.AddrPort
|
|
|
|
allocWorkByCandidatePeerRelayByEndpoint map[*endpoint]map[candidatePeerRelay]*relayEndpointAllocWork
|
|
|
|
allocWorkByEndpoint map[*endpoint]*relayEndpointAllocWork
|
|
|
|
allocWorkByDiscoKeysByServerNodeKey map[key.NodePublic]map[key.SortedPairOfDiscoPublic]*relayEndpointAllocWork
|
|
|
|
handshakeWorkByEndpointByServerDisco map[*endpoint]map[key.DiscoPublic]*relayHandshakeWork
|
|
|
|
handshakeWorkByServerDiscoByEndpoint map[*endpoint]map[key.DiscoPublic]*relayHandshakeWork
|
|
|
|
handshakeWorkByServerDiscoVNI map[serverDiscoVNI]*relayHandshakeWork
|
|
|
|
handshakeWorkByServerDiscoVNI map[serverDiscoVNI]*relayHandshakeWork
|
|
|
|
handshakeWorkAwaitingPong map[*relayHandshakeWork]addrPortVNI
|
|
|
|
handshakeWorkAwaitingPong map[*relayHandshakeWork]addrPortVNI
|
|
|
|
addrPortVNIToHandshakeWork map[addrPortVNI]*relayHandshakeWork
|
|
|
|
addrPortVNIToHandshakeWork map[addrPortVNI]*relayHandshakeWork
|
|
|
|
handshakeGeneration uint32
|
|
|
|
handshakeGeneration uint32
|
|
|
|
|
|
|
|
allocGeneration uint32
|
|
|
|
|
|
|
|
|
|
|
|
// ===================================================================
|
|
|
|
// ===================================================================
|
|
|
|
// The following chan fields serve event inputs to a single goroutine,
|
|
|
|
// The following chan fields serve event inputs to a single goroutine,
|
|
|
|
@ -55,9 +51,10 @@ type relayManager struct {
|
|
|
|
handshakeWorkDoneCh chan relayEndpointHandshakeWorkDoneEvent
|
|
|
|
handshakeWorkDoneCh chan relayEndpointHandshakeWorkDoneEvent
|
|
|
|
cancelWorkCh chan *endpoint
|
|
|
|
cancelWorkCh chan *endpoint
|
|
|
|
newServerEndpointCh chan newRelayServerEndpointEvent
|
|
|
|
newServerEndpointCh chan newRelayServerEndpointEvent
|
|
|
|
rxHandshakeDiscoMsgCh chan relayHandshakeDiscoMsgEvent
|
|
|
|
rxDiscoMsgCh chan relayDiscoMsgEvent
|
|
|
|
serversCh chan set.Set[netip.AddrPort]
|
|
|
|
serversCh chan set.Set[candidatePeerRelay]
|
|
|
|
getServersCh chan chan set.Set[netip.AddrPort]
|
|
|
|
getServersCh chan chan set.Set[candidatePeerRelay]
|
|
|
|
|
|
|
|
derpHomeChangeCh chan derpHomeChangeEvent
|
|
|
|
|
|
|
|
|
|
|
|
discoInfoMu sync.Mutex // guards the following field
|
|
|
|
discoInfoMu sync.Mutex // guards the following field
|
|
|
|
discoInfoByServerDisco map[key.DiscoPublic]*relayHandshakeDiscoInfo
|
|
|
|
discoInfoByServerDisco map[key.DiscoPublic]*relayHandshakeDiscoInfo
|
|
|
|
@ -86,7 +83,7 @@ type relayHandshakeWork struct {
|
|
|
|
// relayManager.handshakeWorkDoneCh if runLoop() can receive it. runLoop()
|
|
|
|
// relayManager.handshakeWorkDoneCh if runLoop() can receive it. runLoop()
|
|
|
|
// must select{} read on doneCh to prevent deadlock when attempting to write
|
|
|
|
// must select{} read on doneCh to prevent deadlock when attempting to write
|
|
|
|
// to rxDiscoMsgCh.
|
|
|
|
// to rxDiscoMsgCh.
|
|
|
|
rxDiscoMsgCh chan relayHandshakeDiscoMsgEvent
|
|
|
|
rxDiscoMsgCh chan relayDiscoMsgEvent
|
|
|
|
doneCh chan relayEndpointHandshakeWorkDoneEvent
|
|
|
|
doneCh chan relayEndpointHandshakeWorkDoneEvent
|
|
|
|
|
|
|
|
|
|
|
|
ctx context.Context
|
|
|
|
ctx context.Context
|
|
|
|
@ -100,7 +97,7 @@ type relayHandshakeWork struct {
|
|
|
|
type newRelayServerEndpointEvent struct {
|
|
|
|
type newRelayServerEndpointEvent struct {
|
|
|
|
wlb endpointWithLastBest
|
|
|
|
wlb endpointWithLastBest
|
|
|
|
se udprelay.ServerEndpoint
|
|
|
|
se udprelay.ServerEndpoint
|
|
|
|
server netip.AddrPort // zero value if learned via [disco.CallMeMaybeVia]
|
|
|
|
server candidatePeerRelay // zero value if learned via [disco.CallMeMaybeVia]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// relayEndpointAllocWorkDoneEvent indicates relay server endpoint allocation
|
|
|
|
// relayEndpointAllocWorkDoneEvent indicates relay server endpoint allocation
|
|
|
|
@ -108,6 +105,7 @@ type newRelayServerEndpointEvent struct {
|
|
|
|
// initialized.
|
|
|
|
// initialized.
|
|
|
|
type relayEndpointAllocWorkDoneEvent struct {
|
|
|
|
type relayEndpointAllocWorkDoneEvent struct {
|
|
|
|
work *relayEndpointAllocWork
|
|
|
|
work *relayEndpointAllocWork
|
|
|
|
|
|
|
|
allocated udprelay.ServerEndpoint // !allocated.ServerDisco.IsZero() if allocation succeeded
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// relayEndpointHandshakeWorkDoneEvent indicates relay server endpoint handshake
|
|
|
|
// relayEndpointHandshakeWorkDoneEvent indicates relay server endpoint handshake
|
|
|
|
@ -122,18 +120,42 @@ type relayEndpointHandshakeWorkDoneEvent struct {
|
|
|
|
// hasActiveWorkRunLoop returns true if there is outstanding allocation or
|
|
|
|
// hasActiveWorkRunLoop returns true if there is outstanding allocation or
|
|
|
|
// handshaking work for any endpoint, otherwise it returns false.
|
|
|
|
// handshaking work for any endpoint, otherwise it returns false.
|
|
|
|
func (r *relayManager) hasActiveWorkRunLoop() bool {
|
|
|
|
func (r *relayManager) hasActiveWorkRunLoop() bool {
|
|
|
|
return len(r.allocWorkByEndpoint) > 0 || len(r.handshakeWorkByEndpointByServerDisco) > 0
|
|
|
|
return len(r.allocWorkByCandidatePeerRelayByEndpoint) > 0 || len(r.handshakeWorkByServerDiscoByEndpoint) > 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// hasActiveWorkForEndpointRunLoop returns true if there is outstanding
|
|
|
|
// hasActiveWorkForEndpointRunLoop returns true if there is outstanding
|
|
|
|
// allocation or handshaking work for the provided endpoint, otherwise it
|
|
|
|
// allocation or handshaking work for the provided endpoint, otherwise it
|
|
|
|
// returns false.
|
|
|
|
// returns false.
|
|
|
|
func (r *relayManager) hasActiveWorkForEndpointRunLoop(ep *endpoint) bool {
|
|
|
|
func (r *relayManager) hasActiveWorkForEndpointRunLoop(ep *endpoint) bool {
|
|
|
|
_, handshakeWork := r.handshakeWorkByEndpointByServerDisco[ep]
|
|
|
|
_, handshakeWork := r.handshakeWorkByServerDiscoByEndpoint[ep]
|
|
|
|
_, allocWork := r.allocWorkByEndpoint[ep]
|
|
|
|
_, allocWork := r.allocWorkByCandidatePeerRelayByEndpoint[ep]
|
|
|
|
return handshakeWork || allocWork
|
|
|
|
return handshakeWork || allocWork
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// derpHomeChangeEvent represents a change in the DERP home region for the
|
|
|
|
|
|
|
|
// node identified by nodeKey. This structure is immutable once initialized.
|
|
|
|
|
|
|
|
type derpHomeChangeEvent struct {
|
|
|
|
|
|
|
|
nodeKey key.NodePublic
|
|
|
|
|
|
|
|
regionID uint16
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// handleDERPHomeChange handles a DERP home change event for nodeKey and
|
|
|
|
|
|
|
|
// regionID.
|
|
|
|
|
|
|
|
func (r *relayManager) handleDERPHomeChange(nodeKey key.NodePublic, regionID uint16) {
|
|
|
|
|
|
|
|
relayManagerInputEvent(r, nil, &r.derpHomeChangeCh, derpHomeChangeEvent{
|
|
|
|
|
|
|
|
nodeKey: nodeKey,
|
|
|
|
|
|
|
|
regionID: regionID,
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (r *relayManager) handleDERPHomeChangeRunLoop(event derpHomeChangeEvent) {
|
|
|
|
|
|
|
|
c, ok := r.serversByNodeKey[event.nodeKey]
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
|
|
|
|
c.derpHomeRegionID = event.regionID
|
|
|
|
|
|
|
|
r.serversByNodeKey[event.nodeKey] = c
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// runLoop is a form of event loop. It ensures exclusive access to most of
|
|
|
|
// runLoop is a form of event loop. It ensures exclusive access to most of
|
|
|
|
// [relayManager] state.
|
|
|
|
// [relayManager] state.
|
|
|
|
func (r *relayManager) runLoop() {
|
|
|
|
func (r *relayManager) runLoop() {
|
|
|
|
@ -151,13 +173,7 @@ func (r *relayManager) runLoop() {
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case done := <-r.allocateWorkDoneCh:
|
|
|
|
case done := <-r.allocateWorkDoneCh:
|
|
|
|
work, ok := r.allocWorkByEndpoint[done.work.ep]
|
|
|
|
r.handleAllocWorkDoneRunLoop(done)
|
|
|
|
if ok && work == done.work {
|
|
|
|
|
|
|
|
// Verify the work in the map is the same as the one that we're
|
|
|
|
|
|
|
|
// cleaning up. New events on r.startDiscoveryCh can
|
|
|
|
|
|
|
|
// overwrite pre-existing keys.
|
|
|
|
|
|
|
|
delete(r.allocWorkByEndpoint, done.work.ep)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if !r.hasActiveWorkRunLoop() {
|
|
|
|
if !r.hasActiveWorkRunLoop() {
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -176,8 +192,8 @@ func (r *relayManager) runLoop() {
|
|
|
|
if !r.hasActiveWorkRunLoop() {
|
|
|
|
if !r.hasActiveWorkRunLoop() {
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case discoMsgEvent := <-r.rxHandshakeDiscoMsgCh:
|
|
|
|
case discoMsgEvent := <-r.rxDiscoMsgCh:
|
|
|
|
r.handleRxHandshakeDiscoMsgRunLoop(discoMsgEvent)
|
|
|
|
r.handleRxDiscoMsgRunLoop(discoMsgEvent)
|
|
|
|
if !r.hasActiveWorkRunLoop() {
|
|
|
|
if !r.hasActiveWorkRunLoop() {
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -191,44 +207,44 @@ func (r *relayManager) runLoop() {
|
|
|
|
if !r.hasActiveWorkRunLoop() {
|
|
|
|
if !r.hasActiveWorkRunLoop() {
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
case derpHomeChange := <-r.derpHomeChangeCh:
|
|
|
|
|
|
|
|
r.handleDERPHomeChangeRunLoop(derpHomeChange)
|
|
|
|
|
|
|
|
if !r.hasActiveWorkRunLoop() {
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (r *relayManager) handleGetServersRunLoop(getServersCh chan set.Set[netip.AddrPort]) {
|
|
|
|
func (r *relayManager) handleGetServersRunLoop(getServersCh chan set.Set[candidatePeerRelay]) {
|
|
|
|
servers := make(set.Set[netip.AddrPort], len(r.serversByAddrPort))
|
|
|
|
servers := make(set.Set[candidatePeerRelay], len(r.serversByNodeKey))
|
|
|
|
for server := range r.serversByAddrPort {
|
|
|
|
for _, v := range r.serversByNodeKey {
|
|
|
|
servers.Add(server)
|
|
|
|
servers.Add(v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
getServersCh <- servers
|
|
|
|
getServersCh <- servers
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (r *relayManager) getServers() set.Set[netip.AddrPort] {
|
|
|
|
func (r *relayManager) getServers() set.Set[candidatePeerRelay] {
|
|
|
|
ch := make(chan set.Set[netip.AddrPort])
|
|
|
|
ch := make(chan set.Set[candidatePeerRelay])
|
|
|
|
relayManagerInputEvent(r, nil, &r.getServersCh, ch)
|
|
|
|
relayManagerInputEvent(r, nil, &r.getServersCh, ch)
|
|
|
|
return <-ch
|
|
|
|
return <-ch
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (r *relayManager) handleServersUpdateRunLoop(update set.Set[netip.AddrPort]) {
|
|
|
|
func (r *relayManager) handleServersUpdateRunLoop(update set.Set[candidatePeerRelay]) {
|
|
|
|
for k, v := range r.serversByAddrPort {
|
|
|
|
for _, v := range r.serversByNodeKey {
|
|
|
|
if !update.Contains(k) {
|
|
|
|
if !update.Contains(v) {
|
|
|
|
delete(r.serversByAddrPort, k)
|
|
|
|
delete(r.serversByNodeKey, v.nodeKey)
|
|
|
|
delete(r.serversByDisco, v)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, v := range update.Slice() {
|
|
|
|
for _, v := range update.Slice() {
|
|
|
|
_, ok := r.serversByAddrPort[v]
|
|
|
|
r.serversByNodeKey[v.nodeKey] = v
|
|
|
|
if ok {
|
|
|
|
|
|
|
|
// don't zero known disco keys
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
r.serversByAddrPort[v] = key.DiscoPublic{}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type relayHandshakeDiscoMsgEvent struct {
|
|
|
|
type relayDiscoMsgEvent struct {
|
|
|
|
conn *Conn // for access to [Conn] if there is no associated [relayHandshakeWork]
|
|
|
|
conn *Conn // for access to [Conn] if there is no associated [relayHandshakeWork]
|
|
|
|
msg disco.Message
|
|
|
|
msg disco.Message
|
|
|
|
|
|
|
|
relayServerNodeKey key.NodePublic // nonzero if msg is a [*disco.AllocateUDPRelayEndpointResponse]
|
|
|
|
disco key.DiscoPublic
|
|
|
|
disco key.DiscoPublic
|
|
|
|
from netip.AddrPort
|
|
|
|
from netip.AddrPort
|
|
|
|
vni uint32
|
|
|
|
vni uint32
|
|
|
|
@ -238,22 +254,30 @@ type relayHandshakeDiscoMsgEvent struct {
|
|
|
|
// relayEndpointAllocWork serves to track in-progress relay endpoint allocation
|
|
|
|
// relayEndpointAllocWork serves to track in-progress relay endpoint allocation
|
|
|
|
// for an [*endpoint]. This structure is immutable once initialized.
|
|
|
|
// for an [*endpoint]. This structure is immutable once initialized.
|
|
|
|
type relayEndpointAllocWork struct {
|
|
|
|
type relayEndpointAllocWork struct {
|
|
|
|
// ep is the [*endpoint] associated with the work
|
|
|
|
wlb endpointWithLastBest
|
|
|
|
ep *endpoint
|
|
|
|
discoKeys key.SortedPairOfDiscoPublic
|
|
|
|
// cancel() will signal all associated goroutines to return
|
|
|
|
candidatePeerRelay candidatePeerRelay
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// allocateServerEndpoint() always writes to doneCh (len 1) when it
|
|
|
|
|
|
|
|
// returns. It may end up writing the same event afterward to
|
|
|
|
|
|
|
|
// [relayManager.allocateWorkDoneCh] if runLoop() can receive it. runLoop()
|
|
|
|
|
|
|
|
// must select{} read on doneCh to prevent deadlock when attempting to write
|
|
|
|
|
|
|
|
// to rxDiscoMsgCh.
|
|
|
|
|
|
|
|
rxDiscoMsgCh chan *disco.AllocateUDPRelayEndpointResponse
|
|
|
|
|
|
|
|
doneCh chan relayEndpointAllocWorkDoneEvent
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ctx context.Context
|
|
|
|
cancel context.CancelFunc
|
|
|
|
cancel context.CancelFunc
|
|
|
|
// wg.Wait() will return once all associated goroutines have returned
|
|
|
|
|
|
|
|
wg *sync.WaitGroup
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// init initializes [relayManager] if it is not already initialized.
|
|
|
|
// init initializes [relayManager] if it is not already initialized.
|
|
|
|
func (r *relayManager) init() {
|
|
|
|
func (r *relayManager) init() {
|
|
|
|
r.initOnce.Do(func() {
|
|
|
|
r.initOnce.Do(func() {
|
|
|
|
r.discoInfoByServerDisco = make(map[key.DiscoPublic]*relayHandshakeDiscoInfo)
|
|
|
|
r.discoInfoByServerDisco = make(map[key.DiscoPublic]*relayHandshakeDiscoInfo)
|
|
|
|
r.serversByDisco = make(map[key.DiscoPublic]netip.AddrPort)
|
|
|
|
r.serversByNodeKey = make(map[key.NodePublic]candidatePeerRelay)
|
|
|
|
r.serversByAddrPort = make(map[netip.AddrPort]key.DiscoPublic)
|
|
|
|
r.allocWorkByCandidatePeerRelayByEndpoint = make(map[*endpoint]map[candidatePeerRelay]*relayEndpointAllocWork)
|
|
|
|
r.allocWorkByEndpoint = make(map[*endpoint]*relayEndpointAllocWork)
|
|
|
|
r.allocWorkByDiscoKeysByServerNodeKey = make(map[key.NodePublic]map[key.SortedPairOfDiscoPublic]*relayEndpointAllocWork)
|
|
|
|
r.handshakeWorkByEndpointByServerDisco = make(map[*endpoint]map[key.DiscoPublic]*relayHandshakeWork)
|
|
|
|
r.handshakeWorkByServerDiscoByEndpoint = make(map[*endpoint]map[key.DiscoPublic]*relayHandshakeWork)
|
|
|
|
r.handshakeWorkByServerDiscoVNI = make(map[serverDiscoVNI]*relayHandshakeWork)
|
|
|
|
r.handshakeWorkByServerDiscoVNI = make(map[serverDiscoVNI]*relayHandshakeWork)
|
|
|
|
r.handshakeWorkAwaitingPong = make(map[*relayHandshakeWork]addrPortVNI)
|
|
|
|
r.handshakeWorkAwaitingPong = make(map[*relayHandshakeWork]addrPortVNI)
|
|
|
|
r.addrPortVNIToHandshakeWork = make(map[addrPortVNI]*relayHandshakeWork)
|
|
|
|
r.addrPortVNIToHandshakeWork = make(map[addrPortVNI]*relayHandshakeWork)
|
|
|
|
@ -262,9 +286,10 @@ func (r *relayManager) init() {
|
|
|
|
r.handshakeWorkDoneCh = make(chan relayEndpointHandshakeWorkDoneEvent)
|
|
|
|
r.handshakeWorkDoneCh = make(chan relayEndpointHandshakeWorkDoneEvent)
|
|
|
|
r.cancelWorkCh = make(chan *endpoint)
|
|
|
|
r.cancelWorkCh = make(chan *endpoint)
|
|
|
|
r.newServerEndpointCh = make(chan newRelayServerEndpointEvent)
|
|
|
|
r.newServerEndpointCh = make(chan newRelayServerEndpointEvent)
|
|
|
|
r.rxHandshakeDiscoMsgCh = make(chan relayHandshakeDiscoMsgEvent)
|
|
|
|
r.rxDiscoMsgCh = make(chan relayDiscoMsgEvent)
|
|
|
|
r.serversCh = make(chan set.Set[netip.AddrPort])
|
|
|
|
r.serversCh = make(chan set.Set[candidatePeerRelay])
|
|
|
|
r.getServersCh = make(chan chan set.Set[netip.AddrPort])
|
|
|
|
r.getServersCh = make(chan chan set.Set[candidatePeerRelay])
|
|
|
|
|
|
|
|
r.derpHomeChangeCh = make(chan derpHomeChangeEvent)
|
|
|
|
r.runLoopStoppedCh = make(chan struct{}, 1)
|
|
|
|
r.runLoopStoppedCh = make(chan struct{}, 1)
|
|
|
|
r.runLoopStoppedCh <- struct{}{}
|
|
|
|
r.runLoopStoppedCh <- struct{}{}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
@ -330,6 +355,7 @@ func (r *relayManager) discoInfo(serverDisco key.DiscoPublic) (_ *discoInfo, ok
|
|
|
|
func (r *relayManager) handleCallMeMaybeVia(ep *endpoint, lastBest addrQuality, lastBestIsTrusted bool, dm *disco.CallMeMaybeVia) {
|
|
|
|
func (r *relayManager) handleCallMeMaybeVia(ep *endpoint, lastBest addrQuality, lastBestIsTrusted bool, dm *disco.CallMeMaybeVia) {
|
|
|
|
se := udprelay.ServerEndpoint{
|
|
|
|
se := udprelay.ServerEndpoint{
|
|
|
|
ServerDisco: dm.ServerDisco,
|
|
|
|
ServerDisco: dm.ServerDisco,
|
|
|
|
|
|
|
|
ClientDisco: dm.ClientDisco,
|
|
|
|
LamportID: dm.LamportID,
|
|
|
|
LamportID: dm.LamportID,
|
|
|
|
AddrPorts: dm.AddrPorts,
|
|
|
|
AddrPorts: dm.AddrPorts,
|
|
|
|
VNI: dm.VNI,
|
|
|
|
VNI: dm.VNI,
|
|
|
|
@ -346,14 +372,25 @@ func (r *relayManager) handleCallMeMaybeVia(ep *endpoint, lastBest addrQuality,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// handleGeneveEncapDiscoMsg handles reception of Geneve-encapsulated disco
|
|
|
|
// handleRxDiscoMsg handles reception of disco messages that [relayManager]
|
|
|
|
// messages.
|
|
|
|
// may be interested in. This includes all Geneve-encapsulated disco messages
|
|
|
|
func (r *relayManager) handleGeneveEncapDiscoMsg(conn *Conn, dm disco.Message, di *discoInfo, src epAddr) {
|
|
|
|
// and [*disco.AllocateUDPRelayEndpointResponse]. If dm is a
|
|
|
|
relayManagerInputEvent(r, nil, &r.rxHandshakeDiscoMsgCh, relayHandshakeDiscoMsgEvent{conn: conn, msg: dm, disco: di.discoKey, from: src.ap, vni: src.vni.get(), at: time.Now()})
|
|
|
|
// [*disco.AllocateUDPRelayEndpointResponse] then relayServerNodeKey must be
|
|
|
|
|
|
|
|
// nonzero.
|
|
|
|
|
|
|
|
func (r *relayManager) handleRxDiscoMsg(conn *Conn, dm disco.Message, relayServerNodeKey key.NodePublic, discoKey key.DiscoPublic, src epAddr) {
|
|
|
|
|
|
|
|
relayManagerInputEvent(r, nil, &r.rxDiscoMsgCh, relayDiscoMsgEvent{
|
|
|
|
|
|
|
|
conn: conn,
|
|
|
|
|
|
|
|
msg: dm,
|
|
|
|
|
|
|
|
relayServerNodeKey: relayServerNodeKey,
|
|
|
|
|
|
|
|
disco: discoKey,
|
|
|
|
|
|
|
|
from: src.ap,
|
|
|
|
|
|
|
|
vni: src.vni.get(),
|
|
|
|
|
|
|
|
at: time.Now(),
|
|
|
|
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// handleRelayServersSet handles an update of the complete relay server set.
|
|
|
|
// handleRelayServersSet handles an update of the complete relay server set.
|
|
|
|
func (r *relayManager) handleRelayServersSet(servers set.Set[netip.AddrPort]) {
|
|
|
|
func (r *relayManager) handleRelayServersSet(servers set.Set[candidatePeerRelay]) {
|
|
|
|
relayManagerInputEvent(r, nil, &r.serversCh, servers)
|
|
|
|
relayManagerInputEvent(r, nil, &r.serversCh, servers)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -396,7 +433,11 @@ type endpointWithLastBest struct {
|
|
|
|
// startUDPRelayPathDiscoveryFor starts UDP relay path discovery for ep on all
|
|
|
|
// startUDPRelayPathDiscoveryFor starts UDP relay path discovery for ep on all
|
|
|
|
// known relay servers if ep has no in-progress work.
|
|
|
|
// known relay servers if ep has no in-progress work.
|
|
|
|
func (r *relayManager) startUDPRelayPathDiscoveryFor(ep *endpoint, lastBest addrQuality, lastBestIsTrusted bool) {
|
|
|
|
func (r *relayManager) startUDPRelayPathDiscoveryFor(ep *endpoint, lastBest addrQuality, lastBestIsTrusted bool) {
|
|
|
|
relayManagerInputEvent(r, nil, &r.startDiscoveryCh, endpointWithLastBest{ep, lastBest, lastBestIsTrusted})
|
|
|
|
relayManagerInputEvent(r, nil, &r.startDiscoveryCh, endpointWithLastBest{
|
|
|
|
|
|
|
|
ep: ep,
|
|
|
|
|
|
|
|
lastBest: lastBest,
|
|
|
|
|
|
|
|
lastBestIsTrusted: lastBestIsTrusted,
|
|
|
|
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// stopWork stops all outstanding allocation & handshaking work for 'ep'.
|
|
|
|
// stopWork stops all outstanding allocation & handshaking work for 'ep'.
|
|
|
|
@ -407,13 +448,15 @@ func (r *relayManager) stopWork(ep *endpoint) {
|
|
|
|
// stopWorkRunLoop cancels & clears outstanding allocation and handshaking
|
|
|
|
// stopWorkRunLoop cancels & clears outstanding allocation and handshaking
|
|
|
|
// work for 'ep'.
|
|
|
|
// work for 'ep'.
|
|
|
|
func (r *relayManager) stopWorkRunLoop(ep *endpoint) {
|
|
|
|
func (r *relayManager) stopWorkRunLoop(ep *endpoint) {
|
|
|
|
allocWork, ok := r.allocWorkByEndpoint[ep]
|
|
|
|
byDiscoKeys, ok := r.allocWorkByCandidatePeerRelayByEndpoint[ep]
|
|
|
|
if ok {
|
|
|
|
if ok {
|
|
|
|
allocWork.cancel()
|
|
|
|
for _, work := range byDiscoKeys {
|
|
|
|
allocWork.wg.Wait()
|
|
|
|
work.cancel()
|
|
|
|
delete(r.allocWorkByEndpoint, ep)
|
|
|
|
done := <-work.doneCh
|
|
|
|
|
|
|
|
r.handleAllocWorkDoneRunLoop(done)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
byServerDisco, ok := r.handshakeWorkByEndpointByServerDisco[ep]
|
|
|
|
byServerDisco, ok := r.handshakeWorkByServerDiscoByEndpoint[ep]
|
|
|
|
if ok {
|
|
|
|
if ok {
|
|
|
|
for _, handshakeWork := range byServerDisco {
|
|
|
|
for _, handshakeWork := range byServerDisco {
|
|
|
|
handshakeWork.cancel()
|
|
|
|
handshakeWork.cancel()
|
|
|
|
@ -430,13 +473,33 @@ type addrPortVNI struct {
|
|
|
|
vni uint32
|
|
|
|
vni uint32
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (r *relayManager) handleRxHandshakeDiscoMsgRunLoop(event relayHandshakeDiscoMsgEvent) {
|
|
|
|
func (r *relayManager) handleRxDiscoMsgRunLoop(event relayDiscoMsgEvent) {
|
|
|
|
var (
|
|
|
|
var (
|
|
|
|
work *relayHandshakeWork
|
|
|
|
work *relayHandshakeWork
|
|
|
|
ok bool
|
|
|
|
ok bool
|
|
|
|
)
|
|
|
|
)
|
|
|
|
apv := addrPortVNI{event.from, event.vni}
|
|
|
|
apv := addrPortVNI{event.from, event.vni}
|
|
|
|
switch msg := event.msg.(type) {
|
|
|
|
switch msg := event.msg.(type) {
|
|
|
|
|
|
|
|
case *disco.AllocateUDPRelayEndpointResponse:
|
|
|
|
|
|
|
|
sorted := key.NewSortedPairOfDiscoPublic(msg.ClientDisco[0], msg.ClientDisco[1])
|
|
|
|
|
|
|
|
byDiscoKeys, ok := r.allocWorkByDiscoKeysByServerNodeKey[event.relayServerNodeKey]
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
|
|
// No outstanding work tied to this relay sever, discard.
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
allocWork, ok := byDiscoKeys[sorted]
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
|
|
// No outstanding work tied to these disco keys, discard.
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
|
|
|
|
case done := <-allocWork.doneCh:
|
|
|
|
|
|
|
|
// allocateServerEndpoint returned, clean up its state
|
|
|
|
|
|
|
|
r.handleAllocWorkDoneRunLoop(done)
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
case allocWork.rxDiscoMsgCh <- msg:
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
case *disco.BindUDPRelayEndpointChallenge:
|
|
|
|
case *disco.BindUDPRelayEndpointChallenge:
|
|
|
|
work, ok = r.handshakeWorkByServerDiscoVNI[serverDiscoVNI{event.disco, event.vni}]
|
|
|
|
work, ok = r.handshakeWorkByServerDiscoVNI[serverDiscoVNI{event.disco, event.vni}]
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
@ -504,8 +567,39 @@ func (r *relayManager) handleRxHandshakeDiscoMsgRunLoop(event relayHandshakeDisc
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (r *relayManager) handleAllocWorkDoneRunLoop(done relayEndpointAllocWorkDoneEvent) {
|
|
|
|
|
|
|
|
byCandidatePeerRelay, ok := r.allocWorkByCandidatePeerRelayByEndpoint[done.work.wlb.ep]
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
work, ok := byCandidatePeerRelay[done.work.candidatePeerRelay]
|
|
|
|
|
|
|
|
if !ok || work != done.work {
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(byCandidatePeerRelay, done.work.candidatePeerRelay)
|
|
|
|
|
|
|
|
if len(byCandidatePeerRelay) == 0 {
|
|
|
|
|
|
|
|
delete(r.allocWorkByCandidatePeerRelayByEndpoint, done.work.wlb.ep)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
byDiscoKeys, ok := r.allocWorkByDiscoKeysByServerNodeKey[done.work.candidatePeerRelay.nodeKey]
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
|
|
// unexpected
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(byDiscoKeys, done.work.discoKeys)
|
|
|
|
|
|
|
|
if len(byDiscoKeys) == 0 {
|
|
|
|
|
|
|
|
delete(r.allocWorkByDiscoKeysByServerNodeKey, done.work.candidatePeerRelay.nodeKey)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if !done.allocated.ServerDisco.IsZero() {
|
|
|
|
|
|
|
|
r.handleNewServerEndpointRunLoop(newRelayServerEndpointEvent{
|
|
|
|
|
|
|
|
wlb: done.work.wlb,
|
|
|
|
|
|
|
|
se: done.allocated,
|
|
|
|
|
|
|
|
server: done.work.candidatePeerRelay,
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (r *relayManager) handleHandshakeWorkDoneRunLoop(done relayEndpointHandshakeWorkDoneEvent) {
|
|
|
|
func (r *relayManager) handleHandshakeWorkDoneRunLoop(done relayEndpointHandshakeWorkDoneEvent) {
|
|
|
|
byServerDisco, ok := r.handshakeWorkByEndpointByServerDisco[done.work.wlb.ep]
|
|
|
|
byServerDisco, ok := r.handshakeWorkByServerDiscoByEndpoint[done.work.wlb.ep]
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -515,7 +609,7 @@ func (r *relayManager) handleHandshakeWorkDoneRunLoop(done relayEndpointHandshak
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete(byServerDisco, done.work.se.ServerDisco)
|
|
|
|
delete(byServerDisco, done.work.se.ServerDisco)
|
|
|
|
if len(byServerDisco) == 0 {
|
|
|
|
if len(byServerDisco) == 0 {
|
|
|
|
delete(r.handshakeWorkByEndpointByServerDisco, done.work.wlb.ep)
|
|
|
|
delete(r.handshakeWorkByServerDiscoByEndpoint, done.work.wlb.ep)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete(r.handshakeWorkByServerDiscoVNI, serverDiscoVNI{done.work.se.ServerDisco, done.work.se.VNI})
|
|
|
|
delete(r.handshakeWorkByServerDiscoVNI, serverDiscoVNI{done.work.se.ServerDisco, done.work.se.VNI})
|
|
|
|
apv, ok := r.handshakeWorkAwaitingPong[work]
|
|
|
|
apv, ok := r.handshakeWorkAwaitingPong[work]
|
|
|
|
@ -562,7 +656,7 @@ func (r *relayManager) handleNewServerEndpointRunLoop(newServerEndpoint newRelay
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check for duplicate work by [*endpoint] + server disco.
|
|
|
|
// Check for duplicate work by [*endpoint] + server disco.
|
|
|
|
byServerDisco, ok := r.handshakeWorkByEndpointByServerDisco[newServerEndpoint.wlb.ep]
|
|
|
|
byServerDisco, ok := r.handshakeWorkByServerDiscoByEndpoint[newServerEndpoint.wlb.ep]
|
|
|
|
if ok {
|
|
|
|
if ok {
|
|
|
|
existingWork, ok := byServerDisco[newServerEndpoint.se.ServerDisco]
|
|
|
|
existingWork, ok := byServerDisco[newServerEndpoint.se.ServerDisco]
|
|
|
|
if ok {
|
|
|
|
if ok {
|
|
|
|
@ -580,33 +674,9 @@ func (r *relayManager) handleNewServerEndpointRunLoop(newServerEndpoint newRelay
|
|
|
|
|
|
|
|
|
|
|
|
// We're now reasonably sure we're dealing with the latest
|
|
|
|
// We're now reasonably sure we're dealing with the latest
|
|
|
|
// [udprelay.ServerEndpoint] from a server event order perspective
|
|
|
|
// [udprelay.ServerEndpoint] from a server event order perspective
|
|
|
|
// (LamportID). Update server disco key tracking if appropriate.
|
|
|
|
// (LamportID).
|
|
|
|
if newServerEndpoint.server.IsValid() {
|
|
|
|
|
|
|
|
serverDisco, ok := r.serversByAddrPort[newServerEndpoint.server]
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
|
|
// Allocation raced with an update to our known servers set. This
|
|
|
|
|
|
|
|
// server is no longer known. Return early.
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if serverDisco.Compare(newServerEndpoint.se.ServerDisco) != 0 {
|
|
|
|
|
|
|
|
// The server's disco key has either changed, or simply become
|
|
|
|
|
|
|
|
// known for the first time. In the former case we end up detaching
|
|
|
|
|
|
|
|
// any in-progress handshake work from a "known" relay server.
|
|
|
|
|
|
|
|
// Practically speaking we expect the detached work to fail
|
|
|
|
|
|
|
|
// if the server key did in fact change (server restart) while we
|
|
|
|
|
|
|
|
// were attempting to handshake with it. It is possible, though
|
|
|
|
|
|
|
|
// unlikely, for a server addr:port to effectively move between
|
|
|
|
|
|
|
|
// nodes. Either way, there is no harm in detaching existing work,
|
|
|
|
|
|
|
|
// and we explicitly let that happen for the rare case the detached
|
|
|
|
|
|
|
|
// handshake would complete and remain functional.
|
|
|
|
|
|
|
|
delete(r.serversByDisco, serverDisco)
|
|
|
|
|
|
|
|
delete(r.serversByAddrPort, newServerEndpoint.server)
|
|
|
|
|
|
|
|
r.serversByDisco[serverDisco] = newServerEndpoint.server
|
|
|
|
|
|
|
|
r.serversByAddrPort[newServerEndpoint.server] = serverDisco
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if newServerEndpoint.server.IsValid() {
|
|
|
|
if newServerEndpoint.server.isValid() {
|
|
|
|
// Send a [disco.CallMeMaybeVia] to the remote peer if we allocated this
|
|
|
|
// Send a [disco.CallMeMaybeVia] to the remote peer if we allocated this
|
|
|
|
// endpoint, regardless of if we start a handshake below.
|
|
|
|
// endpoint, regardless of if we start a handshake below.
|
|
|
|
go r.sendCallMeMaybeVia(newServerEndpoint.wlb.ep, newServerEndpoint.se)
|
|
|
|
go r.sendCallMeMaybeVia(newServerEndpoint.wlb.ep, newServerEndpoint.se)
|
|
|
|
@ -641,14 +711,14 @@ func (r *relayManager) handleNewServerEndpointRunLoop(newServerEndpoint newRelay
|
|
|
|
work := &relayHandshakeWork{
|
|
|
|
work := &relayHandshakeWork{
|
|
|
|
wlb: newServerEndpoint.wlb,
|
|
|
|
wlb: newServerEndpoint.wlb,
|
|
|
|
se: newServerEndpoint.se,
|
|
|
|
se: newServerEndpoint.se,
|
|
|
|
rxDiscoMsgCh: make(chan relayHandshakeDiscoMsgEvent),
|
|
|
|
rxDiscoMsgCh: make(chan relayDiscoMsgEvent),
|
|
|
|
doneCh: make(chan relayEndpointHandshakeWorkDoneEvent, 1),
|
|
|
|
doneCh: make(chan relayEndpointHandshakeWorkDoneEvent, 1),
|
|
|
|
ctx: ctx,
|
|
|
|
ctx: ctx,
|
|
|
|
cancel: cancel,
|
|
|
|
cancel: cancel,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if byServerDisco == nil {
|
|
|
|
if byServerDisco == nil {
|
|
|
|
byServerDisco = make(map[key.DiscoPublic]*relayHandshakeWork)
|
|
|
|
byServerDisco = make(map[key.DiscoPublic]*relayHandshakeWork)
|
|
|
|
r.handshakeWorkByEndpointByServerDisco[newServerEndpoint.wlb.ep] = byServerDisco
|
|
|
|
r.handshakeWorkByServerDiscoByEndpoint[newServerEndpoint.wlb.ep] = byServerDisco
|
|
|
|
}
|
|
|
|
}
|
|
|
|
byServerDisco[newServerEndpoint.se.ServerDisco] = work
|
|
|
|
byServerDisco[newServerEndpoint.se.ServerDisco] = work
|
|
|
|
r.handshakeWorkByServerDiscoVNI[sdv] = work
|
|
|
|
r.handshakeWorkByServerDiscoVNI[sdv] = work
|
|
|
|
@ -674,12 +744,15 @@ func (r *relayManager) sendCallMeMaybeVia(ep *endpoint, se udprelay.ServerEndpoi
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
callMeMaybeVia := &disco.CallMeMaybeVia{
|
|
|
|
callMeMaybeVia := &disco.CallMeMaybeVia{
|
|
|
|
|
|
|
|
UDPRelayEndpoint: disco.UDPRelayEndpoint{
|
|
|
|
ServerDisco: se.ServerDisco,
|
|
|
|
ServerDisco: se.ServerDisco,
|
|
|
|
|
|
|
|
ClientDisco: se.ClientDisco,
|
|
|
|
LamportID: se.LamportID,
|
|
|
|
LamportID: se.LamportID,
|
|
|
|
VNI: se.VNI,
|
|
|
|
VNI: se.VNI,
|
|
|
|
BindLifetime: se.BindLifetime.Duration,
|
|
|
|
BindLifetime: se.BindLifetime.Duration,
|
|
|
|
SteadyStateLifetime: se.SteadyStateLifetime.Duration,
|
|
|
|
SteadyStateLifetime: se.SteadyStateLifetime.Duration,
|
|
|
|
AddrPorts: se.AddrPorts,
|
|
|
|
AddrPorts: se.AddrPorts,
|
|
|
|
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ep.c.sendDiscoMessage(epAddr{ap: derpAddr}, ep.publicKey, epDisco.key, callMeMaybeVia, discoVerboseLog)
|
|
|
|
ep.c.sendDiscoMessage(epAddr{ap: derpAddr}, ep.publicKey, epDisco.key, callMeMaybeVia, discoVerboseLog)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -800,7 +873,7 @@ func (r *relayManager) handshakeServerEndpoint(work *relayHandshakeWork, generat
|
|
|
|
// one.
|
|
|
|
// one.
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// We don't need to TX a pong, that was already handled for us
|
|
|
|
// We don't need to TX a pong, that was already handled for us
|
|
|
|
// in handleRxHandshakeDiscoMsgRunLoop().
|
|
|
|
// in handleRxDiscoMsgRunLoop().
|
|
|
|
txPing(msgEvent.from, nil)
|
|
|
|
txPing(msgEvent.from, nil)
|
|
|
|
case *disco.Pong:
|
|
|
|
case *disco.Pong:
|
|
|
|
at, ok := sentPingAt[msg.TxID]
|
|
|
|
at, ok := sentPingAt[msg.TxID]
|
|
|
|
@ -823,104 +896,113 @@ func (r *relayManager) handshakeServerEndpoint(work *relayHandshakeWork, generat
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (r *relayManager) allocateAllServersRunLoop(wlb endpointWithLastBest) {
|
|
|
|
const allocateUDPRelayEndpointRequestTimeout = time.Second * 10
|
|
|
|
if len(r.serversByAddrPort) == 0 {
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
|
|
|
started := &relayEndpointAllocWork{ep: wlb.ep, cancel: cancel, wg: &sync.WaitGroup{}}
|
|
|
|
|
|
|
|
for k := range r.serversByAddrPort {
|
|
|
|
|
|
|
|
started.wg.Add(1)
|
|
|
|
|
|
|
|
go r.allocateSingleServer(ctx, started.wg, k, wlb)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
r.allocWorkByEndpoint[wlb.ep] = started
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
|
|
|
started.wg.Wait()
|
|
|
|
|
|
|
|
relayManagerInputEvent(r, ctx, &r.allocateWorkDoneCh, relayEndpointAllocWorkDoneEvent{work: started})
|
|
|
|
|
|
|
|
// cleanup context cancellation must come after the
|
|
|
|
|
|
|
|
// relayManagerInputEvent call, otherwise it returns early without
|
|
|
|
|
|
|
|
// writing the event to runLoop().
|
|
|
|
|
|
|
|
started.cancel()
|
|
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type errNotReady struct{ retryAfter time.Duration }
|
|
|
|
func (r *relayManager) allocateServerEndpoint(work *relayEndpointAllocWork, generation uint32) {
|
|
|
|
|
|
|
|
done := relayEndpointAllocWorkDoneEvent{work: work}
|
|
|
|
|
|
|
|
|
|
|
|
func (e errNotReady) Error() string {
|
|
|
|
defer func() {
|
|
|
|
return fmt.Sprintf("server not ready, retry after %v", e.retryAfter)
|
|
|
|
work.doneCh <- done
|
|
|
|
}
|
|
|
|
relayManagerInputEvent(r, work.ctx, &r.allocateWorkDoneCh, done)
|
|
|
|
|
|
|
|
work.cancel()
|
|
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
|
|
const reqTimeout = time.Second * 10
|
|
|
|
dm := &disco.AllocateUDPRelayEndpointRequest{
|
|
|
|
|
|
|
|
ClientDisco: work.discoKeys.Get(),
|
|
|
|
|
|
|
|
Generation: generation,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func doAllocate(ctx context.Context, server netip.AddrPort, discoKeys [2]key.DiscoPublic) (udprelay.ServerEndpoint, error) {
|
|
|
|
sendAllocReq := func() {
|
|
|
|
var reqBody bytes.Buffer
|
|
|
|
work.wlb.ep.c.sendDiscoAllocateUDPRelayEndpointRequest(
|
|
|
|
type allocateRelayEndpointReq struct {
|
|
|
|
epAddr{
|
|
|
|
DiscoKeys []key.DiscoPublic
|
|
|
|
ap: netip.AddrPortFrom(tailcfg.DerpMagicIPAddr, work.candidatePeerRelay.derpHomeRegionID),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
work.candidatePeerRelay.nodeKey,
|
|
|
|
|
|
|
|
work.candidatePeerRelay.discoKey,
|
|
|
|
|
|
|
|
dm,
|
|
|
|
|
|
|
|
discoVerboseLog,
|
|
|
|
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
a := &allocateRelayEndpointReq{
|
|
|
|
go sendAllocReq()
|
|
|
|
DiscoKeys: []key.DiscoPublic{discoKeys[0], discoKeys[1]},
|
|
|
|
|
|
|
|
|
|
|
|
returnAfterTimer := time.NewTimer(allocateUDPRelayEndpointRequestTimeout)
|
|
|
|
|
|
|
|
defer returnAfterTimer.Stop()
|
|
|
|
|
|
|
|
// While connections to DERP are over TCP, they can be lossy on the DERP
|
|
|
|
|
|
|
|
// server when data moves between the two independent streams. Also, the
|
|
|
|
|
|
|
|
// peer relay server may not be "ready" (see [tailscale.com/net/udprelay.ErrServerNotReady]).
|
|
|
|
|
|
|
|
// So, start a timer to retry once if needed.
|
|
|
|
|
|
|
|
retryAfterTimer := time.NewTimer(udprelay.ServerRetryAfter)
|
|
|
|
|
|
|
|
defer retryAfterTimer.Stop()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
|
|
|
select {
|
|
|
|
|
|
|
|
case <-work.ctx.Done():
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
case <-returnAfterTimer.C:
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
case <-retryAfterTimer.C:
|
|
|
|
|
|
|
|
go sendAllocReq()
|
|
|
|
|
|
|
|
case resp := <-work.rxDiscoMsgCh:
|
|
|
|
|
|
|
|
if resp.Generation != generation ||
|
|
|
|
|
|
|
|
!work.discoKeys.Equal(key.NewSortedPairOfDiscoPublic(resp.ClientDisco[0], resp.ClientDisco[1])) {
|
|
|
|
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
err := json.NewEncoder(&reqBody).Encode(a)
|
|
|
|
done.allocated = udprelay.ServerEndpoint{
|
|
|
|
if err != nil {
|
|
|
|
ServerDisco: resp.ServerDisco,
|
|
|
|
return udprelay.ServerEndpoint{}, err
|
|
|
|
ClientDisco: resp.ClientDisco,
|
|
|
|
|
|
|
|
LamportID: resp.LamportID,
|
|
|
|
|
|
|
|
AddrPorts: resp.AddrPorts,
|
|
|
|
|
|
|
|
VNI: resp.VNI,
|
|
|
|
|
|
|
|
BindLifetime: tstime.GoDuration{Duration: resp.BindLifetime},
|
|
|
|
|
|
|
|
SteadyStateLifetime: tstime.GoDuration{Duration: resp.SteadyStateLifetime},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
reqCtx, cancel := context.WithTimeout(ctx, reqTimeout)
|
|
|
|
return
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
req, err := http.NewRequestWithContext(reqCtx, httpm.POST, "http://"+server.String()+"/v0/relay/endpoint", &reqBody)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return udprelay.ServerEndpoint{}, err
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return udprelay.ServerEndpoint{}, err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
switch resp.StatusCode {
|
|
|
|
|
|
|
|
case http.StatusOK:
|
|
|
|
|
|
|
|
var se udprelay.ServerEndpoint
|
|
|
|
|
|
|
|
err = json.NewDecoder(io.LimitReader(resp.Body, 4096)).Decode(&se)
|
|
|
|
|
|
|
|
return se, err
|
|
|
|
|
|
|
|
case http.StatusServiceUnavailable:
|
|
|
|
|
|
|
|
raHeader := resp.Header.Get("Retry-After")
|
|
|
|
|
|
|
|
raSeconds, err := strconv.ParseUint(raHeader, 10, 32)
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
|
|
|
return udprelay.ServerEndpoint{}, errNotReady{retryAfter: time.Second * time.Duration(raSeconds)}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
fallthrough
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
return udprelay.ServerEndpoint{}, fmt.Errorf("non-200 status: %d", resp.StatusCode)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (r *relayManager) allocateSingleServer(ctx context.Context, wg *sync.WaitGroup, server netip.AddrPort, wlb endpointWithLastBest) {
|
|
|
|
func (r *relayManager) allocateAllServersRunLoop(wlb endpointWithLastBest) {
|
|
|
|
// TODO(jwhited): introduce client metrics counters for notable failures
|
|
|
|
if len(r.serversByNodeKey) == 0 {
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
remoteDisco := wlb.ep.disco.Load()
|
|
|
|
|
|
|
|
if remoteDisco == nil {
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
firstTry := true
|
|
|
|
remoteDisco := wlb.ep.disco.Load()
|
|
|
|
for {
|
|
|
|
if remoteDisco == nil {
|
|
|
|
se, err := doAllocate(ctx, server, [2]key.DiscoPublic{wlb.ep.c.discoPublic, remoteDisco.key})
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
|
|
|
relayManagerInputEvent(r, ctx, &r.newServerEndpointCh, newRelayServerEndpointEvent{
|
|
|
|
|
|
|
|
wlb: wlb,
|
|
|
|
|
|
|
|
se: se,
|
|
|
|
|
|
|
|
server: server, // we allocated this endpoint (vs CallMeMaybeVia reception), mark it as such
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
wlb.ep.c.logf("[v1] magicsock: relayManager: error allocating endpoint on %v for %v: %v", server, wlb.ep.discoShort(), err)
|
|
|
|
discoKeys := key.NewSortedPairOfDiscoPublic(wlb.ep.c.discoPublic, remoteDisco.key)
|
|
|
|
var notReady errNotReady
|
|
|
|
for _, v := range r.serversByNodeKey {
|
|
|
|
if firstTry && errors.As(err, ¬Ready) {
|
|
|
|
byDiscoKeys, ok := r.allocWorkByDiscoKeysByServerNodeKey[v.nodeKey]
|
|
|
|
select {
|
|
|
|
if !ok {
|
|
|
|
case <-ctx.Done():
|
|
|
|
byDiscoKeys = make(map[key.SortedPairOfDiscoPublic]*relayEndpointAllocWork)
|
|
|
|
return
|
|
|
|
r.allocWorkByDiscoKeysByServerNodeKey[v.nodeKey] = byDiscoKeys
|
|
|
|
case <-time.After(min(notReady.retryAfter, reqTimeout)):
|
|
|
|
} else {
|
|
|
|
firstTry = false
|
|
|
|
_, ok = byDiscoKeys[discoKeys]
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
|
|
|
|
// If there is an existing key, a disco key collision may have
|
|
|
|
|
|
|
|
// occurred across peers ([*endpoint]). Do not overwrite the
|
|
|
|
|
|
|
|
// existing work, let it finish.
|
|
|
|
|
|
|
|
wlb.ep.c.logf("[unexpected] magicsock: relayManager: suspected disco key collision on server %v for keys: %v", v.nodeKey.ShortString(), discoKeys)
|
|
|
|
continue
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
|
|
|
started := &relayEndpointAllocWork{
|
|
|
|
|
|
|
|
wlb: wlb,
|
|
|
|
|
|
|
|
discoKeys: discoKeys,
|
|
|
|
|
|
|
|
candidatePeerRelay: v,
|
|
|
|
|
|
|
|
rxDiscoMsgCh: make(chan *disco.AllocateUDPRelayEndpointResponse),
|
|
|
|
|
|
|
|
doneCh: make(chan relayEndpointAllocWorkDoneEvent, 1),
|
|
|
|
|
|
|
|
ctx: ctx,
|
|
|
|
|
|
|
|
cancel: cancel,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
byDiscoKeys[discoKeys] = started
|
|
|
|
|
|
|
|
byCandidatePeerRelay, ok := r.allocWorkByCandidatePeerRelayByEndpoint[wlb.ep]
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
|
|
byCandidatePeerRelay = make(map[candidatePeerRelay]*relayEndpointAllocWork)
|
|
|
|
|
|
|
|
r.allocWorkByCandidatePeerRelayByEndpoint[wlb.ep] = byCandidatePeerRelay
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
byCandidatePeerRelay[v] = started
|
|
|
|
|
|
|
|
r.allocGeneration++
|
|
|
|
|
|
|
|
go r.allocateServerEndpoint(started, r.allocGeneration)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|