single socket ice (#232)

Enables single socket for HOST and SRFLX candidates by utilizing pion.ice UDPMux
This commit is contained in:
Mikhail Bragin
2022-02-16 20:00:21 +01:00
committed by GitHub
parent 765d3a0ad0
commit e5dcd4753e
5 changed files with 107 additions and 40 deletions

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"math/rand"
"net"
"strings"
"sync"
"time"
@@ -22,10 +23,13 @@ import (
)
// PeerConnectionTimeoutMax is a timeout of an initial connection attempt to a remote peer.
// E.g. this peer will wait PeerConnectionTimeoutMax for the remote peer to respond, if not successful then it will retry the connection attempt.
// E.g. this peer will wait PeerConnectionTimeoutMax for the remote peer to respond,
// if not successful then it will retry the connection attempt.
// Todo pass timeout at EnginConfig
const PeerConnectionTimeoutMax = 45000 //ms
const PeerConnectionTimeoutMin = 30000 //ms
const (
PeerConnectionTimeoutMax = 45000 // ms
PeerConnectionTimeoutMin = 30000 // ms
)
const WgPort = 51820
@@ -33,14 +37,23 @@ const WgPort = 51820
type EngineConfig struct {
WgPort int
WgIfaceName string
// WgAddr is a Wireguard local address (Wiretrustee Network IP)
WgAddr string
// WgPrivateKey is a Wireguard private key of our peer (it MUST never leave the machine)
WgPrivateKey wgtypes.Key
// IFaceBlackList is a list of network interfaces to ignore when discovering connection candidates (ICE related)
IFaceBlackList map[string]struct{}
PreSharedKey *wgtypes.Key
// UDPMuxPort default value 0 - the system will pick an available port
UDPMuxPort int
// UDPMuxSrflxPort default value 0 - the system will pick an available port
UDPMuxSrflxPort int
}
// Engine is a mechanism responsible for reacting on Signal and Management stream events and managing connections to the remote peers.
@@ -67,6 +80,11 @@ type Engine struct {
wgInterface iface.WGIface
udpMux ice.UDPMux
udpMuxSrflx ice.UniversalUDPMux
udpMuxConn *net.UDPConn
udpMuxConnSrflx *net.UDPConn
// networkSerial is the latest Serial (state ID) of the network sent by the Management service
networkSerial uint64
}
@@ -78,7 +96,10 @@ type Peer struct {
}
// NewEngine creates a new Connection Engine
func NewEngine(signalClient signal.Client, mgmClient mgm.Client, config *EngineConfig, cancel context.CancelFunc, ctx context.Context) *Engine {
func NewEngine(
signalClient signal.Client, mgmClient mgm.Client, config *EngineConfig,
cancel context.CancelFunc, ctx context.Context,
) *Engine {
return &Engine{
signal: signalClient,
mgmClient: mgmClient,
@@ -111,6 +132,30 @@ func (e *Engine) Stop() error {
}
}
if e.udpMux != nil {
if err := e.udpMux.Close(); err != nil {
log.Debugf("close udp mux: %v", err)
}
}
if e.udpMuxSrflx != nil {
if err := e.udpMuxSrflx.Close(); err != nil {
log.Debugf("close server reflexive udp mux: %v", err)
}
}
if e.udpMuxConn != nil {
if err := e.udpMuxConn.Close(); err != nil {
log.Debugf("close udp mux connection: %v", err)
}
}
if e.udpMuxConnSrflx != nil {
if err := e.udpMuxConnSrflx.Close(); err != nil {
log.Debugf("close server reflexive udp mux connection: %v", err)
}
}
log.Infof("stopped Wiretrustee Engine")
return nil
@@ -134,6 +179,21 @@ func (e *Engine) Start() error {
return err
}
e.udpMuxConn, err = net.ListenUDP("udp4", &net.UDPAddr{Port: e.config.UDPMuxPort})
if err != nil {
log.Errorf("failed listening on UDP port %d: [%s]", e.config.UDPMuxPort, err.Error())
return err
}
e.udpMuxConnSrflx, err = net.ListenUDP("udp4", &net.UDPAddr{Port: e.config.UDPMuxSrflxPort})
if err != nil {
log.Errorf("failed listening on UDP port %d: [%s]", e.config.UDPMuxSrflxPort, err.Error())
return err
}
e.udpMux = ice.NewUDPMuxDefault(ice.UDPMuxParams{UDPConn: e.udpMuxConn})
e.udpMuxSrflx = ice.NewUniversalUDPMuxDefault(ice.UniversalUDPMuxParams{UDPConn: e.udpMuxConnSrflx})
err = e.wgInterface.Create()
if err != nil {
log.Errorf("failed creating tunnel interface %s: [%s]", wgIfaceName, err.Error())
@@ -154,7 +214,6 @@ func (e *Engine) Start() error {
// removePeers finds and removes peers that do not exist anymore in the network map received from the Management Service
func (e *Engine) removePeers(peersUpdate []*mgmProto.RemotePeerConfig) error {
currentPeers := make([]string, 0, len(e.peerConns))
for p := range e.peerConns {
currentPeers = append(currentPeers, p)
@@ -209,7 +268,6 @@ func (e *Engine) removePeer(peerKey string) error {
// GetPeerConnectionStatus returns a connection Status or nil if peer connection wasn't found
func (e *Engine) GetPeerConnectionStatus(peerKey string) peer.ConnStatus {
conn, exists := e.peerConns[peerKey]
if exists && conn != nil {
return conn.Status()
@@ -217,6 +275,7 @@ func (e *Engine) GetPeerConnectionStatus(peerKey string) peer.ConnStatus {
return -1
}
func (e *Engine) GetPeers() []string {
e.syncMsgMux.Lock()
defer e.syncMsgMux.Unlock()
@@ -254,7 +313,7 @@ func signalCandidate(candidate ice.Candidate, myKey wgtypes.Key, remoteKey wgtyp
})
if err != nil {
log.Errorf("failed signaling candidate to the remote peer %s %s", remoteKey.String(), err)
//todo ??
// todo ??
return err
}
@@ -262,7 +321,6 @@ func signalCandidate(candidate ice.Candidate, myKey wgtypes.Key, remoteKey wgtyp
}
func signalAuth(uFrag string, pwd string, myKey wgtypes.Key, remoteKey wgtypes.Key, s signal.Client, isAnswer bool) error {
var t sProto.Body_Type
if isAnswer {
t = sProto.Body_ANSWER
@@ -272,7 +330,8 @@ func signalAuth(uFrag string, pwd string, myKey wgtypes.Key, remoteKey wgtypes.K
msg, err := signal.MarshalCredential(myKey, remoteKey, &signal.Credential{
UFrag: uFrag,
Pwd: pwd}, t)
Pwd: pwd,
}, t)
if err != nil {
return err
}
@@ -299,7 +358,7 @@ func (e *Engine) handleSync(update *mgmProto.SyncResponse) error {
return err
}
//todo update signal
// todo update signal
}
if update.GetNetworkMap() != nil {
@@ -311,7 +370,6 @@ func (e *Engine) handleSync(update *mgmProto.SyncResponse) error {
}
return nil
}
// receiveManagementEvents connects to the Management Service event stream to receive updates from the management service
@@ -371,7 +429,6 @@ func (e *Engine) updateTURNs(turns []*mgmProto.ProtectedHostConfig) error {
}
func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
serial := networkMap.GetSerial()
if e.networkSerial > serial {
log.Debugf("received outdated NetworkMap with serial %d, ignoring", serial)
@@ -455,7 +512,6 @@ func (e Engine) peerExists(peerKey string) bool {
}
func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, error) {
var stunTurn []*ice.URL
stunTurn = append(stunTurn, e.STUNs...)
stunTurn = append(stunTurn, e.TURNs...)
@@ -481,6 +537,8 @@ func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, er
StunTurn: stunTurn,
InterfaceBlackList: interfaceBlacklist,
Timeout: timeout,
UDPMux: e.udpMux,
UDPMuxSrflx: e.udpMuxSrflx,
ProxyConfig: proxyConfig,
}
@@ -515,11 +573,9 @@ func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, er
// receiveSignalEvents connects to the Signal Service event stream to negotiate connection with remote peers
func (e *Engine) receiveSignalEvents() {
go func() {
// connect to a stream of messages coming from the signal server
err := e.signal.Receive(func(msg *sProto.Message) error {
e.syncMsgMux.Lock()
defer e.syncMsgMux.Unlock()

View File

@@ -256,6 +256,7 @@ func TestEngine_Sync(t *testing.T) {
select {
case <-timeout:
t.Fatalf("timeout while waiting for test to finish")
return
default:
}
@@ -397,11 +398,12 @@ func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey strin
ifaceName = fmt.Sprintf("wt%d", i)
}
wgPort := 33100 + i
conf := &EngineConfig{
WgIfaceName: ifaceName,
WgAddr: resp.PeerConfig.Address,
WgPrivateKey: key,
WgPort: 33100 + i,
WgPort: wgPort,
}
return NewEngine(signalClient, mgmtClient, conf, cancel, ctx), nil

View File

@@ -2,12 +2,13 @@ package peer
import (
"context"
"github.com/pion/ice/v2"
log "github.com/sirupsen/logrus"
"github.com/wiretrustee/wiretrustee/client/internal/proxy"
"net"
"sync"
"time"
"github.com/pion/ice/v2"
log "github.com/sirupsen/logrus"
"github.com/wiretrustee/wiretrustee/client/internal/proxy"
)
// ConnConfig is a peer Connection configuration
@@ -28,6 +29,9 @@ type ConnConfig struct {
Timeout time.Duration
ProxyConfig proxy.Config
UDPMux ice.UDPMux
UDPMuxSrflx ice.UniversalUDPMux
}
// IceCredentials ICE protocol credentials struct
@@ -104,6 +108,8 @@ func (conn *Conn) reCreateAgent() error {
CandidateTypes: []ice.CandidateType{ice.CandidateTypeHost, ice.CandidateTypeServerReflexive, ice.CandidateTypeRelay},
FailedTimeout: &failedTimeout,
InterfaceFilter: interfaceFilter(conn.config.InterfaceBlackList),
UDPMux: conn.config.UDPMux,
UDPMuxSrflx: conn.config.UDPMuxSrflx,
})
if err != nil {
return err
@@ -174,7 +180,7 @@ func (conn *Conn) Open() error {
log.Debugf("received connection confirmation from peer %s", conn.config.Key)
//at this point we received offer/answer and we are ready to gather candidates
// at this point we received offer/answer and we are ready to gather candidates
conn.mu.Lock()
conn.status = StatusConnecting
conn.ctx, conn.notifyDisconnected = context.WithCancel(context.Background())
@@ -287,7 +293,7 @@ func (conn *Conn) SetSignalCandidate(handler func(candidate ice.Candidate) error
// and then signals them to the remote peer
func (conn *Conn) onICECandidate(candidate ice.Candidate) {
if candidate != nil {
//log.Debugf("discovered local candidate %s", candidate.String())
// log.Debugf("discovered local candidate %s", candidate.String())
go func() {
err := conn.signalCandidate(candidate)
if err != nil {
@@ -386,7 +392,7 @@ func (conn *Conn) OnRemoteOffer(remoteAuth IceCredentials) bool {
return true
default:
log.Debugf("OnRemoteOffer skipping message from peer %s on status %s because is not ready", conn.config.Key, conn.status.String())
//connection might not be ready yet to receive so we ignore the message
// connection might not be ready yet to receive so we ignore the message
return false
}
}
@@ -400,7 +406,7 @@ func (conn *Conn) OnRemoteAnswer(remoteAuth IceCredentials) bool {
case conn.remoteAnswerCh <- remoteAuth:
return true
default:
//connection might not be ready yet to receive so we ignore the message
// connection might not be ready yet to receive so we ignore the message
log.Debugf("OnRemoteAnswer skipping message from peer %s on status %s because is not ready", conn.config.Key, conn.status.String())
return false
}