Compare commits

...

1 Commits

Author SHA1 Message Date
Zoltan Papp
d66b425bb6 Separate proxy from wg peer config 2023-04-25 17:10:36 +02:00
12 changed files with 216 additions and 353 deletions

View File

@@ -18,7 +18,6 @@ import (
"github.com/netbirdio/netbird/client/internal/dns" "github.com/netbirdio/netbird/client/internal/dns"
"github.com/netbirdio/netbird/client/internal/peer" "github.com/netbirdio/netbird/client/internal/peer"
"github.com/netbirdio/netbird/client/internal/proxy"
"github.com/netbirdio/netbird/client/internal/routemanager" "github.com/netbirdio/netbird/client/internal/routemanager"
nbssh "github.com/netbirdio/netbird/client/ssh" nbssh "github.com/netbirdio/netbird/client/ssh"
nbdns "github.com/netbirdio/netbird/dns" nbdns "github.com/netbirdio/netbird/dns"
@@ -262,7 +261,7 @@ func (e *Engine) modifyPeers(peersUpdate []*mgmProto.RemotePeerConfig) error {
for _, p := range peersUpdate { for _, p := range peersUpdate {
peerPubKey := p.GetWgPubKey() peerPubKey := p.GetWgPubKey()
if peerConn, ok := e.peerConns[peerPubKey]; ok { if peerConn, ok := e.peerConns[peerPubKey]; ok {
if peerConn.GetConf().ProxyConfig.AllowedIps != strings.Join(p.AllowedIps, ",") { if peerConn.WgConfig().AllowedIps != strings.Join(p.AllowedIps, ",") {
modified = append(modified, p) modified = append(modified, p)
continue continue
} }
@@ -776,9 +775,7 @@ func (e *Engine) connWorker(conn *peer.Conn, peerKey string) {
// we might have received new STUN and TURN servers meanwhile, so update them // we might have received new STUN and TURN servers meanwhile, so update them
e.syncMsgMux.Lock() e.syncMsgMux.Lock()
conf := conn.GetConf() conn.UpdateStunTurn(append(e.STUNs, e.TURNs...))
conf.StunTurn = append(e.STUNs, e.TURNs...)
conn.UpdateConf(conf)
e.syncMsgMux.Unlock() e.syncMsgMux.Unlock()
err := conn.Open() err := conn.Open()
@@ -807,7 +804,7 @@ func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, er
stunTurn = append(stunTurn, e.STUNs...) stunTurn = append(stunTurn, e.STUNs...)
stunTurn = append(stunTurn, e.TURNs...) stunTurn = append(stunTurn, e.TURNs...)
proxyConfig := proxy.Config{ wgConfig := peer.WgConfig{
RemoteKey: pubKey, RemoteKey: pubKey,
WgListenAddr: fmt.Sprintf("127.0.0.1:%d", e.config.WgPort), WgListenAddr: fmt.Sprintf("127.0.0.1:%d", e.config.WgPort),
WgInterface: e.wgInterface, WgInterface: e.wgInterface,
@@ -826,7 +823,7 @@ func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, er
Timeout: timeout, Timeout: timeout,
UDPMux: e.udpMux, UDPMux: e.udpMux,
UDPMuxSrflx: e.udpMuxSrflx, UDPMuxSrflx: e.udpMuxSrflx,
ProxyConfig: proxyConfig, WgConfig: wgConfig,
LocalWgPort: e.config.WgPort, LocalWgPort: e.config.WgPort,
NATExternalIPs: e.parseNATExternalIPMappings(), NATExternalIPs: e.parseNATExternalIPMappings(),
UserspaceBind: e.wgInterface.IsUserspaceBind(), UserspaceBind: e.wgInterface.IsUserspaceBind(),

View File

@@ -3,8 +3,6 @@ package internal
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/netbirdio/netbird/iface/bind"
"github.com/pion/transport/v2/stdnet"
"net" "net"
"net/netip" "net/netip"
"os" "os"
@@ -15,6 +13,10 @@ import (
"testing" "testing"
"time" "time"
"github.com/pion/transport/v2/stdnet"
"github.com/netbirdio/netbird/iface/bind"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -367,9 +369,9 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
t.Errorf("expecting Engine.peerConns to contain peer %s", p) t.Errorf("expecting Engine.peerConns to contain peer %s", p)
} }
expectedAllowedIPs := strings.Join(p.AllowedIps, ",") expectedAllowedIPs := strings.Join(p.AllowedIps, ",")
if conn.GetConf().ProxyConfig.AllowedIps != expectedAllowedIPs { if conn.WgConfig().AllowedIps != expectedAllowedIPs {
t.Errorf("expecting peer %s to have AllowedIPs= %s, got %s", p.GetWgPubKey(), t.Errorf("expecting peer %s to have AllowedIPs= %s, got %s", p.GetWgPubKey(),
expectedAllowedIPs, conn.GetConf().ProxyConfig.AllowedIps) expectedAllowedIPs, conn.WgConfig().AllowedIps)
} }
} }
}) })

View File

@@ -11,7 +11,6 @@ import (
"github.com/pion/ice/v2" "github.com/pion/ice/v2"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/internal/proxy"
"github.com/netbirdio/netbird/client/internal/stdnet" "github.com/netbirdio/netbird/client/internal/stdnet"
"github.com/netbirdio/netbird/iface" "github.com/netbirdio/netbird/iface"
signal "github.com/netbirdio/netbird/signal/client" signal "github.com/netbirdio/netbird/signal/client"
@@ -37,7 +36,7 @@ type ConnConfig struct {
Timeout time.Duration Timeout time.Duration
ProxyConfig proxy.Config WgConfig WgConfig
UDPMux ice.UDPMux UDPMux ice.UDPMux
UDPMuxSrflx ice.UniversalUDPMux UDPMuxSrflx ice.UniversalUDPMux
@@ -92,7 +91,8 @@ type Conn struct {
statusRecorder *Status statusRecorder *Status
proxy proxy.Proxy proxy proxy
wgPeerMgr *wgPeerManager
remoteModeCh chan ModeMessage remoteModeCh chan ModeMessage
meta meta meta meta
@@ -111,14 +111,14 @@ type ModeMessage struct {
Direct bool Direct bool
} }
// GetConf returns the connection config // WgConfig returns the WireGuard config
func (conn *Conn) GetConf() ConnConfig { func (conn *Conn) WgConfig() WgConfig {
return conn.config return conn.config.WgConfig
} }
// UpdateConf updates the connection config // UpdateStunTurn update the turn and stun addresses
func (conn *Conn) UpdateConf(conf ConnConfig) { func (conn *Conn) UpdateStunTurn(turnStun []*ice.URL) {
conn.config = conf conn.config.StunTurn = turnStun
} }
// NewConn creates a new not opened Conn to the remote peer. // NewConn creates a new not opened Conn to the remote peer.
@@ -198,7 +198,7 @@ func (conn *Conn) Open() error {
peerState := State{PubKey: conn.config.Key} peerState := State{PubKey: conn.config.Key}
peerState.IP = strings.Split(conn.config.ProxyConfig.AllowedIps, "/")[0] peerState.IP = strings.Split(conn.config.WgConfig.AllowedIps, "/")[0]
peerState.ConnStatusUpdate = time.Now() peerState.ConnStatusUpdate = time.Now()
peerState.ConnStatus = conn.status peerState.ConnStatus = conn.status
@@ -290,19 +290,12 @@ func (conn *Conn) Open() error {
remoteWgPort = remoteOfferAnswer.WgListenPort remoteWgPort = remoteOfferAnswer.WgListenPort
} }
// the ice connection has been established successfully so we are ready to start the proxy // the ice connection has been established successfully so we are ready to start the proxy
err = conn.startProxy(remoteConn, remoteWgPort) err = conn.configureConnection(remoteConn, remoteWgPort)
if err != nil { if err != nil {
return err return err
} }
if conn.proxy.Type() == proxy.TypeDirectNoProxy { log.Infof("connected to peer %s with proxy %v, [laddr <-> raddr] [%s <-> %s]", conn.config.Key, conn.proxy != nil, laddr, conn.wgPeerMgr.remoteAddr.String())
host, _, _ := net.SplitHostPort(remoteConn.LocalAddr().String())
rhost, _, _ := net.SplitHostPort(remoteConn.RemoteAddr().String())
// direct Wireguard connection
log.Infof("directly connected to peer %s [laddr <-> raddr] [%s:%d <-> %s:%d]", conn.config.Key, host, conn.config.LocalWgPort, rhost, remoteWgPort)
} else {
log.Infof("connected to peer %s [laddr <-> raddr] [%s <-> %s]", conn.config.Key, remoteConn.LocalAddr().String(), remoteConn.RemoteAddr().String())
}
// wait until connection disconnected or has been closed externally (upper layer, e.g. engine) // wait until connection disconnected or has been closed externally (upper layer, e.g. engine)
select { select {
@@ -315,7 +308,7 @@ func (conn *Conn) Open() error {
} }
} }
// useProxy determines whether a direct connection (without a go proxy) is possible // isPreferredDirectMode determines whether a direct connection (without a go proxy) is not possible
// //
// There are 3 cases: // There are 3 cases:
// //
@@ -326,36 +319,34 @@ func (conn *Conn) Open() error {
// * Local peer uses userspace interface with bind.ICEBind and is not relayed // * Local peer uses userspace interface with bind.ICEBind and is not relayed
// //
// Please note, that this check happens when peers were already able to ping each other using ICE layer. // Please note, that this check happens when peers were already able to ping each other using ICE layer.
func shouldUseProxy(pair *ice.CandidatePair, userspaceBind bool) bool { func isPreferredDirectMode(pair *ice.CandidatePair, userspaceBind bool) bool {
if !isRelayCandidate(pair.Local) && userspaceBind { if !isRelayCandidate(pair.Local) && userspaceBind {
log.Debugf("shouldn't use proxy because using Bind and the connection is not relayed") log.Debugf("shouldn't use proxy because using Bind and the connection is not relayed")
return false return true
} }
if !isHardNATCandidate(pair.Local) && isHostCandidateWithPublicIP(pair.Remote) { if !isHardNATCandidate(pair.Local) && isHostCandidateWithPublicIP(pair.Remote) {
log.Debugf("shouldn't use proxy because the local peer is not behind a hard NAT and the remote one has a public IP") log.Debugf("shouldn't use proxy because the local peer is not behind a hard NAT and the remote one has a public IP")
return false return true
} }
if !isHardNATCandidate(pair.Remote) && isHostCandidateWithPublicIP(pair.Local) { if !isHardNATCandidate(pair.Remote) && isHostCandidateWithPublicIP(pair.Local) {
log.Debugf("shouldn't use proxy because the remote peer is not behind a hard NAT and the local one has a public IP") log.Debugf("shouldn't use proxy because the remote peer is not behind a hard NAT and the local one has a public IP")
return false return true
} }
if isHostCandidateWithPrivateIP(pair.Local) && isHostCandidateWithPrivateIP(pair.Remote) && isSameNetworkPrefix(pair) { if isHostCandidateWithPrivateIP(pair.Local) && isHostCandidateWithPrivateIP(pair.Remote) && isSameNetworkPrefix(pair) {
log.Debugf("shouldn't use proxy because peers are in the same private /16 network") log.Debugf("shouldn't use proxy because peers are in the same private /16 network")
return false return true
} }
if (isPeerReflexiveCandidateWithPrivateIP(pair.Local) && isHostCandidateWithPrivateIP(pair.Remote) || if (isPeerReflexiveCandidateWithPrivateIP(pair.Local) && isHostCandidateWithPrivateIP(pair.Remote) ||
isHostCandidateWithPrivateIP(pair.Local) && isPeerReflexiveCandidateWithPrivateIP(pair.Remote)) && isSameNetworkPrefix(pair) { isHostCandidateWithPrivateIP(pair.Local) && isPeerReflexiveCandidateWithPrivateIP(pair.Remote)) && isSameNetworkPrefix(pair) {
log.Debugf("shouldn't use proxy because peers are in the same private /16 network and one peer is peer reflexive") log.Debugf("shouldn't use proxy because peers are in the same private /16 network and one peer is peer reflexive")
return false
}
return true return true
} }
return false
}
func isSameNetworkPrefix(pair *ice.CandidatePair) bool { func isSameNetworkPrefix(pair *ice.CandidatePair) bool {
@@ -397,27 +388,38 @@ func isPublicIP(address string) bool {
return true return true
} }
// startProxy starts proxying traffic from/to local Wireguard and sets connection status to StatusConnected // configureConnection starts proxying traffic from/to local Wireguard and sets connection status to StatusConnected
func (conn *Conn) startProxy(remoteConn net.Conn, remoteWgPort int) error { func (conn *Conn) configureConnection(remoteConn net.Conn, remoteWgPort int) error {
conn.mu.Lock() conn.mu.Lock()
defer conn.mu.Unlock() defer conn.mu.Unlock()
var pair *ice.CandidatePair
pair, err := conn.agent.GetSelectedCandidatePair() pair, err := conn.agent.GetSelectedCandidatePair()
if err != nil { if err != nil {
return err return err
} }
peerState := State{PubKey: conn.config.Key} localDirectMode, remoteDirectMode := conn.getNetworkConditions(pair)
p := conn.getProxyWithMessageExchange(pair, remoteWgPort)
conn.proxy = p wgPeerMgr := newWgPeerManager(conn.config.WgConfig)
err = wgPeerMgr.configureWgPeer(localDirectMode, remoteDirectMode, conn.config.UserspaceBind, remoteConn, remoteWgPort)
if err != nil {
return err
}
conn.wgPeerMgr = wgPeerMgr
if conn.isProxyNeeded(localDirectMode, remoteDirectMode) {
p := NewWireGuardProxy(conn.config.WgConfig.WgListenAddr, conn.config.WgConfig.RemoteKey)
err = p.Start(remoteConn) err = p.Start(remoteConn)
if err != nil { if err != nil {
return err return err
} }
conn.proxy = p
}
conn.status = StatusConnected conn.status = StatusConnected
// update Peer's state
peerState := State{PubKey: conn.config.Key}
peerState.ConnStatus = conn.status peerState.ConnStatus = conn.status
peerState.ConnStatusUpdate = time.Now() peerState.ConnStatusUpdate = time.Now()
peerState.LocalIceCandidateType = pair.Local.Type().String() peerState.LocalIceCandidateType = pair.Local.Type().String()
@@ -425,7 +427,7 @@ func (conn *Conn) startProxy(remoteConn net.Conn, remoteWgPort int) error {
if pair.Local.Type() == ice.CandidateTypeRelay || pair.Remote.Type() == ice.CandidateTypeRelay { if pair.Local.Type() == ice.CandidateTypeRelay || pair.Remote.Type() == ice.CandidateTypeRelay {
peerState.Relayed = true peerState.Relayed = true
} }
peerState.Direct = p.Type() == proxy.TypeDirectNoProxy || p.Type() == proxy.TypeNoProxy peerState.Direct = conn.proxy == nil
err = conn.statusRecorder.UpdatePeerState(peerState) err = conn.statusRecorder.UpdatePeerState(peerState)
if err != nil { if err != nil {
@@ -435,27 +437,29 @@ func (conn *Conn) startProxy(remoteConn net.Conn, remoteWgPort int) error {
return nil return nil
} }
func (conn *Conn) getProxyWithMessageExchange(pair *ice.CandidatePair, remoteWgPort int) proxy.Proxy { func (conn *Conn) getNetworkConditions(pair *ice.CandidatePair) (bool, bool) {
useProxy := shouldUseProxy(pair, conn.config.UserspaceBind) var localDirectMode, remoteDirectMode bool
localDirectMode := !useProxy localDirectMode = isPreferredDirectMode(pair, conn.config.UserspaceBind)
remoteDirectMode := localDirectMode
if conn.meta.protoSupport.DirectCheck { if conn.meta.protoSupport.DirectCheck {
go conn.sendLocalDirectMode(localDirectMode) go conn.sendLocalDirectMode(localDirectMode)
// will block until message received or timeout // will block until message received or timeout
remoteDirectMode = conn.receiveRemoteDirectMode() remoteDirectMode = conn.receiveRemoteDirectMode()
} else {
remoteDirectMode = localDirectMode
}
return localDirectMode, remoteDirectMode
} }
func (conn *Conn) isProxyNeeded(localDirectMode, remoteDirectMode bool) bool {
if conn.config.UserspaceBind && localDirectMode { if conn.config.UserspaceBind && localDirectMode {
return proxy.NewNoProxy(conn.config.ProxyConfig) return false
} }
if localDirectMode && remoteDirectMode { if localDirectMode && remoteDirectMode {
return proxy.NewDirectNoProxy(conn.config.ProxyConfig, remoteWgPort) return false
} }
return true
log.Debugf("falling back to local proxy mode with peer %s", conn.config.Key)
return proxy.NewWireGuardProxy(conn.config.ProxyConfig)
} }
func (conn *Conn) sendLocalDirectMode(localMode bool) { func (conn *Conn) sendLocalDirectMode(localMode bool) {
@@ -500,21 +504,27 @@ func (conn *Conn) cleanup() error {
conn.mu.Lock() conn.mu.Lock()
defer conn.mu.Unlock() defer conn.mu.Unlock()
var err1, err2, err3 error
if conn.agent != nil { if conn.agent != nil {
err := conn.agent.Close() err1 = conn.agent.Close()
if err != nil { if err1 == nil {
return err
}
conn.agent = nil conn.agent = nil
} }
}
if conn.wgPeerMgr != nil {
err2 = conn.wgPeerMgr.close()
if err2 == nil {
conn.wgPeerMgr = nil
}
}
if conn.proxy != nil { if conn.proxy != nil {
err := conn.proxy.Close() err3 = conn.proxy.Close()
if err != nil { if err3 == nil {
return err
}
conn.proxy = nil conn.proxy = nil
} }
}
if conn.notifyDisconnected != nil { if conn.notifyDisconnected != nil {
conn.notifyDisconnected() conn.notifyDisconnected()
@@ -535,8 +545,13 @@ func (conn *Conn) cleanup() error {
} }
log.Debugf("cleaned up connection to peer %s", conn.config.Key) log.Debugf("cleaned up connection to peer %s", conn.config.Key)
if err1 != nil {
return nil return err1
}
if err2 != nil {
return err2
}
return err3
} }
// SetSignalOffer sets a handler function to be triggered by Conn when a new connection offer has to be signalled to the remote peer // SetSignalOffer sets a handler function to be triggered by Conn when a new connection offer has to be signalled to the remote peer

View File

@@ -11,7 +11,6 @@ import (
"github.com/pion/ice/v2" "github.com/pion/ice/v2"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"github.com/netbirdio/netbird/client/internal/proxy"
"github.com/netbirdio/netbird/iface" "github.com/netbirdio/netbird/iface"
sproto "github.com/netbirdio/netbird/signal/proto" sproto "github.com/netbirdio/netbird/signal/proto"
) )
@@ -22,7 +21,7 @@ var connConf = ConnConfig{
StunTurn: []*ice.URL{}, StunTurn: []*ice.URL{},
InterfaceBlackList: nil, InterfaceBlackList: nil,
Timeout: time.Second, Timeout: time.Second,
ProxyConfig: proxy.Config{}, WgConfig: WgConfig{},
LocalWgPort: 51820, LocalWgPort: 51820,
} }
@@ -244,87 +243,87 @@ func TestConn_ShouldUseProxy(t *testing.T) {
expected bool expected bool
}{ }{
{ {
name: "Use Proxy When Local Candidate Is Relay", name: "Use proxy When Local Candidate Is Relay",
candatePair: &ice.CandidatePair{ candatePair: &ice.CandidatePair{
Local: relayCandidate, Local: relayCandidate,
Remote: privateHostCandidate, Remote: privateHostCandidate,
}, },
expected: true, expected: false,
}, },
{ {
name: "Use Proxy When Remote Candidate Is Relay", name: "Use proxy When Remote Candidate Is Relay",
candatePair: &ice.CandidatePair{ candatePair: &ice.CandidatePair{
Local: privateHostCandidate, Local: privateHostCandidate,
Remote: relayCandidate, Remote: relayCandidate,
}, },
expected: true, expected: false,
}, },
{ {
name: "Use Proxy When Local Candidate Is Peer Reflexive", name: "Use proxy When Local Candidate Is Peer Reflexive",
candatePair: &ice.CandidatePair{ candatePair: &ice.CandidatePair{
Local: prflxCandidate, Local: prflxCandidate,
Remote: privateHostCandidate, Remote: privateHostCandidate,
}, },
expected: true, expected: false,
}, },
{ {
name: "Use Proxy When Remote Candidate Is Peer Reflexive", name: "Use proxy When Remote Candidate Is Peer Reflexive",
candatePair: &ice.CandidatePair{ candatePair: &ice.CandidatePair{
Local: privateHostCandidate, Local: privateHostCandidate,
Remote: prflxCandidate, Remote: prflxCandidate,
}, },
expected: true, expected: false,
}, },
{ {
name: "Don't Use Proxy When Local Candidate Is Public And Remote Is Private", name: "Don't Use proxy When Local Candidate Is Public And Remote Is Private",
candatePair: &ice.CandidatePair{ candatePair: &ice.CandidatePair{
Local: publicHostCandidate, Local: publicHostCandidate,
Remote: privateHostCandidate, Remote: privateHostCandidate,
}, },
expected: false, expected: true,
}, },
{ {
name: "Don't Use Proxy When Remote Candidate Is Public And Local Is Private", name: "Don't Use proxy When Remote Candidate Is Public And Local Is Private",
candatePair: &ice.CandidatePair{ candatePair: &ice.CandidatePair{
Local: privateHostCandidate, Local: privateHostCandidate,
Remote: publicHostCandidate, Remote: publicHostCandidate,
}, },
expected: false, expected: true,
}, },
{ {
name: "Don't Use Proxy When Local Candidate is Public And Remote Is Server Reflexive", name: "Don't Use proxy When Local Candidate is Public And Remote Is Server Reflexive",
candatePair: &ice.CandidatePair{ candatePair: &ice.CandidatePair{
Local: publicHostCandidate, Local: publicHostCandidate,
Remote: srflxCandidate, Remote: srflxCandidate,
}, },
expected: false, expected: true,
}, },
{ {
name: "Don't Use Proxy When Remote Candidate is Public And Local Is Server Reflexive", name: "Don't Use proxy When Remote Candidate is Public And Local Is Server Reflexive",
candatePair: &ice.CandidatePair{ candatePair: &ice.CandidatePair{
Local: srflxCandidate, Local: srflxCandidate,
Remote: publicHostCandidate, Remote: publicHostCandidate,
}, },
expected: false, expected: true,
}, },
{ {
name: "Don't Use Proxy When Both Candidates Are Public", name: "Don't Use proxy When Both Candidates Are Public",
candatePair: &ice.CandidatePair{ candatePair: &ice.CandidatePair{
Local: publicHostCandidate, Local: publicHostCandidate,
Remote: publicHostCandidate, Remote: publicHostCandidate,
}, },
expected: false, expected: true,
}, },
{ {
name: "Don't Use Proxy When Both Candidates Are Private", name: "Don't Use proxy When Both Candidates Are Private",
candatePair: &ice.CandidatePair{ candatePair: &ice.CandidatePair{
Local: privateHostCandidate, Local: privateHostCandidate,
Remote: privateHostCandidate, Remote: privateHostCandidate,
}, },
expected: false, expected: true,
}, },
{ {
name: "Don't Use Proxy When Both Candidates are in private network and one is peer reflexive", name: "Don't Use proxy When Both Candidates are in private network and one is peer reflexive",
candatePair: &ice.CandidatePair{ candatePair: &ice.CandidatePair{
Local: &mockICECandidate{AddressFunc: func() string { Local: &mockICECandidate{AddressFunc: func() string {
return "10.16.102.168" return "10.16.102.168"
@@ -339,10 +338,10 @@ func TestConn_ShouldUseProxy(t *testing.T) {
return ice.CandidateTypePeerReflexive return ice.CandidateTypePeerReflexive
}}, }},
}, },
expected: false, expected: true,
}, },
{ {
name: "Should Use Proxy When Both Candidates are in private network and both are peer reflexive", name: "Should Use proxy When Both Candidates are in private network and both are peer reflexive",
candatePair: &ice.CandidatePair{ candatePair: &ice.CandidatePair{
Local: &mockICECandidate{AddressFunc: func() string { Local: &mockICECandidate{AddressFunc: func() string {
return "10.16.102.168" return "10.16.102.168"
@@ -357,13 +356,13 @@ func TestConn_ShouldUseProxy(t *testing.T) {
return ice.CandidateTypePeerReflexive return ice.CandidateTypePeerReflexive
}}, }},
}, },
expected: true, expected: false,
}, },
} }
for _, testCase := range testCases { for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
result := shouldUseProxy(testCase.candatePair, false) result := isPreferredDirectMode(testCase.candatePair, false)
if result != testCase.expected { if result != testCase.expected {
t.Errorf("got a different result. Expected %t Got %t", testCase.expected, result) t.Errorf("got a different result. Expected %t Got %t", testCase.expected, result)
} }
@@ -394,57 +393,57 @@ func TestGetProxyWithMessageExchange(t *testing.T) {
candatePair *ice.CandidatePair candatePair *ice.CandidatePair
inputDirectModeSupport bool inputDirectModeSupport bool
inputRemoteModeMessage bool inputRemoteModeMessage bool
expected proxy.Type expected bool
}{ }{
{ {
name: "Should Result In Using Wireguard Proxy When Local Eval Is Use Proxy", name: "Should Result In Using Wireguard proxy When Local Eval Is Use proxy",
candatePair: &ice.CandidatePair{ candatePair: &ice.CandidatePair{
Local: relayCandidate, Local: relayCandidate,
Remote: publicHostCandidate, Remote: publicHostCandidate,
}, },
inputDirectModeSupport: true, inputDirectModeSupport: true,
inputRemoteModeMessage: true, inputRemoteModeMessage: true,
expected: proxy.TypeWireGuard, expected: true,
}, },
{ {
name: "Should Result In Using Wireguard Proxy When Remote Eval Is Use Proxy", name: "Should Result In Using Wireguard proxy When Remote Eval Is Use proxy",
candatePair: &ice.CandidatePair{ candatePair: &ice.CandidatePair{
Local: publicHostCandidate, Local: publicHostCandidate,
Remote: publicHostCandidate, Remote: publicHostCandidate,
}, },
inputDirectModeSupport: true, inputDirectModeSupport: true,
inputRemoteModeMessage: false, inputRemoteModeMessage: false,
expected: proxy.TypeWireGuard, expected: true,
}, },
{ {
name: "Should Result In Using Wireguard Proxy When Remote Direct Mode Support Is False And Local Eval Is Use Proxy", name: "Should Result In Using Wireguard proxy When Remote Direct Mode Support Is False And Local Eval Is Use proxy",
candatePair: &ice.CandidatePair{ candatePair: &ice.CandidatePair{
Local: relayCandidate, Local: relayCandidate,
Remote: publicHostCandidate, Remote: publicHostCandidate,
}, },
inputDirectModeSupport: false, inputDirectModeSupport: false,
inputRemoteModeMessage: false, inputRemoteModeMessage: false,
expected: proxy.TypeWireGuard, expected: true,
}, },
{ {
name: "Should Result In Using Direct When Remote Direct Mode Support Is False And Local Eval Is No Use Proxy", name: "Should Result In Using Direct When Remote Direct Mode Support Is False And Local Eval Is No Use proxy",
candatePair: &ice.CandidatePair{ candatePair: &ice.CandidatePair{
Local: publicHostCandidate, Local: publicHostCandidate,
Remote: publicHostCandidate, Remote: publicHostCandidate,
}, },
inputDirectModeSupport: false, inputDirectModeSupport: false,
inputRemoteModeMessage: false, inputRemoteModeMessage: false,
expected: proxy.TypeDirectNoProxy, expected: false,
}, },
{ {
name: "Should Result In Using Direct When Local And Remote Eval Is No Proxy", name: "Should Result In Using Direct When Local And Remote Eval Is No proxy",
candatePair: &ice.CandidatePair{ candatePair: &ice.CandidatePair{
Local: publicHostCandidate, Local: publicHostCandidate,
Remote: publicHostCandidate, Remote: publicHostCandidate,
}, },
inputDirectModeSupport: true, inputDirectModeSupport: true,
inputRemoteModeMessage: true, inputRemoteModeMessage: true,
expected: proxy.TypeDirectNoProxy, expected: false,
}, },
} }
for _, testCase := range testCases { for _, testCase := range testCases {
@@ -464,15 +463,15 @@ func TestGetProxyWithMessageExchange(t *testing.T) {
Direct: testCase.inputRemoteModeMessage, Direct: testCase.inputRemoteModeMessage,
}) })
}) })
conn.config.UserspaceBind = false
resultProxy := conn.getProxyWithMessageExchange(testCase.candatePair, 1000) resultProxy := conn.isProxyNeeded(testCase.inputDirectModeSupport, testCase.inputRemoteModeMessage)
err = g.Wait() err = g.Wait()
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
if resultProxy.Type() != testCase.expected { if resultProxy != testCase.expected {
t.Errorf("result didn't match expected value: Expected: %s, Got: %s", testCase.expected, resultProxy.Type()) t.Errorf("result didn't match expected value: Expected: %v, Got: %v", testCase.expected, resultProxy)
} }
}) })
} }

View File

@@ -0,0 +1,12 @@
package peer
import (
"io"
"net"
)
type proxy interface {
io.Closer
// Start creates a local remoteConn and starts proxying data from/to remoteConn
Start(remoteConn net.Conn) error
}

View File

@@ -1,9 +1,10 @@
package proxy package peer
import ( import (
"context" "context"
log "github.com/sirupsen/logrus"
"net" "net"
log "github.com/sirupsen/logrus"
) )
// WireGuardProxy proxies // WireGuardProxy proxies
@@ -11,49 +12,32 @@ type WireGuardProxy struct {
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
config Config wgListenAddr string
remoteKey string
remoteConn net.Conn remoteConn net.Conn
localConn net.Conn localConn net.Conn
} }
func NewWireGuardProxy(config Config) *WireGuardProxy { func NewWireGuardProxy(wgListenAddr, remoteKey string) *WireGuardProxy {
p := &WireGuardProxy{config: config} p := &WireGuardProxy{
wgListenAddr: wgListenAddr,
remoteKey: remoteKey,
}
p.ctx, p.cancel = context.WithCancel(context.Background()) p.ctx, p.cancel = context.WithCancel(context.Background())
return p return p
} }
func (p *WireGuardProxy) updateEndpoint() error {
udpAddr, err := net.ResolveUDPAddr(p.localConn.LocalAddr().Network(), p.localConn.LocalAddr().String())
if err != nil {
return err
}
// add local proxy connection as a Wireguard peer
err = p.config.WgInterface.UpdatePeer(p.config.RemoteKey, p.config.AllowedIps, DefaultWgKeepAlive,
udpAddr, p.config.PreSharedKey)
if err != nil {
return err
}
return nil
}
func (p *WireGuardProxy) Start(remoteConn net.Conn) error { func (p *WireGuardProxy) Start(remoteConn net.Conn) error {
p.remoteConn = remoteConn p.remoteConn = remoteConn
var err error var err error
p.localConn, err = net.Dial("udp", p.config.WgListenAddr) p.localConn, err = net.Dial("udp", p.wgListenAddr)
if err != nil { if err != nil {
log.Errorf("failed dialing to local Wireguard port %s", err) log.Errorf("failed dialing to local Wireguard port %s", err)
return err return err
} }
err = p.updateEndpoint()
if err != nil {
log.Errorf("error while updating Wireguard peer endpoint [%s] %v", p.config.RemoteKey, err)
return err
}
go p.proxyToRemote() go p.proxyToRemote()
go p.proxyToLocal() go p.proxyToLocal()
@@ -68,10 +52,6 @@ func (p *WireGuardProxy) Close() error {
return err return err
} }
} }
err := p.config.WgInterface.RemovePeer(p.config.RemoteKey)
if err != nil {
return err
}
return nil return nil
} }
@@ -83,7 +63,7 @@ func (p *WireGuardProxy) proxyToRemote() {
for { for {
select { select {
case <-p.ctx.Done(): case <-p.ctx.Done():
log.Debugf("stopped proxying to remote peer %s due to closed connection", p.config.RemoteKey) log.Debugf("stopped proxying to remote peer %s due to closed connection", p.remoteKey)
return return
default: default:
n, err := p.localConn.Read(buf) n, err := p.localConn.Read(buf)
@@ -107,7 +87,7 @@ func (p *WireGuardProxy) proxyToLocal() {
for { for {
select { select {
case <-p.ctx.Done(): case <-p.ctx.Done():
log.Debugf("stopped proxying from remote peer %s due to closed connection", p.config.RemoteKey) log.Debugf("stopped proxying from remote peer %s due to closed connection", p.remoteKey)
return return
default: default:
n, err := p.remoteConn.Read(buf) n, err := p.remoteConn.Read(buf)
@@ -122,7 +102,3 @@ func (p *WireGuardProxy) proxyToLocal() {
} }
} }
} }
func (p *WireGuardProxy) Type() Type {
return TypeWireGuard
}

View File

@@ -0,0 +1,68 @@
package peer
import (
"net"
"time"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"github.com/netbirdio/netbird/iface"
)
const defaultWgKeepAlive = 25 * time.Second
type WgConfig struct {
WgListenAddr string
RemoteKey string
WgInterface *iface.WGIface
AllowedIps string
PreSharedKey *wgtypes.Key
}
type wgPeerManager struct {
wgConfig WgConfig
remoteAddr *net.UDPAddr
}
func newWgPeerManager(wgConfig WgConfig) *wgPeerManager {
return &wgPeerManager{
wgConfig: wgConfig,
}
}
func (mgr *wgPeerManager) configureWgPeer(localDirectMode, remoteDirectMode, userspaceBind bool, remoteConn net.Conn, remoteWgPort int) error {
var err error
mgr.remoteAddr, err = net.ResolveUDPAddr("udp", remoteConn.RemoteAddr().String())
if err != nil {
return err
}
if remoteDirectMode {
mgr.remoteAddr.Port = remoteWgPort
}
if userspaceBind && localDirectMode {
return mgr.updateWgPeer()
}
if localDirectMode && remoteDirectMode {
return mgr.updateWgPeer()
}
mgr.remoteAddr, err = net.ResolveUDPAddr("udp", mgr.wgConfig.WgListenAddr)
if err != nil {
return err
}
return mgr.updateWgPeer()
}
// Close removes peer from the WireGuard interface
func (mgr *wgPeerManager) close() error {
return mgr.wgConfig.WgInterface.RemovePeer(mgr.wgConfig.RemoteKey)
}
func (mgr *wgPeerManager) updateWgPeer() error {
return mgr.wgConfig.WgInterface.UpdatePeer(mgr.wgConfig.RemoteKey, mgr.wgConfig.AllowedIps, defaultWgKeepAlive,
mgr.remoteAddr, mgr.wgConfig.PreSharedKey)
}

View File

@@ -1,57 +0,0 @@
package proxy
import (
log "github.com/sirupsen/logrus"
"net"
)
// DirectNoProxy is used when there is no need for a proxy between ICE and WireGuard.
// This is possible in either of these cases:
// - peers are in the same local network
// - one of the peers has a public static IP (host)
// DirectNoProxy will just update remote peer with a remote host and fixed WireGuard port (r.g. 51820).
// In order DirectNoProxy to work, WireGuard port has to be fixed for the time being.
type DirectNoProxy struct {
config Config
// RemoteWgListenPort is a WireGuard port of a remote peer.
// It is used instead of the hardcoded 51820 port.
RemoteWgListenPort int
}
// NewDirectNoProxy creates a new DirectNoProxy with a provided config and remote peer's WireGuard listen port
func NewDirectNoProxy(config Config, remoteWgPort int) *DirectNoProxy {
return &DirectNoProxy{config: config, RemoteWgListenPort: remoteWgPort}
}
// Close removes peer from the WireGuard interface
func (p *DirectNoProxy) Close() error {
err := p.config.WgInterface.RemovePeer(p.config.RemoteKey)
if err != nil {
return err
}
return nil
}
// Start just updates WireGuard peer with the remote IP and default WireGuard port
func (p *DirectNoProxy) Start(remoteConn net.Conn) error {
log.Debugf("using DirectNoProxy while connecting to peer %s", p.config.RemoteKey)
addr, err := net.ResolveUDPAddr("udp", remoteConn.RemoteAddr().String())
if err != nil {
return err
}
addr.Port = p.RemoteWgListenPort
err = p.config.WgInterface.UpdatePeer(p.config.RemoteKey, p.config.AllowedIps, DefaultWgKeepAlive,
addr, p.config.PreSharedKey)
if err != nil {
return err
}
return nil
}
// Type returns the type of this proxy
func (p *DirectNoProxy) Type() Type {
return TypeDirectNoProxy
}

View File

@@ -1,72 +0,0 @@
package proxy
import (
"context"
log "github.com/sirupsen/logrus"
"net"
"time"
)
// DummyProxy just sends pings to the RemoteKey peer and reads responses
type DummyProxy struct {
conn net.Conn
remote string
ctx context.Context
cancel context.CancelFunc
}
func NewDummyProxy(remote string) *DummyProxy {
p := &DummyProxy{remote: remote}
p.ctx, p.cancel = context.WithCancel(context.Background())
return p
}
func (p *DummyProxy) Close() error {
p.cancel()
return nil
}
func (p *DummyProxy) Start(remoteConn net.Conn) error {
p.conn = remoteConn
go func() {
buf := make([]byte, 1500)
for {
select {
case <-p.ctx.Done():
return
default:
_, err := p.conn.Read(buf)
if err != nil {
log.Errorf("error while reading RemoteKey %s proxy %v", p.remote, err)
return
}
//log.Debugf("received %s from %s", string(buf[:n]), p.remote)
}
}
}()
go func() {
for {
select {
case <-p.ctx.Done():
return
default:
_, err := p.conn.Write([]byte("hello"))
//log.Debugf("sent ping to %s", p.remote)
if err != nil {
log.Errorf("error while writing to RemoteKey %s proxy %v", p.remote, err)
return
}
time.Sleep(5 * time.Second)
}
}
}()
return nil
}
func (p *DummyProxy) Type() Type {
return TypeDummy
}

View File

@@ -1,42 +0,0 @@
package proxy
import (
log "github.com/sirupsen/logrus"
"net"
)
// NoProxy is used just to configure WireGuard without any local proxy in between.
// Used when the WireGuard interface is userspace and uses bind.ICEBind
type NoProxy struct {
config Config
}
// NewNoProxy creates a new NoProxy with a provided config
func NewNoProxy(config Config) *NoProxy {
return &NoProxy{config: config}
}
// Close removes peer from the WireGuard interface
func (p *NoProxy) Close() error {
err := p.config.WgInterface.RemovePeer(p.config.RemoteKey)
if err != nil {
return err
}
return nil
}
// Start just updates WireGuard peer with the remote address
func (p *NoProxy) Start(remoteConn net.Conn) error {
log.Debugf("using NoProxy to connect to peer %s at %s", p.config.RemoteKey, remoteConn.RemoteAddr().String())
addr, err := net.ResolveUDPAddr("udp", remoteConn.RemoteAddr().String())
if err != nil {
return err
}
return p.config.WgInterface.UpdatePeer(p.config.RemoteKey, p.config.AllowedIps, DefaultWgKeepAlive,
addr, p.config.PreSharedKey)
}
func (p *NoProxy) Type() Type {
return TypeNoProxy
}

View File

@@ -1,35 +0,0 @@
package proxy
import (
"github.com/netbirdio/netbird/iface"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"io"
"net"
"time"
)
const DefaultWgKeepAlive = 25 * time.Second
type Type string
const (
TypeDirectNoProxy Type = "DirectNoProxy"
TypeWireGuard Type = "WireGuard"
TypeDummy Type = "Dummy"
TypeNoProxy Type = "NoProxy"
)
type Config struct {
WgListenAddr string
RemoteKey string
WgInterface *iface.WGIface
AllowedIps string
PreSharedKey *wgtypes.Key
}
type Proxy interface {
io.Closer
// Start creates a local remoteConn and starts proxying data from/to remoteConn
Start(remoteConn net.Conn) error
Type() Type
}

View File

@@ -350,7 +350,7 @@ func (c *GrpcClient) receive(stream proto.SignalExchange_ConnectStreamClient,
} else if err != nil { } else if err != nil {
return err return err
} }
log.Debugf("received a new message from Peer [fingerprint: %s]", msg.Key) log.Tracef("received a new message from Peer [fingerprint: %s]", msg.Key)
decryptedMessage, err := c.decryptMessage(msg) decryptedMessage, err := c.decryptMessage(msg)
if err != nil { if err != nil {