package socket
import (
"context"
"errors"
"fmt"
"iter"
"net/netip"
"sort"
"sync"
"sync/atomic"
"time"
"github.com/tmc/go-iroh/key"
"github.com/tmc/go-iroh/netaddr"
)
const (
HeartbeatInterval = 5 * time .Second
UpgradeInterval = 60 * time .Second
HolepunchAttemptsInterval = 5 * time .Second
PathMaxIdleTimeout = 15 * time .Second
RelayPathMaxIdleTimeout = 30 * time .Second
ActorMaxIdleTimeout = 60 * time .Second
)
var ErrExtensionNotNegotiated = errors .New ("socket: QUIC extension not negotiated (qng X1/X2/X3 gate)" )
type Connection interface {
SmoothedRTT () time .Duration
Done () <-chan struct {}
RemoteAddr () Addr
}
type multipathConnection interface {
MultipathNegotiated() bool
}
type pathOpeningConnection interface {
OpenPath(context .Context ) error
}
type natTraversalRoundConnection interface {
AddNATTraversalAddress(netip .AddrPort ) error
InitiateNATTraversalRound(context .Context ) ([]netip .AddrPort , error )
}
type natTraversalRemoteAddressConnection interface {
NATTraversalAddresses() ([]netip .AddrPort , error )
}
type natTraversalRemoteAddressSeedConnection interface {
AddRemoteNATTraversalAddress(netip .AddrPort ) error
}
type PathInfo struct {
ID uint32
Validated bool
Addr Addr
HasAddr bool
RTT time .Duration
HasRTT bool
BytesInFlight uint64
HasBytesInFlight bool
BytesSent uint64
HasBytesSent bool
BytesReceived uint64
HasBytesReceived bool
CongestionWindow uint64
HasCongestionWindow bool
LostPackets uint64
LostBytes uint64
HasLoss bool
Selected bool
}
type pathObservingConnection interface {
Paths() []PathInfo
}
type natTraversalAddressConnection interface {
AddNATTraversalAddress(netip .AddrPort ) error
RemoveNATTraversalAddress(netip .AddrPort ) error
}
type RemoteInfo struct {
ID key .EndpointID
Addrs []TransportAddrInfo
}
type ResolvedAddr struct {
Addr netaddr .TransportAddr
Provenance string
}
type ResolveFunc func (ctx context .Context , id key .EndpointID ) iter .Seq2 [ResolvedAddr , error ]
type remoteMessage struct {
addConnection *addConnectionMsg
resolve *resolveMsg
resolved *resolvedMsg
connClosed Connection
}
type addConnectionMsg struct {
conn Connection
reply chan <- (<-chan PathEvent )
}
type resolveMsg struct {
addrs netaddr .EndpointAddr
reply chan <- error
}
type resolvedMsg struct {
addr ResolvedAddr
}
type connState struct {
conn Connection
addr Addr
paths []Addr
hasDirect bool
cancel func ()
}
type RemoteStateActor struct {
id key .EndpointID
selector PathSelector
resolve ResolveFunc
idle time .Duration
metrics *Metrics
watcher *PathWatcher
inbox chan remoteMessage
done chan struct {}
onExit func ()
mu sync .Mutex
paths *RemotePathState
conns map [Connection ]*connState
selected *Addr
localNAT []netip .AddrPort
noHolepunch atomic .Bool
}
func newRemoteStateActor(ctx context .Context , id key .EndpointID , selector PathSelector , resolve ResolveFunc , idle time .Duration , metrics *Metrics , onExit func ()) *RemoteStateActor {
if selector == nil {
selector = BiasedRttPathSelector {}
}
if idle <= 0 {
idle = ActorMaxIdleTimeout
}
a := &RemoteStateActor {
id : id ,
selector : selector ,
resolve : resolve ,
idle : idle ,
metrics : metrics ,
watcher : NewPathWatcher (),
inbox : make (chan remoteMessage , 16 ),
done : make (chan struct {}),
onExit : onExit ,
paths : NewRemotePathState (),
conns : make (map [Connection ]*connState ),
}
go a .run (ctx )
return a
}
func (a *RemoteStateActor ) ID () key .EndpointID { return a .id }
func (a *RemoteStateActor ) donec () <-chan struct {} { return a .done }
func (a *RemoteStateActor ) AddConnection (conn Connection ) (events <-chan PathEvent , ok bool ) {
reply := make (chan (<-chan PathEvent ), 1 )
select {
case a .inbox <- remoteMessage {addConnection : &addConnectionMsg {conn : conn , reply : reply }}:
case <- a .done :
return nil , false
}
select {
case ch := <- reply :
return ch , true
case <- a .done :
return nil , false
}
}
func (a *RemoteStateActor ) ResolveRemote (addr netaddr .EndpointAddr ) error {
reply := make (chan error , 1 )
select {
case a .inbox <- remoteMessage {resolve : &resolveMsg {addrs : addr , reply : reply }}:
case <- a .done :
return context .Canceled
}
select {
case err := <- reply :
return err
case <- a .done :
return context .Canceled
}
}
func (a *RemoteStateActor ) run (ctx context .Context ) {
defer close (a .done )
defer a .watcher .Close ()
defer func () {
a .mu .Lock ()
conns := make ([]*connState , 0 , len (a .conns ))
for _ , cs := range a .conns {
conns = append (conns , cs )
}
a .mu .Unlock ()
for _ , cs := range conns {
cs .cancel ()
}
}()
if a .onExit != nil {
defer a .onExit ()
}
heartbeat := time .NewTicker (HeartbeatInterval )
defer heartbeat .Stop ()
upgrade := time .NewTicker (UpgradeInterval )
defer upgrade .Stop ()
idleTimer := time .NewTimer (a .idle )
defer idleTimer .Stop ()
for {
select {
case <- ctx .Done ():
return
case msg := <- a .inbox :
a .handle (ctx , msg )
case <- idleTimer .C :
a .mu .Lock ()
n := len (a .conns )
a .mu .Unlock ()
if n == 0 {
return
}
resetTimer (idleTimer , a .idle )
case <- heartbeat .C :
a .reselect ()
case <- upgrade .C :
sel , selected := a .SelectedPath ()
switch {
case a .noHolepunch .Load ():
case selected && sel .Kind () == AddrIP :
case selected && sel .Kind () == AddrRelay :
go func () { _ = a .TriggerHolepunch () }()
default :
go func () {
_ = a .ValidateDirectPath (context .Background ())
_ = a .TriggerHolepunch ()
}()
}
a .reselect ()
}
a .mu .Lock ()
n := len (a .conns )
a .mu .Unlock ()
if n > 0 {
resetTimer (idleTimer , a .idle )
}
}
}
func (a *RemoteStateActor ) handle (ctx context .Context , msg remoteMessage ) {
switch {
case msg .addConnection != nil :
a .handleAddConnection (msg .addConnection )
case msg .resolve != nil :
a .handleResolve (ctx , msg .resolve )
case msg .resolved != nil :
a .handleResolved (msg .resolved )
case msg .connClosed != nil :
a .handleConnClosed (msg .connClosed )
}
}
func (a *RemoteStateActor ) handleAddConnection (m *addConnectionMsg ) {
sub , cancel := a .watcher .Subscribe ()
addr := m .conn .RemoteAddr ()
cs := &connState {conn : m .conn , addr : addr , cancel : cancel }
paths := observeMultipathPaths (m .conn )
a .mu .Lock ()
a .conns [m .conn ] = cs
if a .metrics != nil {
a .metrics .numConnsOpened .Add (1 )
}
a .paths .SetOpen (addr )
a .recordPathOpenedLocked (cs , addr )
opened := a .syncMultipathPathsLocked (cs , paths )
a .paths .Prune ()
localNAT := append ([]netip .AddrPort (nil ), a .localNAT ...)
a .mu .Unlock ()
seedNATTraversalAddresses (m .conn , localNAT )
go func (conn Connection ) {
select {
case <- conn .Done ():
select {
case a .inbox <- remoteMessage {connClosed : conn }:
case <- a .done :
}
case <- a .done :
}
}(m .conn )
a .watcher .Send (PathEvent {Kind : PathEventOpened , Addr : addr })
for _ , addr := range opened {
a .watcher .Send (PathEvent {Kind : PathEventOpened , Addr : addr })
}
a .reselect ()
m .reply <- sub
}
func (a *RemoteStateActor ) handleResolve (ctx context .Context , m *resolveMsg ) {
a .mu .Lock ()
for _ , ta := range m .addrs .Addrs () {
if pa , ok := transportToAddr (ta , a .id ); ok {
a .paths .Add (pa )
}
}
a .mu .Unlock ()
if a .resolve == nil {
m .reply <- nil
return
}
streamCtx , cancel := context .WithCancel (ctx )
seq := a .resolve (streamCtx , m .addrs .ID )
if seq == nil {
cancel ()
m .reply <- nil
return
}
go func () {
select {
case <- a .done :
cancel ()
case <- streamCtx .Done ():
}
}()
go a .runResolveStream (streamCtx , cancel , seq )
m .reply <- nil
}
func (a *RemoteStateActor ) runResolveStream (ctx context .Context , cancel context .CancelFunc , seq iter .Seq2 [ResolvedAddr , error ]) {
defer cancel ()
for addr , err := range seq {
if err != nil {
continue
}
select {
case a .inbox <- remoteMessage {resolved : &resolvedMsg {addr : addr }}:
case <- a .done :
return
case <- ctx .Done ():
return
}
}
}
func (a *RemoteStateActor ) handleResolved (m *resolvedMsg ) {
a .mu .Lock ()
if pa , ok := transportToAddr (m .addr .Addr , a .id ); ok {
a .paths .AddWithProvenance (pa , m .addr .Provenance )
}
a .paths .Prune ()
a .mu .Unlock ()
a .reselect ()
}
func (a *RemoteStateActor ) handleConnClosed (conn Connection ) {
a .mu .Lock ()
cs , ok := a .conns [conn ]
if !ok {
a .mu .Unlock ()
return
}
delete (a .conns , conn )
now := time .Now ()
closed := []Addr {cs .addr }
a .paths .SetClosed (cs .addr , now )
a .recordPathClosedLocked (cs .addr )
for _ , addr := range cs .paths {
if a .multipathPathOpenLocked (addr ) {
continue
}
a .paths .SetClosed (addr , now )
a .recordPathClosedLocked (addr )
closed = appendUniqueAddr (closed , addr )
}
if a .selected != nil && a .selected .String () == cs .addr .String () {
a .selected = nil
}
if a .selected != nil {
for _ , addr := range closed {
if a .selected .String () == addr .String () {
a .selected = nil
break
}
}
}
a .mu .Unlock ()
for _ , addr := range closed {
a .watcher .Send (PathEvent {Kind : PathEventClosed , Addr : addr })
}
cs .cancel ()
}
func (a *RemoteStateActor ) recordPathOpenedLocked (cs *connState , addr Addr ) {
if a .metrics == nil {
return
}
switch addr .Kind () {
case AddrIP :
a .metrics .pathsDirect .Add (1 )
a .metrics .transportIPPathsAdded .Add (1 )
if !cs .hasDirect {
cs .hasDirect = true
a .metrics .numConnsDirect .Add (1 )
}
case AddrRelay :
a .metrics .pathsRelay .Add (1 )
a .metrics .transportRelayPathsAdded .Add (1 )
case AddrCustom :
a .metrics .pathsCustom .Add (1 )
a .metrics .transportCustomPathsAdded .Add (1 )
}
}
func (a *RemoteStateActor ) recordPathClosedLocked (addr Addr ) {
if a .metrics == nil {
return
}
a .metrics .numConnsClosed .Add (1 )
switch addr .Kind () {
case AddrIP :
a .metrics .transportIPPathsRemoved .Add (1 )
case AddrRelay :
a .metrics .transportRelayPathsRemoved .Add (1 )
case AddrCustom :
a .metrics .transportCustomPathsRemoved .Add (1 )
}
}
type connPathSnapshot struct {
conn Connection
addr Addr
rtt time .Duration
paths []PathInfo
}
func (a *RemoteStateActor ) connectionPathSnapshots () []connPathSnapshot {
a .mu .Lock ()
conns := make ([]connPathSnapshot , 0 , len (a .conns ))
for _ , cs := range a .conns {
conns = append (conns , connPathSnapshot {conn : cs .conn , addr : cs .addr })
}
a .mu .Unlock ()
for i := range conns {
conns [i ].rtt = conns [i ].conn .SmoothedRTT ()
conns [i ].paths = observeMultipathPaths (conns [i ].conn )
}
return conns
}
func observeMultipathPaths(conn Connection ) []PathInfo {
observer , ok := conn .(pathObservingConnection )
if !ok {
return nil
}
return observer .Paths ()
}
func appendCandidate(candidates []PathCandidate , seen map [string ]struct {}, addr Addr , rtt time .Duration ) []PathCandidate {
k := addr .String ()
if _ , ok := seen [k ]; ok {
return candidates
}
seen [k ] = struct {}{}
return append (candidates , PathCandidate {Addr : addr , RTT : rtt })
}
func appendMultipathCandidates(candidates []PathCandidate , seen map [string ]struct {}, paths []PathInfo , rtt time .Duration ) []PathCandidate {
for _ , p := range paths {
if p .Validated && p .HasAddr {
pathRTT := rtt
if p .HasRTT {
pathRTT = p .RTT
}
candidates = appendCandidate (candidates , seen , p .Addr , pathRTT )
}
}
return candidates
}
func (a *RemoteStateActor ) syncMultipathPathsLocked (cs *connState , paths []PathInfo ) []Addr {
var opened []Addr
for _ , p := range paths {
if !p .Validated || !p .HasAddr {
continue
}
cs .paths = appendUniqueAddr (cs .paths , p .Addr )
if status , known := a .paths .Status (p .Addr ); known && status == PathStatusOpen {
continue
}
a .paths .SetOpen (p .Addr )
a .recordPathOpenedLocked (cs , p .Addr )
opened = appendUniqueAddr (opened , p .Addr )
}
return opened
}
func (a *RemoteStateActor ) multipathPathOpenLocked (addr Addr ) bool {
for _ , cs := range a .conns {
for _ , path := range cs .paths {
if path .String () == addr .String () {
return true
}
}
}
return false
}
func appendUniqueAddr(addrs []Addr , addr Addr ) []Addr {
for _ , a := range addrs {
if a .String () == addr .String () {
return addrs
}
}
return append (addrs , addr )
}
func appendUniqueNATAddr(addrs []netip .AddrPort , addr netip .AddrPort ) []netip .AddrPort {
addr , ok := canonicalNATAddr (addr )
if !ok {
return addrs
}
for _ , a := range addrs {
if a == addr {
return addrs
}
}
return append (addrs , addr )
}
func containsNATAddr(addrs []netip .AddrPort , addr netip .AddrPort ) bool {
for _ , a := range addrs {
if a == addr {
return true
}
}
return false
}
func canonicalNATAddr(addr netip .AddrPort ) (netip .AddrPort , bool ) {
if !addr .IsValid () || addr .Port () == 0 {
return netip .AddrPort {}, false
}
return netip .AddrPortFrom (addr .Addr ().Unmap (), addr .Port ()), true
}
func (a *RemoteStateActor ) reselect () {
snapshots := a .connectionPathSnapshots ()
a .mu .Lock ()
candidates := make ([]PathCandidate , 0 , len (snapshots ))
seen := make (map [string ]struct {}, len (snapshots ))
var opened []Addr
now := time .Now ()
for _ , snap := range snapshots {
cs , ok := a .conns [snap .conn ]
if !ok {
continue
}
a .paths .SetOpenAt (snap .addr , now )
candidates = appendCandidate (candidates , seen , snap .addr , snap .rtt )
opened = append (opened , a .syncMultipathPathsLocked (cs , snap .paths )...)
candidates = appendMultipathCandidates (candidates , seen , snap .paths , snap .rtt )
}
closed := a .paths .ExpireIdle (now )
for _ , addr := range closed {
a .recordPathClosedLocked (addr )
}
a .paths .Prune ()
current := a .selected
selected , ok := a .selector .Select (current , candidates )
if !ok {
a .mu .Unlock ()
for _ , addr := range opened {
a .watcher .Send (PathEvent {Kind : PathEventOpened , Addr : addr })
}
for _ , addr := range closed {
a .watcher .Send (PathEvent {Kind : PathEventClosed , Addr : addr })
}
return
}
if current != nil && current .String () == selected .String () {
a .mu .Unlock ()
for _ , addr := range opened {
a .watcher .Send (PathEvent {Kind : PathEventOpened , Addr : addr })
}
for _ , addr := range closed {
a .watcher .Send (PathEvent {Kind : PathEventClosed , Addr : addr })
}
return
}
sel := selected
a .selected = &sel
a .mu .Unlock ()
for _ , addr := range opened {
a .watcher .Send (PathEvent {Kind : PathEventOpened , Addr : addr })
}
for _ , addr := range closed {
a .watcher .Send (PathEvent {Kind : PathEventClosed , Addr : addr })
}
a .watcher .Send (PathEvent {Kind : PathEventSelected , Addr : selected })
}
func seedNATTraversalAddresses(conn Connection , candidates []netip .AddrPort ) {
if len (candidates ) == 0 {
return
}
mp , ok := conn .(multipathConnection )
if !ok || !mp .MultipathNegotiated () {
return
}
qnt , ok := conn .(natTraversalAddressConnection )
if !ok {
return
}
for _ , addr := range candidates {
_ = qnt .AddNATTraversalAddress (addr )
}
}
func (a *RemoteStateActor ) ValidateDirectPath (ctx context .Context ) error {
a .mu .Lock ()
conns := make ([]Connection , 0 , len (a .conns ))
for conn := range a .conns {
conns = append (conns , conn )
}
a .mu .Unlock ()
negotiated := false
var target pathOpeningConnection
for _ , conn := range conns {
mp , ok := conn .(multipathConnection )
if !ok || !mp .MultipathNegotiated () {
continue
}
negotiated = true
if opener , ok := conn .(pathOpeningConnection ); ok {
target = opener
break
}
}
if !negotiated || target == nil {
return ErrExtensionNotNegotiated
}
ctx , cancel := context .WithTimeout (ctx , HolepunchAttemptsInterval )
defer cancel ()
if err := target .OpenPath (ctx ); err != nil {
return fmt .Errorf ("socket: open direct path: %w" , err )
}
return nil
}
func (a *RemoteStateActor ) TriggerHolepunch () error {
a .mu .Lock ()
conns := make ([]Connection , 0 , len (a .conns ))
for conn := range a .conns {
conns = append (conns , conn )
}
candidates := append ([]netip .AddrPort (nil ), a .localNAT ...)
a .mu .Unlock ()
negotiated := false
var target natTraversalRoundConnection
for _ , conn := range conns {
mp , ok := conn .(multipathConnection )
if !ok || !mp .MultipathNegotiated () {
continue
}
negotiated = true
if qnt , ok := conn .(natTraversalRoundConnection ); ok {
target = qnt
break
}
}
if !negotiated {
return ErrExtensionNotNegotiated
}
if target == nil {
return ErrExtensionNotNegotiated
}
return a .triggerHolepunch (target , candidates )
}
func (a *RemoteStateActor ) TriggerHolepunchConn (conn Connection ) error {
a .mu .Lock ()
_ , registered := a .conns [conn ]
candidates := append ([]netip .AddrPort (nil ), a .localNAT ...)
a .mu .Unlock ()
if !registered {
return context .Canceled
}
mp , ok := conn .(multipathConnection )
if !ok || !mp .MultipathNegotiated () {
return ErrExtensionNotNegotiated
}
target , ok := conn .(natTraversalRoundConnection )
if !ok {
return ErrExtensionNotNegotiated
}
return a .triggerHolepunch (target , candidates )
}
func (a *RemoteStateActor ) triggerHolepunch (target natTraversalRoundConnection , candidates []netip .AddrPort ) error {
if a .metrics != nil {
a .metrics .holepunchAttempts .Add (1 )
}
ctx , cancel := context .WithTimeout (context .Background (), HolepunchAttemptsInterval )
defer cancel ()
for _ , addr := range candidates {
if err := target .AddNATTraversalAddress (addr ); err != nil {
return fmt .Errorf ("socket: add nat traversal address %s: %w" , addr , err )
}
}
if _ , err := target .InitiateNATTraversalRound (ctx ); err != nil {
return fmt .Errorf ("socket: initiate nat traversal round: %w" , err )
}
return nil
}
func (a *RemoteStateActor ) SelectedPath () (Addr , bool ) {
a .mu .Lock ()
defer a .mu .Unlock ()
if a .selected == nil {
return Addr {}, false
}
return *a .selected , true
}
func (a *RemoteStateActor ) RemoteInfo () RemoteInfo {
a .mu .Lock ()
defer a .mu .Unlock ()
return RemoteInfo {
ID : a .id ,
Addrs : a .paths .RemoteAddrs (),
}
}
func (a *RemoteStateActor ) PathInfos (conn Connection ) []PathInfo {
a .mu .Lock ()
cs , ok := a .conns [conn ]
if !ok {
a .mu .Unlock ()
return nil
}
open := append ([]Addr {cs .addr }, cs .paths ...)
var selected *Addr
if a .selected != nil {
sel := *a .selected
selected = &sel
}
a .mu .Unlock ()
infos := make ([]PathInfo , 0 , len (open ))
byAddr := make (map [string ]int , len (open ))
for _ , addr := range open {
if _ , dup := byAddr [addr .String ()]; dup {
continue
}
info := PathInfo {
Validated : true ,
Addr : addr ,
HasAddr : true ,
Selected : selected != nil && selected .String () == addr .String (),
}
byAddr [addr .String ()] = len (infos )
infos = append (infos , info )
}
for _ , p := range observeMultipathPaths (conn ) {
if p .HasAddr {
if i , ok := byAddr [p .Addr .String ()]; ok {
infos [i ].ID = p .ID
infos [i ].Validated = p .Validated
infos [i ].RTT = p .RTT
infos [i ].HasRTT = p .HasRTT
infos [i ].BytesInFlight = p .BytesInFlight
infos [i ].HasBytesInFlight = p .HasBytesInFlight
infos [i ].BytesSent = p .BytesSent
infos [i ].HasBytesSent = p .HasBytesSent
infos [i ].BytesReceived = p .BytesReceived
infos [i ].HasBytesReceived = p .HasBytesReceived
infos [i ].CongestionWindow = p .CongestionWindow
infos [i ].HasCongestionWindow = p .HasCongestionWindow
infos [i ].LostPackets = p .LostPackets
infos [i ].LostBytes = p .LostBytes
infos [i ].HasLoss = p .HasLoss
continue
}
p .Selected = selected != nil && selected .String () == p .Addr .String ()
byAddr [p .Addr .String ()] = len (infos )
infos = append (infos , p )
continue
}
infos = append (infos , p )
}
sort .Slice (infos , func (i , j int ) bool {
if infos [i ].Selected != infos [j ].Selected {
return infos [i ].Selected
}
if infos [i ].HasAddr != infos [j ].HasAddr {
return infos [i ].HasAddr
}
if infos [i ].HasAddr && infos [j ].HasAddr && infos [i ].Addr .String () != infos [j ].Addr .String () {
return infos [i ].Addr .String () < infos [j ].Addr .String ()
}
return infos [i ].ID < infos [j ].ID
})
return infos
}
func (a *RemoteStateActor ) MultipathPaths () []PathInfo {
a .mu .Lock ()
conns := make ([]Connection , 0 , len (a .conns ))
for conn := range a .conns {
conns = append (conns , conn )
}
a .mu .Unlock ()
var paths []PathInfo
for _ , conn := range conns {
observer , ok := conn .(pathObservingConnection )
if !ok {
continue
}
paths = append (paths , observer .Paths ()...)
}
sort .Slice (paths , func (i , j int ) bool {
if paths [i ].ID != paths [j ].ID {
return paths [i ].ID < paths [j ].ID
}
return !paths [i ].Validated && paths [j ].Validated
})
return paths
}
func (a *RemoteStateActor ) NATTraversalAddresses () ([]netip .AddrPort , error ) {
a .mu .Lock ()
conns := make ([]Connection , 0 , len (a .conns ))
for conn := range a .conns {
conns = append (conns , conn )
}
a .mu .Unlock ()
negotiated := false
var out []netip .AddrPort
for _ , conn := range conns {
mp , ok := conn .(multipathConnection )
if !ok || !mp .MultipathNegotiated () {
continue
}
negotiated = true
qnt , ok := conn .(natTraversalRemoteAddressConnection )
if !ok {
continue
}
addrs , err := qnt .NATTraversalAddresses ()
if err != nil {
return nil , err
}
for _ , addr := range addrs {
out = appendUniqueNATAddr (out , addr )
}
}
if !negotiated {
return nil , ErrExtensionNotNegotiated
}
if out == nil {
out = []netip .AddrPort {}
}
return out , nil
}
func (a *RemoteStateActor ) AddRemoteNATTraversalAddresses (addrs []netip .AddrPort ) error {
a .mu .Lock ()
conns := make ([]Connection , 0 , len (a .conns ))
for conn := range a .conns {
conns = append (conns , conn )
}
a .mu .Unlock ()
negotiated := false
var target natTraversalRemoteAddressSeedConnection
for _ , conn := range conns {
mp , ok := conn .(multipathConnection )
if !ok || !mp .MultipathNegotiated () {
continue
}
negotiated = true
if seed , ok := conn .(natTraversalRemoteAddressSeedConnection ); ok {
target = seed
break
}
}
if !negotiated || target == nil {
return ErrExtensionNotNegotiated
}
for _ , addr := range addrs {
if err := target .AddRemoteNATTraversalAddress (addr ); err != nil {
return fmt .Errorf ("socket: add remote nat traversal address %s: %w" , addr , err )
}
}
return nil
}
func (a *RemoteStateActor ) AddNATTraversalAddresses (addrs []netip .AddrPort ) error {
a .mu .Lock ()
var candidates []netip .AddrPort
for _ , addr := range addrs {
canon , ok := canonicalNATAddr (addr )
if !ok {
continue
}
candidates = appendUniqueNATAddr (candidates , canon )
}
var removed []netip .AddrPort
for _ , addr := range a .localNAT {
if !containsNATAddr (candidates , addr ) {
removed = append (removed , addr )
}
}
var added []netip .AddrPort
for _ , addr := range candidates {
if !containsNATAddr (a .localNAT , addr ) {
added = append (added , addr )
}
}
a .localNAT = candidates
conns := make ([]Connection , 0 , len (a .conns ))
for conn := range a .conns {
conns = append (conns , conn )
}
a .mu .Unlock ()
negotiated := false
var target natTraversalAddressConnection
for _ , conn := range conns {
mp , ok := conn .(multipathConnection )
if !ok || !mp .MultipathNegotiated () {
continue
}
negotiated = true
if qnt , ok := conn .(natTraversalAddressConnection ); ok {
target = qnt
break
}
}
if !negotiated {
return ErrExtensionNotNegotiated
}
if target == nil {
return ErrExtensionNotNegotiated
}
if len (added ) != 0 || len (removed ) != 0 {
if a .metrics != nil {
a .metrics .updateDirectAddrs .Add (1 )
}
}
for _ , addr := range removed {
if err := target .RemoveNATTraversalAddress (addr ); err != nil {
return fmt .Errorf ("socket: remove nat traversal address %s: %w" , addr , err )
}
}
for _ , addr := range added {
if err := target .AddNATTraversalAddress (addr ); err != nil {
return fmt .Errorf ("socket: add nat traversal address %s: %w" , addr , err )
}
}
return nil
}
func (a *RemoteStateActor ) SendDatagram (p []byte , send func (Addr , []byte ) bool ) error {
a .mu .Lock ()
var targets []Addr
if a .selected != nil {
targets = []Addr {*a .selected }
} else {
targets = a .paths .Addrs ()
}
a .mu .Unlock ()
for _ , t := range targets {
send (t , p )
}
return nil
}
func (a *RemoteStateActor ) PathEvents () (<-chan PathEvent , func ()) {
return a .watcher .Subscribe ()
}
func resetTimer(t *time .Timer , d time .Duration ) {
if !t .Stop () {
select {
case <- t .C :
default :
}
}
t .Reset (d )
}
func transportToAddr(ta netaddr .TransportAddr , id key .EndpointID ) (Addr , bool ) {
switch v := ta .(type ) {
case netaddr .IPAddr :
return IPAddr (v .Addr ), true
case netaddr .RelayAddr :
return RelayAddr (v .URL , id ), true
case netaddr .CustomAddr :
return CustomAddr (v ), true
default :
return Addr {}, false
}
}
The pages are generated with Golds v0.8.4 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .