2024-06-17 17:52:22 +02:00
package peer
import (
"context"
"errors"
"sync"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/version"
)
var (
ErrSignalIsNotReady = errors . New ( "signal is not ready" )
)
// IceCredentials ICE protocol credentials struct
type IceCredentials struct {
UFrag string
Pwd string
}
// OfferAnswer represents a session establishment offer or answer
type OfferAnswer struct {
IceCredentials IceCredentials
// WgListenPort is a remote WireGuard listen port.
// This field is used when establishing a direct WireGuard connection without any proxy.
// We can set the remote peer's endpoint with this port.
WgListenPort int
// Version of NetBird Agent
Version string
// RosenpassPubKey is the Rosenpass public key of the remote peer when receiving this message
// This value is the local Rosenpass server public key when sending the message
RosenpassPubKey [ ] byte
// RosenpassAddr is the Rosenpass server address (IP:port) of the remote peer when receiving this message
// This value is the local Rosenpass server address when sending the message
RosenpassAddr string
// relay server address
RelaySrvAddress string
}
type HandshakeArgs struct {
IceUFrag string
IcePwd string
RelayAddr string
}
type Handshaker struct {
2024-06-21 15:35:15 +02:00
mu sync . Mutex
ctx context . Context
log * log . Entry
config ConnConfig
signaler * Signaler
onNewOfferListeners [ ] func ( * OfferAnswer )
2024-06-17 17:52:22 +02:00
// remoteOffersCh is a channel used to wait for remote credentials to proceed with the connection
remoteOffersCh chan OfferAnswer
// remoteAnswerCh is a channel used to wait for remote credentials answer (confirmation of our offer) to proceed with the connection
remoteAnswerCh chan OfferAnswer
2024-06-21 12:35:28 +02:00
lastOfferArgs HandshakeArgs
2024-06-17 17:52:22 +02:00
}
2024-06-21 15:35:15 +02:00
func NewHandshaker ( ctx context . Context , log * log . Entry , config ConnConfig , signaler * Signaler ) * Handshaker {
2024-06-17 17:52:22 +02:00
return & Handshaker {
2024-06-21 15:35:15 +02:00
ctx : ctx ,
log : log ,
config : config ,
signaler : signaler ,
remoteOffersCh : make ( chan OfferAnswer ) ,
remoteAnswerCh : make ( chan OfferAnswer ) ,
2024-06-17 17:52:22 +02:00
}
}
2024-06-21 15:35:15 +02:00
func ( h * Handshaker ) AddOnNewOfferListener ( offer func ( remoteOfferAnswer * OfferAnswer ) ) {
h . onNewOfferListeners = append ( h . onNewOfferListeners , offer )
}
2024-06-21 12:35:28 +02:00
func ( h * Handshaker ) Listen ( ) {
for {
2024-06-25 15:13:08 +02:00
log . Debugf ( "wait for remote offer confirmation" )
2024-06-21 12:35:28 +02:00
remoteOfferAnswer , err := h . waitForRemoteOfferConfirmation ( )
if err != nil {
if _ , ok := err . ( * ConnectionClosedError ) ; ok {
2024-06-21 15:02:54 +02:00
log . Tracef ( "stop handshaker" )
2024-06-21 12:35:28 +02:00
return
}
log . Errorf ( "failed to received remote offer confirmation: %s" , err )
continue
}
h . log . Debugf ( "received connection confirmation, running version %s and with remote WireGuard listen port %d" , remoteOfferAnswer . Version , remoteOfferAnswer . WgListenPort )
2024-06-21 15:35:15 +02:00
for _ , listener := range h . onNewOfferListeners {
go listener ( remoteOfferAnswer )
}
2024-06-21 12:35:28 +02:00
}
}
func ( h * Handshaker ) SendOffer ( args HandshakeArgs ) error {
2024-06-17 17:52:22 +02:00
h . mu . Lock ( )
defer h . mu . Unlock ( )
err := h . sendOffer ( args )
if err != nil {
2024-06-21 12:35:28 +02:00
return err
2024-06-17 17:52:22 +02:00
}
2024-06-21 12:35:28 +02:00
h . lastOfferArgs = args
return nil
2024-06-17 17:52:22 +02:00
}
// OnRemoteOffer handles an offer from the remote peer and returns true if the message was accepted, false otherwise
// doesn't block, discards the message if connection wasn't ready
func ( h * Handshaker ) OnRemoteOffer ( offer OfferAnswer ) bool {
2024-06-19 18:40:49 +02:00
// todo remove this if signaling can support relay
if ForcedRelayAddress ( ) != "" {
offer . RelaySrvAddress = ForcedRelayAddress ( )
}
2024-06-17 17:52:22 +02:00
select {
case h . remoteOffersCh <- offer :
return true
default :
2024-06-19 17:40:16 +02:00
h . log . Debugf ( "OnRemoteOffer skipping message because is not ready" )
2024-06-17 17:52:22 +02:00
// connection might not be ready yet to receive so we ignore the message
return false
}
}
// OnRemoteAnswer handles an offer from the remote peer and returns true if the message was accepted, false otherwise
// doesn't block, discards the message if connection wasn't ready
func ( h * Handshaker ) OnRemoteAnswer ( answer OfferAnswer ) bool {
2024-06-19 18:40:49 +02:00
// todo remove this if signaling can support relay
if ForcedRelayAddress ( ) != "" {
answer . RelaySrvAddress = ForcedRelayAddress ( )
}
2024-06-17 17:52:22 +02:00
select {
case h . remoteAnswerCh <- answer :
return true
default :
// connection might not be ready yet to receive so we ignore the message
2024-06-19 17:40:16 +02:00
h . log . Debugf ( "OnRemoteAnswer skipping message because is not ready" )
2024-06-17 17:52:22 +02:00
return false
}
}
2024-06-21 12:35:28 +02:00
func ( h * Handshaker ) waitForRemoteOfferConfirmation ( ) ( * OfferAnswer , error ) {
select {
case remoteOfferAnswer := <- h . remoteOffersCh :
// received confirmation from the remote peer -> ready to proceed
err := h . sendAnswer ( )
if err != nil {
return nil , err
}
return & remoteOfferAnswer , nil
case remoteOfferAnswer := <- h . remoteAnswerCh :
return & remoteOfferAnswer , nil
case <- h . ctx . Done ( ) :
// closed externally
return nil , NewConnectionClosedError ( h . config . Key )
}
}
2024-06-17 17:52:22 +02:00
// sendOffer prepares local user credentials and signals them to the remote peer
func ( h * Handshaker ) sendOffer ( args HandshakeArgs ) error {
offer := OfferAnswer {
IceCredentials : IceCredentials { args . IceUFrag , args . IcePwd } ,
WgListenPort : h . config . LocalWgPort ,
Version : version . NetbirdVersion ( ) ,
RosenpassPubKey : h . config . RosenpassPubKey ,
RosenpassAddr : h . config . RosenpassAddr ,
RelaySrvAddress : args . RelayAddr ,
}
return h . signaler . SignalOffer ( offer , h . config . Key )
}
func ( h * Handshaker ) sendAnswer ( ) error {
2024-06-19 17:40:16 +02:00
h . log . Debugf ( "sending answer" )
2024-06-17 17:52:22 +02:00
answer := OfferAnswer {
2024-06-21 12:35:28 +02:00
IceCredentials : IceCredentials { h . lastOfferArgs . IceUFrag , h . lastOfferArgs . IcePwd } ,
2024-06-17 17:52:22 +02:00
WgListenPort : h . config . LocalWgPort ,
Version : version . NetbirdVersion ( ) ,
RosenpassPubKey : h . config . RosenpassPubKey ,
RosenpassAddr : h . config . RosenpassAddr ,
2024-06-21 12:35:28 +02:00
RelaySrvAddress : h . lastOfferArgs . RelayAddr ,
2024-06-17 17:52:22 +02:00
}
2024-06-19 09:59:01 +02:00
err := h . signaler . SignalAnswer ( answer , h . config . Key )
2024-06-17 17:52:22 +02:00
if err != nil {
return err
}
return nil
}