2024-06-17 17:52:22 +02:00
|
|
|
package peer
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"math/rand"
|
|
|
|
"net"
|
|
|
|
"net/netip"
|
|
|
|
"runtime"
|
2024-06-18 17:40:37 +02:00
|
|
|
"sync"
|
2024-06-17 17:52:22 +02:00
|
|
|
"sync/atomic"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/pion/ice/v3"
|
2024-06-19 11:28:01 +02:00
|
|
|
"github.com/pion/randutil"
|
2024-06-17 17:52:22 +02:00
|
|
|
"github.com/pion/stun/v2"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
|
|
|
|
"github.com/netbirdio/netbird/client/internal/stdnet"
|
|
|
|
"github.com/netbirdio/netbird/iface"
|
|
|
|
"github.com/netbirdio/netbird/iface/bind"
|
|
|
|
"github.com/netbirdio/netbird/route"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
iceKeepAliveDefault = 4 * time.Second
|
|
|
|
iceDisconnectedTimeoutDefault = 6 * time.Second
|
|
|
|
// iceRelayAcceptanceMinWaitDefault is the same as in the Pion ICE package
|
|
|
|
iceRelayAcceptanceMinWaitDefault = 2 * time.Second
|
2024-06-19 11:28:01 +02:00
|
|
|
|
|
|
|
lenUFrag = 16
|
|
|
|
lenPwd = 32
|
|
|
|
runesAlpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
failedTimeout = 6 * time.Second
|
2024-06-17 17:52:22 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type ICEConfig struct {
|
|
|
|
// StunTurn is a list of STUN and TURN URLs
|
|
|
|
StunTurn atomic.Value // []*stun.URI
|
|
|
|
|
|
|
|
// InterfaceBlackList is a list of machine interfaces that should be filtered out by ICE Candidate gathering
|
|
|
|
// (e.g. if eth0 is in the list, host candidate of this interface won't be used)
|
|
|
|
InterfaceBlackList []string
|
|
|
|
DisableIPv6Discovery bool
|
|
|
|
|
|
|
|
UDPMux ice.UDPMux
|
|
|
|
UDPMuxSrflx ice.UniversalUDPMux
|
|
|
|
|
|
|
|
NATExternalIPs []string
|
|
|
|
}
|
|
|
|
|
|
|
|
type OnICEConnReadyCallback func(ConnPriority, ICEConnInfo)
|
|
|
|
|
|
|
|
type ICEConnInfo struct {
|
|
|
|
RemoteConn net.Conn
|
|
|
|
RosenpassPubKey []byte
|
|
|
|
RosenpassAddr string
|
|
|
|
LocalIceCandidateType string
|
|
|
|
RemoteIceCandidateType string
|
|
|
|
RemoteIceCandidateEndpoint string
|
|
|
|
LocalIceCandidateEndpoint string
|
|
|
|
Direct bool
|
|
|
|
Relayed bool
|
|
|
|
RelayedOnLocal bool
|
|
|
|
}
|
|
|
|
|
2024-06-18 11:22:40 +02:00
|
|
|
type WorkerICE struct {
|
2024-06-18 17:40:37 +02:00
|
|
|
ctx context.Context
|
|
|
|
log *log.Entry
|
|
|
|
config ConnConfig
|
|
|
|
configICE ICEConfig
|
|
|
|
signaler *Signaler
|
|
|
|
iFaceDiscover stdnet.ExternalIFaceDiscover
|
|
|
|
statusRecorder *Status
|
|
|
|
onICEConnReady OnICEConnReadyCallback
|
|
|
|
onStatusChanged func(ConnStatus)
|
|
|
|
doHandshakeFn DoHandshake
|
|
|
|
|
|
|
|
selectedPriority ConnPriority
|
|
|
|
|
|
|
|
agent *ice.Agent
|
2024-06-19 11:28:01 +02:00
|
|
|
muxAgent sync.Mutex
|
2024-06-17 17:52:22 +02:00
|
|
|
|
|
|
|
StunTurn []*stun.URI
|
|
|
|
|
|
|
|
sentExtraSrflx bool
|
2024-06-19 11:28:01 +02:00
|
|
|
localUfrag string
|
|
|
|
localPwd string
|
2024-06-17 17:52:22 +02:00
|
|
|
}
|
|
|
|
|
2024-06-18 17:40:37 +02:00
|
|
|
func NewWorkerICE(ctx context.Context, log *log.Entry, config ConnConfig, configICE ICEConfig, signaler *Signaler, ifaceDiscover stdnet.ExternalIFaceDiscover, statusRecorder *Status, onICEConnReady OnICEConnReadyCallback, onStatusChanged func(ConnStatus), doHandshakeFn DoHandshake) *WorkerICE {
|
2024-06-18 11:22:40 +02:00
|
|
|
cice := &WorkerICE{
|
2024-06-18 17:40:37 +02:00
|
|
|
ctx: ctx,
|
|
|
|
log: log,
|
|
|
|
config: config,
|
|
|
|
configICE: configICE,
|
|
|
|
signaler: signaler,
|
|
|
|
iFaceDiscover: ifaceDiscover,
|
|
|
|
statusRecorder: statusRecorder,
|
|
|
|
onICEConnReady: onICEConnReady,
|
|
|
|
onStatusChanged: onStatusChanged,
|
|
|
|
doHandshakeFn: doHandshakeFn,
|
2024-06-17 17:52:22 +02:00
|
|
|
}
|
|
|
|
return cice
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetupICEConnection sets up an ICE connection with the remote peer.
|
|
|
|
// If the relay mode is supported then try to connect in p2p way only.
|
|
|
|
// It is trying to reconnection in a loop until the context is canceled.
|
|
|
|
// In case of success connection it will call the onICEConnReady callback.
|
2024-06-18 11:22:40 +02:00
|
|
|
func (w *WorkerICE) SetupICEConnection(hasRelayOnLocally bool) {
|
2024-06-17 17:52:22 +02:00
|
|
|
for {
|
2024-06-18 11:22:40 +02:00
|
|
|
if !w.waitForReconnectTry() {
|
2024-06-17 17:52:22 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-06-18 17:40:37 +02:00
|
|
|
w.onStatusChanged(StatusConnecting)
|
|
|
|
|
2024-06-18 11:22:40 +02:00
|
|
|
remoteOfferAnswer, err := w.doHandshakeFn()
|
2024-06-17 17:52:22 +02:00
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, ErrSignalIsNotReady) {
|
2024-06-18 11:22:40 +02:00
|
|
|
w.log.Infof("signal client isn't ready, skipping connection attempt")
|
2024-06-17 17:52:22 +02:00
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-06-18 11:10:17 +02:00
|
|
|
var preferredCandidateTypes []ice.CandidateType
|
|
|
|
if hasRelayOnLocally && remoteOfferAnswer.RelaySrvAddress != "" {
|
2024-06-18 17:40:37 +02:00
|
|
|
w.selectedPriority = connPriorityICEP2P
|
2024-06-18 11:10:17 +02:00
|
|
|
preferredCandidateTypes = candidateTypesP2P()
|
|
|
|
} else {
|
2024-06-18 17:40:37 +02:00
|
|
|
w.selectedPriority = connPriorityICETurn
|
2024-06-18 11:10:17 +02:00
|
|
|
preferredCandidateTypes = candidateTypes()
|
|
|
|
}
|
|
|
|
|
2024-06-18 11:22:40 +02:00
|
|
|
ctx, ctxCancel := context.WithCancel(w.ctx)
|
2024-06-19 11:28:01 +02:00
|
|
|
w.muxAgent.Lock()
|
2024-06-18 11:22:40 +02:00
|
|
|
agent, err := w.reCreateAgent(ctxCancel, preferredCandidateTypes)
|
2024-06-17 17:52:22 +02:00
|
|
|
if err != nil {
|
|
|
|
ctxCancel()
|
2024-06-19 11:28:01 +02:00
|
|
|
w.muxAgent.Unlock()
|
2024-06-17 17:52:22 +02:00
|
|
|
continue
|
|
|
|
}
|
2024-06-18 11:22:40 +02:00
|
|
|
w.agent = agent
|
2024-06-19 11:28:01 +02:00
|
|
|
// generate credentials for the next loop. Important the credentials are generated before handshake, because
|
|
|
|
// the handshake could provide a cached offer-answer
|
|
|
|
w.localUfrag, w.localPwd, err = generateICECredentials()
|
|
|
|
if err != nil {
|
|
|
|
ctxCancel()
|
|
|
|
w.muxAgent.Unlock()
|
|
|
|
continue
|
|
|
|
}
|
2024-06-18 17:40:37 +02:00
|
|
|
w.muxAgent.Unlock()
|
2024-06-17 17:52:22 +02:00
|
|
|
|
2024-06-18 11:22:40 +02:00
|
|
|
err = w.agent.GatherCandidates()
|
2024-06-17 17:52:22 +02:00
|
|
|
if err != nil {
|
|
|
|
ctxCancel()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2024-06-18 11:22:40 +02:00
|
|
|
remoteConn, err := w.turnAgentDial(remoteOfferAnswer)
|
2024-06-17 17:52:22 +02:00
|
|
|
if err != nil {
|
|
|
|
ctxCancel()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-06-18 11:22:40 +02:00
|
|
|
pair, err := w.agent.GetSelectedCandidatePair()
|
2024-06-17 17:52:22 +02:00
|
|
|
if err != nil {
|
|
|
|
ctxCancel()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if !isRelayCandidate(pair.Local) {
|
|
|
|
// dynamically set remote WireGuard port if other side specified a different one from the default one
|
|
|
|
remoteWgPort := iface.DefaultWgPort
|
|
|
|
if remoteOfferAnswer.WgListenPort != 0 {
|
|
|
|
remoteWgPort = remoteOfferAnswer.WgListenPort
|
|
|
|
}
|
|
|
|
|
|
|
|
// To support old version's with direct mode we attempt to punch an additional role with the remote WireGuard port
|
2024-06-18 11:22:40 +02:00
|
|
|
go w.punchRemoteWGPort(pair, remoteWgPort)
|
2024-06-17 17:52:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ci := ICEConnInfo{
|
|
|
|
RemoteConn: remoteConn,
|
|
|
|
RosenpassPubKey: remoteOfferAnswer.RosenpassPubKey,
|
|
|
|
RosenpassAddr: remoteOfferAnswer.RosenpassAddr,
|
|
|
|
LocalIceCandidateType: pair.Local.Type().String(),
|
|
|
|
RemoteIceCandidateType: pair.Remote.Type().String(),
|
|
|
|
LocalIceCandidateEndpoint: fmt.Sprintf("%s:%d", pair.Local.Address(), pair.Local.Port()),
|
|
|
|
RemoteIceCandidateEndpoint: fmt.Sprintf("%s:%d", pair.Remote.Address(), pair.Remote.Port()),
|
|
|
|
Direct: !isRelayCandidate(pair.Local),
|
|
|
|
Relayed: isRelayed(pair),
|
|
|
|
RelayedOnLocal: isRelayCandidate(pair.Local),
|
|
|
|
}
|
2024-06-18 17:40:37 +02:00
|
|
|
go w.onICEConnReady(w.selectedPriority, ci)
|
2024-06-17 17:52:22 +02:00
|
|
|
|
|
|
|
<-ctx.Done()
|
|
|
|
ctxCancel()
|
2024-06-18 11:22:40 +02:00
|
|
|
_ = w.agent.Close()
|
2024-06-18 17:40:37 +02:00
|
|
|
w.onStatusChanged(StatusDisconnected)
|
2024-06-17 17:52:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// OnRemoteCandidate Handles ICE connection Candidate provided by the remote peer.
|
2024-06-18 11:22:40 +02:00
|
|
|
func (w *WorkerICE) OnRemoteCandidate(candidate ice.Candidate, haRoutes route.HAMap) {
|
2024-06-19 11:28:01 +02:00
|
|
|
w.muxAgent.Lock()
|
|
|
|
defer w.muxAgent.Unlock()
|
2024-06-18 11:22:40 +02:00
|
|
|
w.log.Debugf("OnRemoteCandidate from peer %s -> %s", w.config.Key, candidate.String())
|
|
|
|
if w.agent == nil {
|
2024-06-17 17:52:22 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if candidateViaRoutes(candidate, haRoutes) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-06-18 11:22:40 +02:00
|
|
|
err := w.agent.AddRemoteCandidate(candidate)
|
2024-06-17 17:52:22 +02:00
|
|
|
if err != nil {
|
2024-06-18 11:22:40 +02:00
|
|
|
w.log.Errorf("error while handling remote candidate")
|
2024-06-17 17:52:22 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-18 11:22:40 +02:00
|
|
|
func (w *WorkerICE) GetLocalUserCredentials() (frag string, pwd string, err error) {
|
2024-06-19 11:28:01 +02:00
|
|
|
w.muxAgent.Lock()
|
|
|
|
defer w.muxAgent.Unlock()
|
|
|
|
|
|
|
|
if w.localUfrag != "" && w.localPwd != "" {
|
|
|
|
return w.localUfrag, w.localPwd, nil
|
2024-06-17 17:52:22 +02:00
|
|
|
}
|
2024-06-19 11:28:01 +02:00
|
|
|
|
|
|
|
w.localUfrag, w.localPwd, err = generateICECredentials()
|
|
|
|
return w.localUfrag, w.localPwd, err
|
2024-06-17 17:52:22 +02:00
|
|
|
}
|
|
|
|
|
2024-06-18 11:22:40 +02:00
|
|
|
func (w *WorkerICE) reCreateAgent(ctxCancel context.CancelFunc, relaySupport []ice.CandidateType) (*ice.Agent, error) {
|
|
|
|
transportNet, err := w.newStdNet()
|
2024-06-17 17:52:22 +02:00
|
|
|
if err != nil {
|
2024-06-18 11:22:40 +02:00
|
|
|
w.log.Errorf("failed to create pion's stdnet: %s", err)
|
2024-06-17 17:52:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
iceKeepAlive := iceKeepAlive()
|
|
|
|
iceDisconnectedTimeout := iceDisconnectedTimeout()
|
|
|
|
iceRelayAcceptanceMinWait := iceRelayAcceptanceMinWait()
|
|
|
|
|
|
|
|
agentConfig := &ice.AgentConfig{
|
|
|
|
MulticastDNSMode: ice.MulticastDNSModeDisabled,
|
|
|
|
NetworkTypes: []ice.NetworkType{ice.NetworkTypeUDP4, ice.NetworkTypeUDP6},
|
2024-06-18 11:22:40 +02:00
|
|
|
Urls: w.configICE.StunTurn.Load().([]*stun.URI),
|
2024-06-17 17:52:22 +02:00
|
|
|
CandidateTypes: relaySupport,
|
2024-06-18 11:22:40 +02:00
|
|
|
InterfaceFilter: stdnet.InterfaceFilter(w.configICE.InterfaceBlackList),
|
|
|
|
UDPMux: w.configICE.UDPMux,
|
|
|
|
UDPMuxSrflx: w.configICE.UDPMuxSrflx,
|
|
|
|
NAT1To1IPs: w.configICE.NATExternalIPs,
|
2024-06-17 17:52:22 +02:00
|
|
|
Net: transportNet,
|
2024-06-19 11:28:01 +02:00
|
|
|
FailedTimeout: &failedTimeout,
|
2024-06-17 17:52:22 +02:00
|
|
|
DisconnectedTimeout: &iceDisconnectedTimeout,
|
|
|
|
KeepaliveInterval: &iceKeepAlive,
|
|
|
|
RelayAcceptanceMinWait: &iceRelayAcceptanceMinWait,
|
2024-06-19 11:28:01 +02:00
|
|
|
LocalUfrag: w.localUfrag,
|
|
|
|
LocalPwd: w.localPwd,
|
2024-06-17 17:52:22 +02:00
|
|
|
}
|
|
|
|
|
2024-06-18 11:22:40 +02:00
|
|
|
if w.configICE.DisableIPv6Discovery {
|
2024-06-17 17:52:22 +02:00
|
|
|
agentConfig.NetworkTypes = []ice.NetworkType{ice.NetworkTypeUDP4}
|
|
|
|
}
|
|
|
|
|
2024-06-18 11:22:40 +02:00
|
|
|
w.sentExtraSrflx = false
|
2024-06-17 17:52:22 +02:00
|
|
|
agent, err := ice.NewAgent(agentConfig)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-06-18 11:22:40 +02:00
|
|
|
err = agent.OnCandidate(w.onICECandidate)
|
2024-06-17 17:52:22 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = agent.OnConnectionStateChange(func(state ice.ConnectionState) {
|
2024-06-18 11:22:40 +02:00
|
|
|
w.log.Debugf("ICE ConnectionState has changed to %s", state.String())
|
2024-06-17 17:52:22 +02:00
|
|
|
if state == ice.ConnectionStateFailed || state == ice.ConnectionStateDisconnected {
|
|
|
|
ctxCancel()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-06-18 11:22:40 +02:00
|
|
|
err = agent.OnSelectedCandidatePairChange(w.onICESelectedCandidatePair)
|
2024-06-17 17:52:22 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = agent.OnSuccessfulSelectedPairBindingResponse(func(p *ice.CandidatePair) {
|
2024-06-18 11:22:40 +02:00
|
|
|
err := w.statusRecorder.UpdateLatency(w.config.Key, p.Latency())
|
2024-06-17 17:52:22 +02:00
|
|
|
if err != nil {
|
2024-06-18 11:22:40 +02:00
|
|
|
w.log.Debugf("failed to update latency for peer: %s", err)
|
2024-06-17 17:52:22 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed setting binding response callback: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return agent, nil
|
|
|
|
}
|
|
|
|
|
2024-06-18 11:22:40 +02:00
|
|
|
func (w *WorkerICE) punchRemoteWGPort(pair *ice.CandidatePair, remoteWgPort int) {
|
2024-06-17 17:52:22 +02:00
|
|
|
// wait local endpoint configuration
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pair.Remote.Address(), remoteWgPort))
|
|
|
|
if err != nil {
|
2024-06-18 11:22:40 +02:00
|
|
|
w.log.Warnf("got an error while resolving the udp address, err: %s", err)
|
2024-06-17 17:52:22 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-06-18 11:22:40 +02:00
|
|
|
mux, ok := w.configICE.UDPMuxSrflx.(*bind.UniversalUDPMuxDefault)
|
2024-06-17 17:52:22 +02:00
|
|
|
if !ok {
|
2024-06-18 11:22:40 +02:00
|
|
|
w.log.Warn("invalid udp mux conversion")
|
2024-06-17 17:52:22 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
_, err = mux.GetSharedConn().WriteTo([]byte{0x6e, 0x62}, addr)
|
|
|
|
if err != nil {
|
2024-06-18 11:22:40 +02:00
|
|
|
w.log.Warnf("got an error while sending the punch packet, err: %s", err)
|
2024-06-17 17:52:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// onICECandidate is a callback attached to an ICE Agent to receive new local connection candidates
|
|
|
|
// and then signals them to the remote peer
|
2024-06-18 11:22:40 +02:00
|
|
|
func (w *WorkerICE) onICECandidate(candidate ice.Candidate) {
|
2024-06-17 17:52:22 +02:00
|
|
|
// nil means candidate gathering has been ended
|
|
|
|
if candidate == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: reported port is incorrect for CandidateTypeHost, makes understanding ICE use via logs confusing as port is ignored
|
2024-06-18 11:22:40 +02:00
|
|
|
w.log.Debugf("discovered local candidate %s", candidate.String())
|
2024-06-17 17:52:22 +02:00
|
|
|
go func() {
|
2024-06-18 11:22:40 +02:00
|
|
|
err := w.signaler.SignalICECandidate(candidate, w.config.Key)
|
2024-06-17 17:52:22 +02:00
|
|
|
if err != nil {
|
2024-06-18 11:22:40 +02:00
|
|
|
w.log.Errorf("failed signaling candidate to the remote peer %s %s", w.config.Key, err)
|
2024-06-17 17:52:22 +02:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2024-06-18 11:22:40 +02:00
|
|
|
if !w.shouldSendExtraSrflxCandidate(candidate) {
|
2024-06-17 17:52:22 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// sends an extra server reflexive candidate to the remote peer with our related port (usually the wireguard port)
|
|
|
|
// this is useful when network has an existing port forwarding rule for the wireguard port and this peer
|
|
|
|
extraSrflx, err := extraSrflxCandidate(candidate)
|
|
|
|
if err != nil {
|
2024-06-18 11:22:40 +02:00
|
|
|
w.log.Errorf("failed creating extra server reflexive candidate %s", err)
|
2024-06-17 17:52:22 +02:00
|
|
|
return
|
|
|
|
}
|
2024-06-18 11:22:40 +02:00
|
|
|
w.sentExtraSrflx = true
|
2024-06-17 17:52:22 +02:00
|
|
|
|
|
|
|
go func() {
|
2024-06-18 11:22:40 +02:00
|
|
|
err = w.signaler.SignalICECandidate(extraSrflx, w.config.Key)
|
2024-06-17 17:52:22 +02:00
|
|
|
if err != nil {
|
2024-06-18 11:22:40 +02:00
|
|
|
w.log.Errorf("failed signaling the extra server reflexive candidate: %s", err)
|
2024-06-17 17:52:22 +02:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2024-06-18 11:22:40 +02:00
|
|
|
func (w *WorkerICE) onICESelectedCandidatePair(c1 ice.Candidate, c2 ice.Candidate) {
|
|
|
|
w.log.Debugf("selected candidate pair [local <-> remote] -> [%s <-> %s], peer %s", c1.String(), c2.String(),
|
|
|
|
w.config.Key)
|
2024-06-17 17:52:22 +02:00
|
|
|
}
|
|
|
|
|
2024-06-18 11:22:40 +02:00
|
|
|
func (w *WorkerICE) shouldSendExtraSrflxCandidate(candidate ice.Candidate) bool {
|
|
|
|
if !w.sentExtraSrflx && candidate.Type() == ice.CandidateTypeServerReflexive && candidate.Port() != candidate.RelatedAddress().Port {
|
2024-06-17 17:52:22 +02:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2024-06-18 11:22:40 +02:00
|
|
|
func (w *WorkerICE) turnAgentDial(remoteOfferAnswer *OfferAnswer) (*ice.Conn, error) {
|
|
|
|
isControlling := w.config.LocalKey > w.config.Key
|
2024-06-17 17:52:22 +02:00
|
|
|
if isControlling {
|
2024-06-18 11:22:40 +02:00
|
|
|
return w.agent.Dial(w.ctx, remoteOfferAnswer.IceCredentials.UFrag, remoteOfferAnswer.IceCredentials.Pwd)
|
2024-06-17 17:52:22 +02:00
|
|
|
} else {
|
2024-06-18 11:22:40 +02:00
|
|
|
return w.agent.Accept(w.ctx, remoteOfferAnswer.IceCredentials.UFrag, remoteOfferAnswer.IceCredentials.Pwd)
|
2024-06-17 17:52:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// waitForReconnectTry waits for a random duration before trying to reconnect
|
2024-06-18 11:22:40 +02:00
|
|
|
func (w *WorkerICE) waitForReconnectTry() bool {
|
2024-06-17 17:52:22 +02:00
|
|
|
minWait := 500
|
|
|
|
maxWait := 2000
|
|
|
|
duration := time.Duration(rand.Intn(maxWait-minWait)+minWait) * time.Millisecond
|
2024-06-18 11:20:01 +02:00
|
|
|
|
|
|
|
timeout := time.NewTimer(duration)
|
|
|
|
defer timeout.Stop()
|
|
|
|
|
2024-06-17 17:52:22 +02:00
|
|
|
select {
|
2024-06-18 11:22:40 +02:00
|
|
|
case <-w.ctx.Done():
|
2024-06-17 17:52:22 +02:00
|
|
|
return false
|
2024-06-18 11:20:01 +02:00
|
|
|
case <-timeout.C:
|
2024-06-17 17:52:22 +02:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func extraSrflxCandidate(candidate ice.Candidate) (*ice.CandidateServerReflexive, error) {
|
|
|
|
relatedAdd := candidate.RelatedAddress()
|
|
|
|
return ice.NewCandidateServerReflexive(&ice.CandidateServerReflexiveConfig{
|
|
|
|
Network: candidate.NetworkType().String(),
|
|
|
|
Address: candidate.Address(),
|
|
|
|
Port: relatedAdd.Port,
|
|
|
|
Component: candidate.Component(),
|
|
|
|
RelAddr: relatedAdd.Address,
|
|
|
|
RelPort: relatedAdd.Port,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func candidateViaRoutes(candidate ice.Candidate, clientRoutes route.HAMap) bool {
|
|
|
|
var routePrefixes []netip.Prefix
|
|
|
|
for _, routes := range clientRoutes {
|
|
|
|
if len(routes) > 0 && routes[0] != nil {
|
|
|
|
routePrefixes = append(routePrefixes, routes[0].Network)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
addr, err := netip.ParseAddr(candidate.Address())
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Failed to parse IP address %s: %v", candidate.Address(), err)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, prefix := range routePrefixes {
|
|
|
|
// default route is
|
|
|
|
if prefix.Bits() == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if prefix.Contains(addr) {
|
|
|
|
log.Debugf("Ignoring candidate [%s], its address is part of routed network %s", candidate.String(), prefix)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func candidateTypes() []ice.CandidateType {
|
|
|
|
if hasICEForceRelayConn() {
|
|
|
|
return []ice.CandidateType{ice.CandidateTypeRelay}
|
|
|
|
}
|
|
|
|
// TODO: remove this once we have refactored userspace proxy into the bind package
|
|
|
|
if runtime.GOOS == "ios" {
|
|
|
|
return []ice.CandidateType{ice.CandidateTypeHost, ice.CandidateTypeServerReflexive}
|
|
|
|
}
|
|
|
|
return []ice.CandidateType{ice.CandidateTypeHost, ice.CandidateTypeServerReflexive, ice.CandidateTypeRelay}
|
|
|
|
}
|
|
|
|
|
|
|
|
func candidateTypesP2P() []ice.CandidateType {
|
|
|
|
return []ice.CandidateType{ice.CandidateTypeHost, ice.CandidateTypeServerReflexive}
|
|
|
|
}
|
|
|
|
|
|
|
|
func isRelayCandidate(candidate ice.Candidate) bool {
|
|
|
|
return candidate.Type() == ice.CandidateTypeRelay
|
|
|
|
}
|
|
|
|
|
|
|
|
func isRelayed(pair *ice.CandidatePair) bool {
|
|
|
|
if pair.Local.Type() == ice.CandidateTypeRelay || pair.Remote.Type() == ice.CandidateTypeRelay {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2024-06-19 11:28:01 +02:00
|
|
|
|
|
|
|
func generateICECredentials() (string, string, error) {
|
|
|
|
ufrag, err := randutil.GenerateCryptoRandomString(lenUFrag, runesAlpha)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
pwd, err := randutil.GenerateCryptoRandomString(lenPwd, runesAlpha)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
return ufrag, pwd, nil
|
|
|
|
|
|
|
|
}
|