Glue up all together

This commit is contained in:
braginini
2023-04-04 16:50:35 +02:00
parent 24cc5c4ef2
commit 87cbff1e7a
14 changed files with 184 additions and 80 deletions

View File

@@ -111,7 +111,7 @@ func RunClient(ctx context.Context, config *Config, statusRecorder *peer.Status,
localPeerState := peer.LocalPeerState{
IP: loginResp.GetPeerConfig().GetAddress(),
PubKey: myPrivateKey.PublicKey().String(),
KernelInterface: iface.WireguardModuleIsLoaded(),
KernelInterface: iface.WireGuardModuleIsLoaded(),
FQDN: loginResp.GetPeerConfig().GetFqdn(),
}

View File

@@ -3,7 +3,6 @@ package internal
import (
"context"
"fmt"
"github.com/netbirdio/netbird/iface/bind"
"math/rand"
"net"
"net/netip"
@@ -103,7 +102,8 @@ type Engine struct {
wgInterface *iface.WGIface
udpMux *bind.UniversalUDPMuxDefault
udpMux ice.UDPMux
udpMuxSrflx ice.UniversalUDPMux
udpMuxConn *net.UDPConn
udpMuxConnSrflx *net.UDPConn
@@ -118,6 +118,9 @@ type Engine struct {
routeManager routemanager.Manager
dnsServer dns.Server
// userspaceBind indicates whether a
userspaceBind bool
}
// Peer is an instance of the Connection Peer
@@ -173,7 +176,7 @@ func (e *Engine) Start() error {
e.syncMsgMux.Lock()
defer e.syncMsgMux.Unlock()
wgIfaceName := e.config.WgIfaceName
wgIFaceName := e.config.WgIfaceName
wgAddr := e.config.WgAddr
myPrivateKey := e.config.WgPrivateKey
var err error
@@ -181,31 +184,60 @@ func (e *Engine) Start() error {
if err != nil {
log.Warnf("failed to create pion's stdnet: %s", err)
}
e.wgInterface, err = iface.NewWGIFace(wgIfaceName, wgAddr, iface.DefaultMTU, e.config.TunAdapter, transportNet)
e.wgInterface, err = iface.NewWGIFace(wgIFaceName, wgAddr, iface.DefaultMTU, e.config.TunAdapter, transportNet)
if err != nil {
log.Errorf("failed creating wireguard interface instance %s: [%s]", wgIfaceName, err.Error())
log.Errorf("failed creating wireguard interface instance %s: [%s]", wgIFaceName, err.Error())
return err
}
err = e.wgInterface.Create()
if err != nil {
log.Errorf("failed creating tunnel interface %s: [%s]", wgIfaceName, err.Error())
log.Errorf("failed creating tunnel interface %s: [%s]", wgIFaceName, err.Error())
e.close()
return err
}
err = e.wgInterface.Configure(myPrivateKey.String(), e.config.WgPort)
if err != nil {
log.Errorf("failed configuring Wireguard interface [%s]: %s", wgIfaceName, err.Error())
log.Errorf("failed configuring Wireguard interface [%s]: %s", wgIFaceName, err.Error())
e.close()
return err
}
iceBind := e.wgInterface.GetBind()
e.udpMux, err = iceBind.GetICEMux()
if err != nil {
e.close()
return err
if e.wgInterface.IsUserspaceBind() {
iceBind := e.wgInterface.GetBind()
udpMux, err := iceBind.GetICEMux()
if err != nil {
e.close()
return err
}
e.udpMux = udpMux.UDPMuxDefault
e.udpMuxSrflx = udpMux
log.Infof("using userspace bind mode %s", udpMux.LocalAddr().String())
} else {
networkName := "udp"
if e.config.DisableIPv6Discovery {
networkName = "udp4"
}
e.udpMuxConn, err = net.ListenUDP(networkName, &net.UDPAddr{Port: e.config.UDPMuxPort})
if err != nil {
log.Errorf("failed listening on UDP port %d: [%s]", e.config.UDPMuxPort, err.Error())
e.close()
return err
}
udpMuxParams := ice.UDPMuxParams{
UDPConn: e.udpMuxConn,
Net: transportNet,
}
e.udpMux = ice.NewUDPMuxDefault(udpMuxParams)
e.udpMuxConnSrflx, err = net.ListenUDP(networkName, &net.UDPAddr{Port: e.config.UDPMuxSrflxPort})
if err != nil {
log.Errorf("failed listening on UDP port %d: [%s]", e.config.UDPMuxSrflxPort, err.Error())
e.close()
return err
}
e.udpMuxSrflx = ice.NewUniversalUDPMuxDefault(ice.UniversalUDPMuxParams{UDPConn: e.udpMuxConnSrflx, Net: transportNet})
}
e.routeManager = routemanager.NewManager(e.ctx, e.config.WgPrivateKey.PublicKey().String(), e.wgInterface, e.statusRecorder)
@@ -476,7 +508,7 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error {
e.statusRecorder.UpdateLocalPeerState(peer.LocalPeerState{
IP: e.config.WgAddr,
PubKey: e.config.WgPrivateKey.PublicKey().String(),
KernelInterface: iface.WireguardModuleIsLoaded(),
KernelInterface: iface.WireGuardModuleIsLoaded(),
FQDN: conf.GetFqdn(),
})
@@ -797,11 +829,12 @@ func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, er
InterfaceBlackList: e.config.IFaceBlackList,
DisableIPv6Discovery: e.config.DisableIPv6Discovery,
Timeout: timeout,
UDPMux: e.udpMux.UDPMuxDefault,
UDPMuxSrflx: e.udpMux,
UDPMux: e.udpMux,
UDPMuxSrflx: e.udpMuxSrflx,
ProxyConfig: proxyConfig,
LocalWgPort: e.config.WgPort,
NATExternalIPs: e.parseNATExternalIPMappings(),
UserspaceBind: e.wgInterface.IsUserspaceBind(),
}
peerConn, err := peer.NewConn(config, e.statusRecorder, e.config.TunAdapter, e.config.IFaceDiscover)

View File

@@ -44,6 +44,9 @@ type ConnConfig struct {
LocalWgPort int
NATExternalIPs []string
// UsesBind indicates whether the WireGuard interface is userspace and uses bind.ICEBind
UserspaceBind bool
}
// OfferAnswer represents a session establishment offer or answer
@@ -291,7 +294,7 @@ func (conn *Conn) Open() error {
return err
}
if conn.proxy.Type() == proxy.TypeNoProxy {
if conn.proxy.Type() == proxy.TypeDirectNoProxy {
host, _, _ := net.SplitHostPort(remoteConn.LocalAddr().String())
rhost, _, _ := net.SplitHostPort(remoteConn.RemoteAddr().String())
// direct Wireguard connection
@@ -313,14 +316,21 @@ func (conn *Conn) Open() error {
// useProxy determines whether a direct connection (without a go proxy) is possible
//
// There are 2 cases:
// There are 3 cases:
//
// * When neither candidate is from hard nat and one of the peers has a public IP
//
// * both peers are in the same private network
//
// * 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.
func shouldUseProxy(pair *ice.CandidatePair) bool {
func shouldUseProxy(pair *ice.CandidatePair, userspaceBind bool) bool {
if !isRelayCandidate(pair.Local) && userspaceBind {
return false
}
if !isHardNATCandidate(pair.Local) && isHostCandidateWithPublicIP(pair.Remote) {
return false
}
@@ -336,6 +346,10 @@ func shouldUseProxy(pair *ice.CandidatePair) bool {
return true
}
func isRelayCandidate(candidate ice.Candidate) bool {
return candidate.Type() == ice.CandidateTypeRelay
}
func isHardNATCandidate(candidate ice.Candidate) bool {
return candidate.Type() == ice.CandidateTypeRelay || candidate.Type() == ice.CandidateTypePeerReflexive
}
@@ -384,7 +398,7 @@ func (conn *Conn) startProxy(remoteConn net.Conn, remoteWgPort int) error {
if pair.Local.Type() == ice.CandidateTypeRelay || pair.Remote.Type() == ice.CandidateTypeRelay {
peerState.Relayed = true
}
peerState.Direct = p.Type() == proxy.TypeNoProxy
peerState.Direct = p.Type() == proxy.TypeDirectNoProxy
err = conn.statusRecorder.UpdatePeerState(peerState)
if err != nil {
@@ -395,8 +409,7 @@ func (conn *Conn) startProxy(remoteConn net.Conn, remoteWgPort int) error {
}
func (conn *Conn) getProxyWithMessageExchange(pair *ice.CandidatePair, remoteWgPort int) proxy.Proxy {
return proxy.NewWireGuardProxy(conn.config.ProxyConfig)
useProxy := shouldUseProxy(pair)
useProxy := shouldUseProxy(pair, conn.config.UserspaceBind)
localDirectMode := !useProxy
remoteDirectMode := localDirectMode
@@ -406,9 +419,14 @@ func (conn *Conn) getProxyWithMessageExchange(pair *ice.CandidatePair, remoteWgP
remoteDirectMode = conn.receiveRemoteDirectMode()
}
if conn.config.UserspaceBind {
log.Debugf("using WireGuard no proxy userspace bind mode with peer %s", conn.config.Key)
return proxy.NewNoProxy(conn.config.ProxyConfig)
}
if localDirectMode && remoteDirectMode {
log.Debugf("using WireGuard direct mode with peer %s", conn.config.Key)
return proxy.NewNoProxy(conn.config.ProxyConfig, remoteWgPort)
return proxy.NewDirectNoProxy(conn.config.ProxyConfig, remoteWgPort)
}
log.Debugf("falling back to local proxy mode with peer %s", conn.config.Key)

View File

@@ -325,7 +325,7 @@ func TestConn_ShouldUseProxy(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
result := shouldUseProxy(testCase.candatePair)
result := shouldUseProxy(testCase.candatePair, false)
if result != testCase.expected {
t.Errorf("got a different result. Expected %t Got %t", testCase.expected, result)
}
@@ -366,7 +366,7 @@ func TestGetProxyWithMessageExchange(t *testing.T) {
},
inputDirectModeSupport: true,
inputRemoteModeMessage: true,
expected: proxy.TypeWireguard,
expected: proxy.TypeWireGuard,
},
{
name: "Should Result In Using Wireguard Proxy When Remote Eval Is Use Proxy",
@@ -376,7 +376,7 @@ func TestGetProxyWithMessageExchange(t *testing.T) {
},
inputDirectModeSupport: true,
inputRemoteModeMessage: false,
expected: proxy.TypeWireguard,
expected: proxy.TypeWireGuard,
},
{
name: "Should Result In Using Wireguard Proxy When Remote Direct Mode Support Is False And Local Eval Is Use Proxy",
@@ -386,7 +386,7 @@ func TestGetProxyWithMessageExchange(t *testing.T) {
},
inputDirectModeSupport: false,
inputRemoteModeMessage: false,
expected: proxy.TypeWireguard,
expected: proxy.TypeWireGuard,
},
{
name: "Should Result In Using Direct When Remote Direct Mode Support Is False And Local Eval Is No Use Proxy",
@@ -396,7 +396,7 @@ func TestGetProxyWithMessageExchange(t *testing.T) {
},
inputDirectModeSupport: false,
inputRemoteModeMessage: false,
expected: proxy.TypeNoProxy,
expected: proxy.TypeDirectNoProxy,
},
{
name: "Should Result In Using Direct When Local And Remote Eval Is No Proxy",
@@ -406,7 +406,7 @@ func TestGetProxyWithMessageExchange(t *testing.T) {
},
inputDirectModeSupport: true,
inputRemoteModeMessage: true,
expected: proxy.TypeNoProxy,
expected: proxy.TypeDirectNoProxy,
},
}
for _, testCase := range testCases {

View File

@@ -0,0 +1,55 @@
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}
}
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
}
func (p *DirectNoProxy) Type() Type {
return TypeDirectNoProxy
}

View File

@@ -5,24 +5,18 @@ import (
"net"
)
// NoProxy 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)
// NoProxy will just update remote peer with a remote host and fixed WireGuard port (r.g. 51820).
// In order NoProxy to work, WireGuard port has to be fixed for the time being.
// 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
// RemoteWgListenPort is a WireGuard port of a remote peer.
// It is used instead of the hardcoded 51820 port.
RemoteWgListenPort int
}
// NewNoProxy creates a new NoProxy with a provided config and remote peer's WireGuard listen port
func NewNoProxy(config Config, remoteWgPort int) *NoProxy {
return &NoProxy{config: config, RemoteWgListenPort: remoteWgPort}
// 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 {
@@ -31,23 +25,16 @@ func (p *NoProxy) Close() error {
return nil
}
// Start just updates WireGuard peer with the remote IP and default WireGuard port
// Start just updates WireGuard peer with the remote address
func (p *NoProxy) Start(remoteConn net.Conn) error {
log.Debugf("using NoProxy while connecting to peer %s", p.config.RemoteKey)
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
}
addr.Port = p.RemoteWgListenPort
err = p.config.WgInterface.UpdatePeer(p.config.RemoteKey, p.config.AllowedIps, DefaultWgKeepAlive,
return p.config.WgInterface.UpdatePeer(p.config.RemoteKey, p.config.AllowedIps, DefaultWgKeepAlive,
addr, p.config.PreSharedKey)
if err != nil {
return err
}
return nil
}
func (p *NoProxy) Type() Type {

View File

@@ -13,9 +13,10 @@ const DefaultWgKeepAlive = 25 * time.Second
type Type string
const (
TypeNoProxy Type = "NoProxy"
TypeWireguard Type = "Wireguard"
TypeDummy Type = "Dummy"
TypeDirectNoProxy Type = "DirectNoProxy"
TypeWireGuard Type = "WireGuard"
TypeDummy Type = "Dummy"
TypeNoProxy Type = "NoProxy"
)
type Config struct {

View File

@@ -124,5 +124,5 @@ func (p *WireGuardProxy) proxyToLocal() {
}
func (p *WireGuardProxy) Type() Type {
return TypeWireguard
return TypeWireGuard
}

View File

@@ -17,9 +17,15 @@ const (
// WGIface represents a interface instance
type WGIface struct {
tun *tunDevice
configurer wGConfigurer
mu sync.Mutex
tun *tunDevice
configurer wGConfigurer
mu sync.Mutex
userspaceBind bool
}
// IsUserspaceBind indicates whether this interfaces is userspace with bind.ICEBind
func (w *WGIface) IsUserspaceBind() bool {
return w.userspaceBind
}
// GetBind returns a userspace implementation of WireGuard Bind interface
@@ -32,7 +38,7 @@ func (w *WGIface) GetBind() *bind.ICEBind {
func (w *WGIface) Create() error {
w.mu.Lock()
defer w.mu.Unlock()
log.Debugf("create Wireguard interface %s", w.tun.DeviceName())
log.Debugf("create WireGuard interface %s", w.tun.DeviceName())
return w.tun.Create()
}

View File

@@ -5,21 +5,23 @@ import (
"sync"
)
// NewWGIFace Creates a new Wireguard interface instance
func NewWGIFace(ifaceName string, address string, mtu int, tunAdapter TunAdapter, transportNet transport.Net) (*WGIface, error) {
wgIface := &WGIface{
// NewWGIFace Creates a new WireGuard interface instance
func NewWGIFace(iFaceName string, address string, mtu int, tunAdapter TunAdapter, transportNet transport.Net) (*WGIface, error) {
wgIFace := &WGIface{
mu: sync.Mutex{},
}
wgAddress, err := parseWGAddress(address)
if err != nil {
return wgIface, err
return wgIFace, err
}
tun := newTunDevice(wgAddress, mtu, tunAdapter, transportNet)
wgIface.tun = tun
wgIFace.tun = tun
wgIface.configurer = newWGConfigurer(tun)
wgIFace.configurer = newWGConfigurer(tun)
return wgIface, nil
wgIFace.userspaceBind = !WireGuardModuleIsLoaded()
return wgIFace, nil
}

View File

@@ -7,19 +7,20 @@ import (
"sync"
)
// NewWGIFace Creates a new Wireguard interface instance
func NewWGIFace(ifaceName string, address string, mtu int, tunAdapter TunAdapter, transportNet transport.Net) (*WGIface, error) {
wgIface := &WGIface{
// NewWGIFace Creates a new WireGuard interface instance
func NewWGIFace(iFaceName string, address string, mtu int, tunAdapter TunAdapter, transportNet transport.Net) (*WGIface, error) {
wgIFace := &WGIface{
mu: sync.Mutex{},
}
wgAddress, err := parseWGAddress(address)
if err != nil {
return wgIface, err
return wgIFace, err
}
wgIface.tun = newTunDevice(ifaceName, wgAddress, mtu, transportNet)
wgIFace.tun = newTunDevice(iFaceName, wgAddress, mtu, transportNet)
wgIface.configurer = newWGConfigurer(ifaceName)
return wgIface, nil
wgIFace.configurer = newWGConfigurer(iFaceName)
wgIFace.userspaceBind = !WireGuardModuleIsLoaded()
return wgIFace, nil
}

View File

@@ -3,7 +3,7 @@
package iface
// WireguardModuleIsLoaded check if we can load wireguard mod (linux only)
func WireguardModuleIsLoaded() bool {
// WireGuardModuleIsLoaded check if we can load WireGuard mod (linux only)
func WireGuardModuleIsLoaded() bool {
return false
}

View File

@@ -81,9 +81,10 @@ func tunModuleIsLoaded() bool {
return tunLoaded
}
// WireguardModuleIsLoaded check if we can load wireguard mod (linux only)
func WireguardModuleIsLoaded() bool {
if canCreateFakeWireguardInterface() {
// WireGuardModuleIsLoaded check if we can load WireGuard mod (linux only)
func WireGuardModuleIsLoaded() bool {
return false
if canCreateFakeWireGuardInterface() {
return true
}
@@ -96,7 +97,7 @@ func WireguardModuleIsLoaded() bool {
return loaded
}
func canCreateFakeWireguardInterface() bool {
func canCreateFakeWireGuardInterface() bool {
link := newWGLink("mustnotexist")
// We willingly try to create a device with an invalid

View File

@@ -11,7 +11,7 @@ import (
)
func (c *tunDevice) Create() error {
if WireguardModuleIsLoaded() {
if WireGuardModuleIsLoaded() {
log.Info("using kernel WireGuard")
return c.createWithKernel()
}