diff --git a/client/internal/peer/conn.go b/client/internal/peer/conn.go index 315567263..5029960e1 100644 --- a/client/internal/peer/conn.go +++ b/client/internal/peer/conn.go @@ -97,6 +97,10 @@ type ConnConfig struct { ICEConfig icemaker.Config } +func (c ConnConfig) IsController() bool { + return c.LocalKey > c.Key +} + // Conn represents a connection to a remote peer. All mutable connection state // is owned by a single event loop goroutine started in Open; external callers // and the transport workers communicate with the loop by posting events into @@ -120,9 +124,9 @@ type Conn struct { onDisconnected func(remotePeer string) rosenpassInitializedPresharedKeyValidator func(peerKey string) bool - statusRelay *worker.AtomicWorkerStatus - statusICE *worker.AtomicWorkerStatus - currentConnPriority ConnPriority + statusRelay *AtomicWorkerStatus + statusICE *AtomicWorkerStatus + currentConnPriority worker.ConnPriority opened bool // this flag is used to prevent close in case of not opened connection // mailbox delivers events to the event loop of the current open @@ -130,8 +134,8 @@ type Conn struct { mailbox atomic.Pointer[mailbox] loopDone chan struct{} - workerICE *WorkerICE - workerRelay *WorkerRelay + workerICE *worker.ICE + workerRelay *worker.WorkerRelay // relayDialInFlight and pendingRelayOffer serialize the blocking relay // dials spawned by the event loop, keeping only the newest offer while a @@ -189,10 +193,10 @@ func NewConn(config ConnConfig, services ServiceDependencies) (*Conn, error) { relayManager: services.RelayManager, srWatcher: services.SrWatcher, portForwardManager: services.PortForwardManager, - statusRelay: worker.NewAtomicStatus(), - statusICE: worker.NewAtomicStatus(), + statusRelay: NewAtomicStatus(), + statusICE: NewAtomicStatus(), dumpState: dumpState, - endpointUpdater: NewEndpointUpdater(connLog, config.WgConfig, isController(config)), + endpointUpdater: NewEndpointUpdater(connLog, config.WgConfig, config.IsController()), wgWatcher: wg_watcher.NewWGWatcher(connLog, config.WgConfig.WgInterface, config.Key, dumpState), metricsRecorder: services.MetricsRecorder, } @@ -226,11 +230,16 @@ func (conn *Conn) open(engineCtx context.Context, firstPacket []byte) error { conn.ctx, conn.ctxCancel = context.WithCancel(engineCtx) - conn.workerRelay = NewWorkerRelay(conn.ctx, conn.Log, isController(conn.config), conn.config, conn, conn.relayManager) + conn.workerRelay = worker.NewWorkerRelay(conn.Log, conn.config.Key, conn.config.IsController(), conn.onRelayConnectionIsReady, conn.onRelayDisconnected, conn.relayManager) if !IsForceRelayed() { relayIsSupportedLocally := conn.workerRelay.RelayIsSupportedLocally() - workerICE, err := NewWorkerICE(conn.ctx, conn.Log, conn.config, conn, conn.signaler, conn.iFaceDiscover, conn.statusRecorder, conn.portForwardManager, relayIsSupportedLocally) + workerICE, err := worker.NewICE(conn.Log, conn.config.Key, conn.config.ICEConfig, conn.config.IsController(), conn.onICEConnectionIsReady, conn.onICEStateDisconnected, worker.ICEDependencies{ + Signaler: conn.signaler, + IFaceDiscover: conn.iFaceDiscover, + StatusRecorder: conn.statusRecorder, + PortForwardManager: conn.portForwardManager, + }, relayIsSupportedLocally) if err != nil { return err } @@ -507,7 +516,7 @@ func (conn *Conn) releaseEvents(evs []event) { for _, ev := range evs { switch e := ev.(type) { case evRelayReady: - if err := e.info.relayedConn.Close(); err != nil { + if err := e.info.RelayedConn.Close(); err != nil { conn.Log.Warnf("failed to close unnecessary relayed connection: %v", err) } case evClose: @@ -558,7 +567,7 @@ func (conn *Conn) dispatchOfferToICE(offer *signaling.OfferAnswer) { if conn.workerICE == nil || !conn.handshaker.RemoteICESupported() { return } - conn.workerICE.OnNewOffer(offer) + conn.workerICE.OnNewOffer(conn.ctx, offer) } // dispatchOfferToRelay runs the blocking relay dial on a helper goroutine. A @@ -572,7 +581,7 @@ func (conn *Conn) dispatchOfferToRelay(offer *signaling.OfferAnswer) { conn.relayDialInFlight = true go func() { - conn.workerRelay.OnNewOffer(offer) + conn.workerRelay.OnNewOffer(conn.ctx, offer) conn.post(evRelayDialDone{}) }() } @@ -598,7 +607,7 @@ func (conn *Conn) handleGuardTick() { // handleICEReady starts proxying traffic from/to local WireGuard and sets the // connection status to StatusConnected. -func (conn *Conn) handleICEReady(priority ConnPriority, iceConnInfo ICEConnInfo) { +func (conn *Conn) handleICEReady(priority worker.ConnPriority, iceConnInfo worker.ICEConnInfo) { if conn.ctx.Err() != nil { return } @@ -707,16 +716,16 @@ func (conn *Conn) handleICEDisconnected(sessionChanged bool) { conn.Log.Errorf("failed to switch to relay conn: %v", err) } - conn.currentConnPriority = Relay + conn.currentConnPriority = worker.Relay } else { - conn.Log.Infof("ICE disconnected, do not switch to Relay. Reset priority to: %s", None.String()) - conn.currentConnPriority = None + conn.Log.Infof("ICE disconnected, do not switch to Relay. Reset priority to: %s", worker.None.String()) + conn.currentConnPriority = worker.None if err := conn.config.WgConfig.WgInterface.RemoveEndpointAddress(conn.config.WgConfig.RemoteKey); err != nil { conn.Log.Errorf("failed to remove wg endpoint: %v", err) } } - changed := conn.statusICE.Get() != worker.StatusDisconnected + changed := conn.statusICE.Get() != WorkerStatusDisconnected if changed { conn.guard.SetICEConnDisconnected() } @@ -724,7 +733,7 @@ func (conn *Conn) handleICEDisconnected(sessionChanged bool) { conn.disableWgWatcherIfNeeded() - if conn.currentConnPriority == None { + if conn.currentConnPriority == worker.None { conn.metricsStages.Disconnected() } @@ -741,9 +750,9 @@ func (conn *Conn) handleICEDisconnected(sessionChanged bool) { // handleRelayReady sets up the WireGuard proxy for a freshly opened relayed // connection and activates it unless ICE has priority. -func (conn *Conn) handleRelayReady(rci RelayConnInfo) { +func (conn *Conn) handleRelayReady(rci worker.RelayConnInfo) { if conn.ctx.Err() != nil { - if err := rci.relayedConn.Close(); err != nil { + if err := rci.RelayedConn.Close(); err != nil { conn.Log.Warnf("failed to close unnecessary relayed connection: %v", err) } return @@ -752,7 +761,7 @@ func (conn *Conn) handleRelayReady(rci RelayConnInfo) { conn.dumpState.RelayConnected() conn.Log.Debugf("Relay connection has been established, setup the WireGuard") - wgProxy, err := conn.newProxy(rci.relayedConn) + wgProxy, err := conn.newProxy(rci.RelayedConn) if err != nil { conn.Log.Errorf("failed to add relayed net.Conn to local proxy: %v", err) return @@ -767,18 +776,18 @@ func (conn *Conn) handleRelayReady(rci RelayConnInfo) { conn.Log.Debugf("do not switch to relay because current priority is: %s", conn.currentConnPriority.String()) conn.setRelayedProxy(wgProxy) conn.statusRelay.SetConnected() - conn.updateRelayStatus(rci.relayedConn.RemoteAddr().String(), rci.rosenpassPubKey, time.Now()) + conn.updateRelayStatus(rci.RelayedConn.RemoteAddr().String(), rci.RosenpassPubKey, time.Now()) return } - controller := isController(conn.config) + controller := conn.config.IsController() if controller { wgProxy.Work() } updateTime := time.Now() conn.enableWgWatcherIfNeeded(updateTime) - if err := conn.endpointUpdater.ConfigureWGEndpoint(wgProxy.EndpointAddr(), conn.presharedKey(rci.rosenpassPubKey)); err != nil { + if err := conn.endpointUpdater.ConfigureWGEndpoint(wgProxy.EndpointAddr(), conn.presharedKey(rci.RosenpassPubKey)); err != nil { if err := wgProxy.CloseConn(); err != nil { conn.Log.Warnf("Failed to close relay connection: %v", err) } @@ -793,13 +802,13 @@ func (conn *Conn) handleRelayReady(rci RelayConnInfo) { conn.injectPendingFirstPacket(wgProxy, nil) - conn.rosenpassRemoteKey = rci.rosenpassPubKey - conn.currentConnPriority = Relay + conn.rosenpassRemoteKey = rci.RosenpassPubKey + conn.currentConnPriority = worker.Relay conn.statusRelay.SetConnected() conn.setRelayedProxy(wgProxy) - conn.updateRelayStatus(rci.relayedConn.RemoteAddr().String(), rci.rosenpassPubKey, updateTime) + conn.updateRelayStatus(rci.RelayedConn.RemoteAddr().String(), rci.RosenpassPubKey, updateTime) conn.Log.Infof("start to communicate with peer via relay") - conn.doOnConnected(rci.rosenpassPubKey, rci.rosenpassAddr, updateTime) + conn.doOnConnected(rci.RosenpassPubKey, rci.RosenpassAddr, updateTime) } // handleRelayDisconnected cleans up the relayed transport and the WireGuard @@ -811,9 +820,9 @@ func (conn *Conn) handleRelayDisconnected() { conn.Log.Debugf("relay connection is disconnected") - if conn.currentConnPriority == Relay { + if conn.currentConnPriority == worker.Relay { conn.Log.Debugf("clean up WireGuard config") - conn.currentConnPriority = None + conn.currentConnPriority = worker.None if err := conn.config.WgConfig.WgInterface.RemoveEndpointAddress(conn.config.WgConfig.RemoteKey); err != nil { conn.Log.Errorf("failed to remove wg endpoint: %v", err) } @@ -824,7 +833,7 @@ func (conn *Conn) handleRelayDisconnected() { conn.wgProxyRelay = nil } - changed := conn.statusRelay.Get() != worker.StatusDisconnected + changed := conn.statusRelay.Get() != WorkerStatusDisconnected if changed { conn.guard.SetRelayedConnDisconnected() } @@ -832,7 +841,7 @@ func (conn *Conn) handleRelayDisconnected() { conn.disableWgWatcherIfNeeded() - if conn.currentConnPriority == None { + if conn.currentConnPriority == worker.None { conn.metricsStages.Disconnected() } @@ -858,10 +867,10 @@ func (conn *Conn) handleWGTimeout() { // Close the active connection based on current priority switch conn.currentConnPriority { - case Relay: + case worker.Relay: conn.workerRelay.CloseConn() conn.handleRelayDisconnected() - case ICEP2P, ICETurn: + case worker.ICEP2P, worker.ICETurn: conn.workerICE.Close() default: conn.Log.Debugf("No active connection to close on WG timeout") @@ -900,7 +909,7 @@ func (conn *Conn) handleWGCheckSuccess() { conn.wgTimeouts = 0 } -func (conn *Conn) onICEConnectionIsReady(priority ConnPriority, iceConnInfo ICEConnInfo) { +func (conn *Conn) onICEConnectionIsReady(priority worker.ConnPriority, iceConnInfo worker.ICEConnInfo) { conn.post(evICEReady{priority: priority, info: iceConnInfo}) } @@ -910,11 +919,11 @@ func (conn *Conn) onICEStateDisconnected(sessionChanged bool) { // onRelayConnectionIsReady closes the relayed connection when the event loop // is gone and nobody will take ownership of it. -func (conn *Conn) onRelayConnectionIsReady(rci RelayConnInfo) { +func (conn *Conn) onRelayConnectionIsReady(rci worker.RelayConnInfo) { if conn.post(evRelayReady{info: rci}) { return } - if err := rci.relayedConn.Close(); err != nil { + if err := rci.RelayedConn.Close(); err != nil { conn.Log.Warnf("failed to close unnecessary relayed connection: %v", err) } } @@ -984,7 +993,7 @@ func (conn *Conn) updateRelayStatus(relayServerAddr string, rosenpassPubKey []by } } -func (conn *Conn) updateIceState(iceConnInfo ICEConnInfo, updateTime time.Time) { +func (conn *Conn) updateIceState(iceConnInfo worker.ICEConnInfo, updateTime time.Time) { peerState := status.State{ PubKey: conn.config.Key, ConnStatusUpdate: updateTime, @@ -1006,7 +1015,7 @@ func (conn *Conn) updateIceState(iceConnInfo ICEConnInfo, updateTime time.Time) func (conn *Conn) setStatusToDisconnected() { conn.statusRelay.SetDisconnected() conn.statusICE.SetDisconnected() - conn.currentConnPriority = None + conn.currentConnPriority = worker.None peerState := status.State{ PubKey: conn.config.Key, @@ -1039,7 +1048,7 @@ func (conn *Conn) doOnConnected(remoteRosenpassPubKey []byte, remoteRosenpassAdd func (conn *Conn) isRelayed() bool { switch conn.currentConnPriority { - case Relay, ICETurn: + case worker.Relay, worker.ICETurn: return true default: return false @@ -1047,7 +1056,7 @@ func (conn *Conn) isRelayed() bool { } func (conn *Conn) evalStatus() status.ConnStatus { - if conn.statusRelay.Get() == worker.StatusConnected || conn.statusICE.Get() == worker.StatusConnected { + if conn.statusRelay.Get() == WorkerStatusConnected || conn.statusICE.Get() == WorkerStatusConnected { return status.StatusConnected } @@ -1077,10 +1086,10 @@ func (conn *Conn) isConnectedOnAllWay() (status guard.ConnStatus) { return evalConnStatus(connStatusInputs{ forceRelay: IsForceRelayed(), peerUsesRelay: conn.workerRelay.IsRelayConnectionSupportedWithPeer(), - relayConnected: conn.statusRelay.Get() == worker.StatusConnected, + relayConnected: conn.statusRelay.Get() == WorkerStatusConnected, remoteSupportsICE: conn.handshaker.RemoteICESupported(), iceWorkerCreated: iceWorkerCreated, - iceStatusConnecting: conn.statusICE.Get() != worker.StatusDisconnected, + iceStatusConnecting: conn.statusICE.Get() != WorkerStatusDisconnected, iceInProgress: iceInProgress, }) } @@ -1100,7 +1109,7 @@ func (conn *Conn) enableWgWatcherIfNeeded(enabledTime time.Time) { } func (conn *Conn) disableWgWatcherIfNeeded() { - if conn.currentConnPriority == None && conn.wgWatcherCancel != nil { + if conn.currentConnPriority == worker.None && conn.wgWatcherCancel != nil { conn.wgWatcherCancel() conn.wgWatcherCancel = nil } @@ -1122,7 +1131,7 @@ func (conn *Conn) newProxy(remoteConn net.Conn) (wgproxy.Proxy, error) { } func (conn *Conn) resetEndpoint() { - if !isController(conn.config) { + if !conn.config.IsController() { return } conn.Log.Infof("reset wg endpoint") @@ -1133,11 +1142,11 @@ func (conn *Conn) resetEndpoint() { } func (conn *Conn) isReadyToUpgrade() bool { - return conn.wgProxyRelay != nil && conn.currentConnPriority != Relay + return conn.wgProxyRelay != nil && conn.currentConnPriority != worker.Relay } func (conn *Conn) isICEActive() bool { - return (conn.currentConnPriority == ICEP2P || conn.currentConnPriority == ICETurn) && conn.statusICE.Get() == worker.StatusConnected + return (conn.currentConnPriority == worker.ICEP2P || conn.currentConnPriority == worker.ICETurn) && conn.statusICE.Get() == WorkerStatusConnected } func (conn *Conn) handleConfigurationFailure(err error, wgProxy wgproxy.Proxy) { @@ -1177,7 +1186,7 @@ func (conn *Conn) recordConnectionMetrics() { var connType metrics.ConnectionType switch conn.currentConnPriority { - case Relay: + case worker.Relay: connType = metrics.ConnectionTypeRelay default: connType = metrics.ConnectionTypeICE @@ -1225,10 +1234,6 @@ func (conn *Conn) presharedKey(remoteRosenpassKey []byte) *wgtypes.Key { return determKey } -func isController(config ConnConfig) bool { - return config.LocalKey > config.Key -} - func isRosenpassEnabled(remoteRosenpassPubKey []byte) bool { return remoteRosenpassPubKey != nil } diff --git a/client/internal/peer/conn_status.go b/client/internal/peer/conn_status.go index c0e4cfe89..f85d0c873 100644 --- a/client/internal/peer/conn_status.go +++ b/client/internal/peer/conn_status.go @@ -8,7 +8,7 @@ type connStatusInputs struct { peerUsesRelay bool // remote peer advertises relay support AND local has relay relayConnected bool // statusRelay reports Connected (independent of whether peer uses relay) remoteSupportsICE bool // remote peer sent ICE credentials - iceWorkerCreated bool // local WorkerICE exists (false in force-relay mode) + iceWorkerCreated bool // local ICE worker exists (false in force-relay mode) iceStatusConnecting bool // statusICE is anything other than Disconnected iceInProgress bool // a negotiation is currently in flight } diff --git a/client/internal/peer/event.go b/client/internal/peer/event.go index 2e893fc17..8dc1888b2 100644 --- a/client/internal/peer/event.go +++ b/client/internal/peer/event.go @@ -6,6 +6,7 @@ import ( "github.com/pion/ice/v4" "github.com/netbirdio/netbird/client/internal/peer/signaling" + "github.com/netbirdio/netbird/client/internal/peer/worker" "github.com/netbirdio/netbird/route" ) @@ -35,8 +36,8 @@ type evRemoteCandidate struct { } type evICEReady struct { - priority ConnPriority - info ICEConnInfo + priority worker.ConnPriority + info worker.ICEConnInfo } type evICEDown struct { @@ -44,7 +45,7 @@ type evICEDown struct { } type evRelayReady struct { - info RelayConnInfo + info worker.RelayConnInfo } type evRelayDown struct{} diff --git a/client/internal/peer/priority.go b/client/internal/peer/worker/priority.go similarity index 96% rename from client/internal/peer/priority.go rename to client/internal/peer/worker/priority.go index 0aa7be86f..1d678aee2 100644 --- a/client/internal/peer/priority.go +++ b/client/internal/peer/worker/priority.go @@ -1,4 +1,4 @@ -package peer +package worker import ( "fmt" diff --git a/client/internal/peer/worker_ice.go b/client/internal/peer/worker/worker_ice.go similarity index 84% rename from client/internal/peer/worker_ice.go rename to client/internal/peer/worker/worker_ice.go index 47796609b..7e8ff56a7 100644 --- a/client/internal/peer/worker_ice.go +++ b/client/internal/peer/worker/worker_ice.go @@ -1,4 +1,4 @@ -package peer +package worker import ( "context" @@ -21,11 +21,6 @@ import ( "github.com/netbirdio/netbird/route" ) -type iceCallbacks interface { - onICEConnectionIsReady(priority ConnPriority, iceConnInfo ICEConnInfo) - onICEStateDisconnected(sessionChanged bool) -} - type ICEConnInfo struct { RemoteConn net.Conn RosenpassPubKey []byte @@ -38,11 +33,20 @@ type ICEConnInfo struct { RelayedOnLocal bool } -type WorkerICE struct { - ctx context.Context +type ICEDependencies struct { + Signaler *signaling.Signaler + IFaceDiscover stdnet.ExternalIFaceDiscover + StatusRecorder *status.Recorder + PortForwardManager *portforward.Manager +} + +type ICE struct { log *log.Entry - config ConnConfig - conn iceCallbacks + key string + iceConfig icemaker.Config + isController bool + onConnReady func(priority ConnPriority, iceConnInfo ICEConnInfo) + onStatusDisconnect func(sessionChanged bool) signaler *signaling.Signaler iFaceDiscover stdnet.ExternalIFaceDiscover statusRecorder *status.Recorder @@ -72,21 +76,23 @@ type WorkerICE struct { portForwardAttempted bool } -func NewWorkerICE(ctx context.Context, log *log.Entry, config ConnConfig, conn iceCallbacks, signaler *signaling.Signaler, ifaceDiscover stdnet.ExternalIFaceDiscover, statusRecorder *status.Recorder, portForwardManager *portforward.Manager, hasRelayOnLocally bool) (*WorkerICE, error) { +func NewICE(log *log.Entry, key string, iceConfig icemaker.Config, isController bool, onConnReady func(ConnPriority, ICEConnInfo), onStatusDisconnect func(bool), services ICEDependencies, hasRelayOnLocally bool) (*ICE, error) { sessionID, err := icemaker.NewSessionID() if err != nil { return nil, err } - w := &WorkerICE{ - ctx: ctx, + w := &ICE{ log: log, - config: config, - conn: conn, - signaler: signaler, - iFaceDiscover: ifaceDiscover, - statusRecorder: statusRecorder, - portForwardManager: portForwardManager, + key: key, + iceConfig: iceConfig, + isController: isController, + onConnReady: onConnReady, + onStatusDisconnect: onStatusDisconnect, + signaler: services.Signaler, + iFaceDiscover: services.IFaceDiscover, + statusRecorder: services.StatusRecorder, + portForwardManager: services.PortForwardManager, hasRelayOnLocally: hasRelayOnLocally, sessionID: sessionID, } @@ -100,7 +106,7 @@ func NewWorkerICE(ctx context.Context, log *log.Entry, config ConnConfig, conn i return w, nil } -func (w *WorkerICE) OnNewOffer(remoteOfferAnswer *signaling.OfferAnswer) { +func (w *ICE) OnNewOffer(ctx context.Context, remoteOfferAnswer *signaling.OfferAnswer) { w.log.Debugf("OnNewOffer for ICE, serial: %s", remoteOfferAnswer.SessionIDString()) w.muxAgent.Lock() defer w.muxAgent.Unlock() @@ -142,8 +148,8 @@ func (w *WorkerICE) OnNewOffer(remoteOfferAnswer *signaling.OfferAnswer) { if remoteOfferAnswer.SessionID != nil { w.log.Debugf("recreate ICE agent: %s / %s", w.sessionID, *remoteOfferAnswer.SessionID) } - dialerCtx, dialerCancel := context.WithCancel(w.ctx) - agent, err := w.reCreateAgent(dialerCancel, preferredCandidateTypes) + dialerCtx, dialerCancel := context.WithCancel(ctx) + agent, err := w.reCreateAgent(ctx, dialerCancel, preferredCandidateTypes) if err != nil { w.log.Errorf("failed to recreate ICE Agent: %s", err) return @@ -161,10 +167,10 @@ func (w *WorkerICE) OnNewOffer(remoteOfferAnswer *signaling.OfferAnswer) { } // OnRemoteCandidate Handles ICE connection Candidate provided by the remote peer. -func (w *WorkerICE) OnRemoteCandidate(candidate ice.Candidate, haRoutes route.HAMap) { +func (w *ICE) OnRemoteCandidate(candidate ice.Candidate, haRoutes route.HAMap) { w.muxAgent.Lock() defer w.muxAgent.Unlock() - w.log.Debugf("OnRemoteCandidate from peer %s -> %s", w.config.Key, candidate.String()) + w.log.Debugf("OnRemoteCandidate from peer %s -> %s", w.key, candidate.String()) if w.agent == nil { w.log.Warnf("ICE Agent is not initialized yet") return @@ -191,7 +197,7 @@ func (w *WorkerICE) OnRemoteCandidate(candidate ice.Candidate, haRoutes route.HA } } -func (w *WorkerICE) Credentials() signaling.Credentials { +func (w *ICE) Credentials() signaling.Credentials { w.muxAgent.Lock() defer w.muxAgent.Unlock() return signaling.Credentials{ @@ -201,14 +207,14 @@ func (w *WorkerICE) Credentials() signaling.Credentials { } } -func (w *WorkerICE) InProgress() bool { +func (w *ICE) InProgress() bool { w.muxAgent.Lock() defer w.muxAgent.Unlock() return w.agentConnecting } -func (w *WorkerICE) Close() { +func (w *ICE) Close() { w.muxAgent.Lock() defer w.muxAgent.Unlock() @@ -224,10 +230,10 @@ func (w *WorkerICE) Close() { w.agent = nil } -func (w *WorkerICE) reCreateAgent(dialerCancel context.CancelFunc, candidates []ice.CandidateType) (*icemaker.ThreadSafeAgent, error) { +func (w *ICE) reCreateAgent(ctx context.Context, dialerCancel context.CancelFunc, candidates []ice.CandidateType) (*icemaker.ThreadSafeAgent, error) { w.portForwardAttempted = false - agent, err := icemaker.NewAgent(w.ctx, w.iFaceDiscover, w.config.ICEConfig, candidates, w.localUfrag, w.localPwd) + agent, err := icemaker.NewAgent(ctx, w.iFaceDiscover, w.iceConfig, candidates, w.localUfrag, w.localPwd) if err != nil { return nil, fmt.Errorf("create agent: %w", err) } @@ -249,7 +255,7 @@ func (w *WorkerICE) reCreateAgent(dialerCancel context.CancelFunc, candidates [] return agent, nil } -func (w *WorkerICE) getSessionID() icemaker.SessionID { +func (w *ICE) getSessionID() icemaker.SessionID { w.muxAgent.Lock() defer w.muxAgent.Unlock() @@ -259,7 +265,7 @@ func (w *WorkerICE) getSessionID() icemaker.SessionID { // will block until connection succeeded // but it won't release if ICE Agent went into Disconnected or Failed state, // so we have to cancel it with the provided context once agent detected a broken connection -func (w *WorkerICE) connect(ctx context.Context, dialerCancel context.CancelFunc, agent *icemaker.ThreadSafeAgent, remoteOfferAnswer *signaling.OfferAnswer) { +func (w *ICE) connect(ctx context.Context, dialerCancel context.CancelFunc, agent *icemaker.ThreadSafeAgent, remoteOfferAnswer *signaling.OfferAnswer) { w.log.Debugf("gather candidates") if err := agent.GatherCandidates(); err != nil { w.log.Warnf("failed to gather candidates: %s", err) @@ -323,10 +329,10 @@ func (w *WorkerICE) connect(ctx context.Context, dialerCancel context.CancelFunc w.muxAgent.Unlock() w.log.Infof("connection succeeded with offer session: %s", remoteOfferAnswer.SessionIDString()) - w.conn.onICEConnectionIsReady(selectedPriority(pair), ci) + w.onConnReady(selectedPriority(pair), ci) } -func (w *WorkerICE) closeAgent(agent *icemaker.ThreadSafeAgent, cancel context.CancelFunc) bool { +func (w *ICE) closeAgent(agent *icemaker.ThreadSafeAgent, cancel context.CancelFunc) bool { cancel() if err := agent.Close(); err != nil { w.log.Warnf("failed to close ICE agent: %s", err) @@ -352,7 +358,7 @@ func (w *WorkerICE) closeAgent(agent *icemaker.ThreadSafeAgent, cancel context.C return sessionChanged } -func (w *WorkerICE) punchRemoteWGPort(pair *ice.CandidatePair, remoteWgPort int) { +func (w *ICE) punchRemoteWGPort(pair *ice.CandidatePair, remoteWgPort int) { // wait local endpoint configuration time.Sleep(time.Second) addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(pair.Remote.Address(), strconv.Itoa(remoteWgPort))) @@ -361,7 +367,7 @@ func (w *WorkerICE) punchRemoteWGPort(pair *ice.CandidatePair, remoteWgPort int) return } - mux, ok := w.config.ICEConfig.UDPMuxSrflx.(*udpmux.UniversalUDPMuxDefault) + mux, ok := w.iceConfig.UDPMuxSrflx.(*udpmux.UniversalUDPMuxDefault) if !ok { w.log.Warn("invalid udp mux conversion") return @@ -374,7 +380,7 @@ func (w *WorkerICE) punchRemoteWGPort(pair *ice.CandidatePair, remoteWgPort int) // onICECandidate is a callback attached to an ICE Agent to receive new local connection candidates // and then signals them to the remote peer -func (w *WorkerICE) onICECandidate(candidate ice.Candidate) { +func (w *ICE) onICECandidate(candidate ice.Candidate) { // nil means candidate gathering has been ended if candidate == nil { return @@ -383,9 +389,9 @@ func (w *WorkerICE) onICECandidate(candidate ice.Candidate) { // TODO: reported port is incorrect for CandidateTypeHost, makes understanding ICE use via logs confusing as port is ignored w.log.Debugf("discovered local candidate %s", candidate.String()) go func() { - err := w.signaler.SignalICECandidate(candidate, w.config.Key) + err := w.signaler.SignalICECandidate(candidate, w.key) if err != nil { - w.log.Errorf("failed signaling candidate to the remote peer %s %s", w.config.Key, err) + w.log.Errorf("failed signaling candidate to the remote peer %s %s", w.key, err) } }() @@ -395,7 +401,7 @@ func (w *WorkerICE) onICECandidate(candidate ice.Candidate) { } // injectPortForwardedCandidate signals an additional candidate using the pre-created port mapping. -func (w *WorkerICE) injectPortForwardedCandidate(srflxCandidate ice.Candidate) { +func (w *ICE) injectPortForwardedCandidate(srflxCandidate ice.Candidate) { pfManager := w.portForwardManager if pfManager == nil { return @@ -424,7 +430,7 @@ func (w *WorkerICE) injectPortForwardedCandidate(srflxCandidate ice.Candidate) { forwardedCandidate.String(), mapping.InternalPort, mapping.ExternalPort, mapping.NATType, forwardedCandidate.Priority()) go func() { - if err := w.signaler.SignalICECandidate(forwardedCandidate, w.config.Key); err != nil { + if err := w.signaler.SignalICECandidate(forwardedCandidate, w.key); err != nil { w.log.Errorf("signal port-forwarded candidate: %v", err) } }() @@ -432,7 +438,7 @@ func (w *WorkerICE) injectPortForwardedCandidate(srflxCandidate ice.Candidate) { // createForwardedCandidate creates a new server reflexive candidate with the forwarded port. // It uses the NAT gateway's external IP with the forwarded port. -func (w *WorkerICE) createForwardedCandidate(srflxCandidate ice.Candidate, mapping *portforward.Mapping) (ice.Candidate, error) { +func (w *ICE) createForwardedCandidate(srflxCandidate ice.Candidate, mapping *portforward.Mapping) (ice.Candidate, error) { var externalIP string if mapping.ExternalIP != nil && !mapping.ExternalIP.IsUnspecified() { externalIP = mapping.ExternalIP.String() @@ -477,9 +483,9 @@ func (w *WorkerICE) createForwardedCandidate(srflxCandidate ice.Candidate, mappi return candidate, nil } -func (w *WorkerICE) onICESelectedCandidatePair(agent *icemaker.ThreadSafeAgent, c1, c2 ice.Candidate) { +func (w *ICE) onICESelectedCandidatePair(agent *icemaker.ThreadSafeAgent, c1, c2 ice.Candidate) { w.log.Debugf("selected candidate pair [local <-> remote] -> [%s <-> %s], peer %s", c1.String(), c2.String(), - w.config.Key) + w.key) pairStat, ok := agent.GetSelectedCandidatePairStats() if !ok { @@ -488,13 +494,13 @@ func (w *WorkerICE) onICESelectedCandidatePair(agent *icemaker.ThreadSafeAgent, } duration := time.Duration(pairStat.CurrentRoundTripTime * float64(time.Second)) - if err := w.statusRecorder.UpdateLatency(w.config.Key, duration); err != nil { + if err := w.statusRecorder.UpdateLatency(w.key, duration); err != nil { w.log.Debugf("failed to update latency for peer: %s", err) return } } -func (w *WorkerICE) logSuccessfulPaths(agent *icemaker.ThreadSafeAgent) { +func (w *ICE) logSuccessfulPaths(agent *icemaker.ThreadSafeAgent) { sessionID := w.getSessionID() stats := agent.GetCandidatePairsStats() localCandidates, _ := agent.GetLocalCandidates() @@ -525,7 +531,7 @@ func (w *WorkerICE) logSuccessfulPaths(agent *icemaker.ThreadSafeAgent) { } } -func (w *WorkerICE) onConnectionStateChange(agent *icemaker.ThreadSafeAgent, dialerCancel context.CancelFunc) func(ice.ConnectionState) { +func (w *ICE) onConnectionStateChange(agent *icemaker.ThreadSafeAgent, dialerCancel context.CancelFunc) func(ice.ConnectionState) { // per-agent state; pion delivers callbacks of one agent sequentially var connected bool return func(state ice.ConnectionState) { @@ -556,13 +562,13 @@ func (w *WorkerICE) onConnectionStateChange(agent *icemaker.ThreadSafeAgent, dia w.log.Debugf("suppress disconnected event of replaced ICE agent") return } - w.conn.onICEStateDisconnected(sessionChanged) + w.onStatusDisconnect(sessionChanged) } } } -func (w *WorkerICE) turnAgentDial(ctx context.Context, agent *icemaker.ThreadSafeAgent, remoteOfferAnswer *signaling.OfferAnswer) (*ice.Conn, error) { - if isController(w.config) { +func (w *ICE) turnAgentDial(ctx context.Context, agent *icemaker.ThreadSafeAgent, remoteOfferAnswer *signaling.OfferAnswer) (*ice.Conn, error) { + if w.isController { return agent.Dial(ctx, remoteOfferAnswer.IceCredentials.UFrag, remoteOfferAnswer.IceCredentials.Pwd) } else { return agent.Accept(ctx, remoteOfferAnswer.IceCredentials.UFrag, remoteOfferAnswer.IceCredentials.Pwd) diff --git a/client/internal/peer/worker_relay.go b/client/internal/peer/worker/worker_relay.go similarity index 71% rename from client/internal/peer/worker_relay.go rename to client/internal/peer/worker/worker_relay.go index 255d12d5e..c32748656 100644 --- a/client/internal/peer/worker_relay.go +++ b/client/internal/peer/worker/worker_relay.go @@ -1,4 +1,4 @@ -package peer +package worker import ( "context" @@ -15,23 +15,18 @@ import ( ) type RelayConnInfo struct { - relayedConn net.Conn - rosenpassPubKey []byte - rosenpassAddr string -} - -type relayCallbacks interface { - onRelayConnectionIsReady(rci RelayConnInfo) - onRelayDisconnected() + RelayedConn net.Conn + RosenpassPubKey []byte + RosenpassAddr string } type WorkerRelay struct { - peerCtx context.Context - log *log.Entry - isController bool - config ConnConfig - conn relayCallbacks - relayManager *relayClient.Manager + log *log.Entry + key string + isController bool + onConnReady func(RelayConnInfo) + onDisconnected func() + relayManager *relayClient.Manager relayedConn net.Conn relayLock sync.Mutex @@ -39,19 +34,19 @@ type WorkerRelay struct { relaySupportedOnRemotePeer atomic.Bool } -func NewWorkerRelay(ctx context.Context, log *log.Entry, ctrl bool, config ConnConfig, conn relayCallbacks, relayManager *relayClient.Manager) *WorkerRelay { +func NewWorkerRelay(log *log.Entry, key string, isController bool, onConnReady func(RelayConnInfo), onDisconnected func(), relayManager *relayClient.Manager) *WorkerRelay { r := &WorkerRelay{ - peerCtx: ctx, - log: log, - isController: ctrl, - config: config, - conn: conn, - relayManager: relayManager, + log: log, + key: key, + isController: isController, + onConnReady: onConnReady, + onDisconnected: onDisconnected, + relayManager: relayManager, } return r } -func (w *WorkerRelay) OnNewOffer(remoteOfferAnswer *signaling.OfferAnswer) { +func (w *WorkerRelay) OnNewOffer(ctx context.Context, remoteOfferAnswer *signaling.OfferAnswer) { if !w.isRelaySupported(remoteOfferAnswer) { w.log.Infof("Relay is not supported by remote peer") w.relaySupportedOnRemotePeer.Store(false) @@ -72,7 +67,7 @@ func (w *WorkerRelay) OnNewOffer(remoteOfferAnswer *signaling.OfferAnswer) { serverIP = remoteOfferAnswer.RelaySrvIP } - relayedConn, err := w.relayManager.OpenConn(w.peerCtx, srv, w.config.Key, serverIP) + relayedConn, err := w.relayManager.OpenConn(ctx, srv, w.key, serverIP) if err != nil { if errors.Is(err, relayClient.ErrConnAlreadyExists) { w.log.Debugf("handled offer by reusing existing relay connection") @@ -94,10 +89,10 @@ func (w *WorkerRelay) OnNewOffer(remoteOfferAnswer *signaling.OfferAnswer) { } w.log.Debugf("peer conn opened via Relay: %s", srv) - w.conn.onRelayConnectionIsReady(RelayConnInfo{ - relayedConn: relayedConn, - rosenpassPubKey: remoteOfferAnswer.RosenpassPubKey, - rosenpassAddr: remoteOfferAnswer.RosenpassAddr, + w.onConnReady(RelayConnInfo{ + RelayedConn: relayedConn, + RosenpassPubKey: remoteOfferAnswer.RosenpassPubKey, + RosenpassAddr: remoteOfferAnswer.RosenpassAddr, }) } @@ -140,5 +135,5 @@ func (w *WorkerRelay) preferredRelayServer(myRelayAddress, remoteRelayAddress st } func (w *WorkerRelay) onRelayClientDisconnected() { - w.conn.onRelayDisconnected() + w.onDisconnected() } diff --git a/client/internal/peer/worker/state.go b/client/internal/peer/worker_status.go similarity index 64% rename from client/internal/peer/worker/state.go rename to client/internal/peer/worker_status.go index 14b53aa4e..54d609f80 100644 --- a/client/internal/peer/worker/state.go +++ b/client/internal/peer/worker_status.go @@ -1,4 +1,4 @@ -package worker +package peer import ( "sync/atomic" @@ -7,17 +7,17 @@ import ( ) const ( - StatusDisconnected Status = iota - StatusConnected + WorkerStatusDisconnected WorkerStatus = iota + WorkerStatusConnected ) -type Status int32 +type WorkerStatus int32 -func (s Status) String() string { +func (s WorkerStatus) String() string { switch s { - case StatusDisconnected: + case WorkerStatusDisconnected: return "Disconnected" - case StatusConnected: + case WorkerStatusConnected: return "Connected" default: log.Errorf("unknown status: %d", s) @@ -37,16 +37,16 @@ func NewAtomicStatus() *AtomicWorkerStatus { } // Get returns the current connection status -func (acs *AtomicWorkerStatus) Get() Status { - return Status(acs.status.Load()) +func (acs *AtomicWorkerStatus) Get() WorkerStatus { + return WorkerStatus(acs.status.Load()) } func (acs *AtomicWorkerStatus) SetConnected() { - acs.status.Store(int32(StatusConnected)) + acs.status.Store(int32(WorkerStatusConnected)) } func (acs *AtomicWorkerStatus) SetDisconnected() { - acs.status.Store(int32(StatusDisconnected)) + acs.status.Store(int32(WorkerStatusDisconnected)) } // String returns the string representation of the current status