diff --git a/client/internal/connect.go b/client/internal/connect.go index 3aca0bab9..62b899d96 100644 --- a/client/internal/connect.go +++ b/client/internal/connect.go @@ -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(), } diff --git a/client/internal/engine.go b/client/internal/engine.go index f73a8e4b9..854b5220c 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -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) diff --git a/client/internal/peer/conn.go b/client/internal/peer/conn.go index f725f61d0..ee4440b8e 100644 --- a/client/internal/peer/conn.go +++ b/client/internal/peer/conn.go @@ -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) diff --git a/client/internal/peer/conn_test.go b/client/internal/peer/conn_test.go index a1fea337a..107852c51 100644 --- a/client/internal/peer/conn_test.go +++ b/client/internal/peer/conn_test.go @@ -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 { diff --git a/client/internal/proxy/direct.go b/client/internal/proxy/direct.go new file mode 100644 index 000000000..10123bd10 --- /dev/null +++ b/client/internal/proxy/direct.go @@ -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 +} diff --git a/client/internal/proxy/noproxy.go b/client/internal/proxy/noproxy.go index 1ad5c79de..dcfe182fd 100644 --- a/client/internal/proxy/noproxy.go +++ b/client/internal/proxy/noproxy.go @@ -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 { diff --git a/client/internal/proxy/proxy.go b/client/internal/proxy/proxy.go index 5a889388a..a0b9e98a1 100644 --- a/client/internal/proxy/proxy.go +++ b/client/internal/proxy/proxy.go @@ -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 { diff --git a/client/internal/proxy/wireguard.go b/client/internal/proxy/wireguard.go index 67b1c4498..ec3c6a730 100644 --- a/client/internal/proxy/wireguard.go +++ b/client/internal/proxy/wireguard.go @@ -124,5 +124,5 @@ func (p *WireGuardProxy) proxyToLocal() { } func (p *WireGuardProxy) Type() Type { - return TypeWireguard + return TypeWireGuard } diff --git a/iface/iface.go b/iface/iface.go index d1ea561e0..26d86d296 100644 --- a/iface/iface.go +++ b/iface/iface.go @@ -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() } diff --git a/iface/iface_android.go b/iface/iface_android.go index 292cf58cf..fb57f0762 100644 --- a/iface/iface_android.go +++ b/iface/iface_android.go @@ -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 } diff --git a/iface/iface_nonandroid.go b/iface/iface_nonandroid.go index 774a7bf3d..7168eea40 100644 --- a/iface/iface_nonandroid.go +++ b/iface/iface_nonandroid.go @@ -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 } diff --git a/iface/module.go b/iface/module.go index 31635fa65..7f337201d 100644 --- a/iface/module.go +++ b/iface/module.go @@ -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 } diff --git a/iface/module_linux.go b/iface/module_linux.go index af47b6ef5..a0caf26b4 100644 --- a/iface/module_linux.go +++ b/iface/module_linux.go @@ -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 diff --git a/iface/tun_linux.go b/iface/tun_linux.go index abe3e5543..bb3b5a47a 100644 --- a/iface/tun_linux.go +++ b/iface/tun_linux.go @@ -11,7 +11,7 @@ import ( ) func (c *tunDevice) Create() error { - if WireguardModuleIsLoaded() { + if WireGuardModuleIsLoaded() { log.Info("using kernel WireGuard") return c.createWithKernel() }