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 Handshaker struct {
2024-06-21 15:35:15 +02:00
mu sync . Mutex
ctx context . Context
log * log . Entry
config ConnConfig
signaler * Signaler
2024-07-16 11:02:32 +02:00
ice * WorkerICE
relay * WorkerRelay
2024-06-21 15:35:15 +02:00
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-07-16 11:02:32 +02:00
func NewHandshaker ( ctx context . Context , log * log . Entry , config ConnConfig , signaler * Signaler , ice * WorkerICE , relay * WorkerRelay ) * 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 ,
2024-07-16 11:02:32 +02:00
ice : ice ,
relay : relay ,
2024-06-21 15:35:15 +02:00
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-07-09 16:06:36 +02:00
h . 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-07-09 16:06:36 +02:00
h . log . Tracef ( "stop handshaker" )
2024-06-21 12:35:28 +02:00
return
}
2024-07-09 16:06:36 +02:00
h . log . Errorf ( "failed to received remote offer confirmation: %s" , err )
2024-06-21 12:35:28 +02:00
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
}
}
2024-07-16 11:02:32 +02:00
func ( h * Handshaker ) SendOffer ( ) error {
2024-06-17 17:52:22 +02:00
h . mu . Lock ( )
defer h . mu . Unlock ( )
2024-07-16 11:02:32 +02:00
return h . sendOffer ( )
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 {
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 {
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
2024-07-16 11:02:32 +02:00
func ( h * Handshaker ) sendOffer ( ) error {
if ! h . signaler . Ready ( ) {
return ErrSignalIsNotReady
}
iceUFrag , icePwd := h . ice . GetLocalUserCredentials ( )
2024-06-17 17:52:22 +02:00
offer := OfferAnswer {
2024-07-16 11:02:32 +02:00
IceCredentials : IceCredentials { iceUFrag , 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-07-16 11:02:32 +02:00
}
addr , err := h . relay . RelayInstanceAddress ( )
if err == nil {
offer . RelaySrvAddress = addr
2024-06-17 17:52:22 +02:00
}
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-07-16 11:02:32 +02:00
uFrag , pwd := h . ice . GetLocalUserCredentials ( )
2024-06-17 17:52:22 +02:00
answer := OfferAnswer {
2024-07-16 11:02:32 +02:00
IceCredentials : IceCredentials { uFrag , pwd } ,
2024-06-17 17:52:22 +02:00
WgListenPort : h . config . LocalWgPort ,
Version : version . NetbirdVersion ( ) ,
RosenpassPubKey : h . config . RosenpassPubKey ,
RosenpassAddr : h . config . RosenpassAddr ,
}
2024-07-16 11:02:32 +02:00
addr , err := h . relay . RelayInstanceAddress ( )
if err == nil {
answer . RelaySrvAddress = addr
}
err = h . signaler . SignalAnswer ( answer , h . config . Key )
2024-06-17 17:52:22 +02:00
if err != nil {
return err
}
return nil
}