diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e4875bf390..41b00e8888 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ on: - 'client/ui/**' env: - SIGN_PIPE_VER: "v0.0.10" + SIGN_PIPE_VER: "v0.0.11" GORELEASER_VER: "v1.14.1" concurrency: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8fe1a5255a..9e658b51b2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -189,6 +189,8 @@ CGO_ENABLED=0 go build . > Windows clients have a Wireguard driver requirement. You can download the wintun driver from https://www.wintun.net/builds/wintun-0.14.1.zip, after decompressing, you can copy the file `windtun\bin\ARCH\wintun.dll` to the same path as your binary file or to `C:\Windows\System32\wintun.dll`. +> To test the client GUI application on Windows machines with RDP or vituralized environments (e.g. virtualbox or cloud), you need to download and extract the opengl32.dll from https://fdossena.com/?p=mesa/index.frag next to the built application. + To start NetBird the client in the foreground: ``` diff --git a/README.md b/README.md index ef391e90fc..fab580dbbb 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ https://user-images.githubusercontent.com/700848/197345890-2e2cded5-7b7a-436f-a4 | | | | | | | | | | | | | | | -| | | | | +| | | | | | | | | | | | | | | | | | | | diff --git a/client/cmd/login.go b/client/cmd/login.go index 5af8c17750..71153906cc 100644 --- a/client/cmd/login.go +++ b/client/cmd/login.go @@ -60,7 +60,7 @@ var loginCmd = &cobra.Command{ return fmt.Errorf("get config file: %v", err) } - config, _ = internal.UpdateOldManagementPort(ctx, config, configPath) + config, _ = internal.UpdateOldManagementURL(ctx, config, configPath) err = foregroundLogin(ctx, cmd, config, setupKey) if err != nil { diff --git a/client/cmd/up.go b/client/cmd/up.go index ebfcb2b9d0..e895b2f221 100644 --- a/client/cmd/up.go +++ b/client/cmd/up.go @@ -95,7 +95,7 @@ func runInForegroundMode(ctx context.Context, cmd *cobra.Command) error { return fmt.Errorf("get config file: %v", err) } - config, _ = internal.UpdateOldManagementPort(ctx, config, configPath) + config, _ = internal.UpdateOldManagementURL(ctx, config, configPath) err = foregroundLogin(ctx, cmd, config, setupKey) if err != nil { diff --git a/client/installer.nsis b/client/installer.nsis index fbffa326d8..af942a868e 100644 --- a/client/installer.nsis +++ b/client/installer.nsis @@ -193,6 +193,7 @@ Sleep 3000 Delete "$INSTDIR\${UI_APP_EXE}" Delete "$INSTDIR\${MAIN_APP_EXE}" Delete "$INSTDIR\wintun.dll" +Delete "$INSTDIR\opengl32.dll" RmDir /r "$INSTDIR" SetShellVarContext all diff --git a/client/internal/config.go b/client/internal/config.go index 8f433a0419..fdc6385f40 100644 --- a/client/internal/config.go +++ b/client/internal/config.go @@ -1,6 +1,7 @@ package internal import ( + "context" "fmt" "net/url" "os" @@ -12,16 +13,19 @@ import ( "github.com/netbirdio/netbird/client/ssh" "github.com/netbirdio/netbird/iface" + mgm "github.com/netbirdio/netbird/management/client" "github.com/netbirdio/netbird/util" ) const ( - // ManagementLegacyPort is the port that was used before by the Management gRPC server. + // managementLegacyPortString is the port that was used before by the Management gRPC server. // It is used for backward compatibility now. // NB: hardcoded from github.com/netbirdio/netbird/management/cmd to avoid import - ManagementLegacyPort = 33073 + managementLegacyPortString = "33073" // DefaultManagementURL points to the NetBird's cloud management endpoint - DefaultManagementURL = "https://api.wiretrustee.com:443" + DefaultManagementURL = "https://api.netbird.io:443" + // oldDefaultManagementURL points to the NetBird's old cloud management endpoint + oldDefaultManagementURL = "https://api.wiretrustee.com:443" // DefaultAdminURL points to NetBird's cloud management console DefaultAdminURL = "https://app.netbird.io:443" ) @@ -302,3 +306,86 @@ func configFileIsExists(path string) bool { _, err := os.Stat(path) return !os.IsNotExist(err) } + +// UpdateOldManagementURL checks whether client can switch to the new Management URL with port 443 and the management domain. +// If it can switch, then it updates the config and returns a new one. Otherwise, it returns the provided config. +// The check is performed only for the NetBird's managed version. +func UpdateOldManagementURL(ctx context.Context, config *Config, configPath string) (*Config, error) { + + defaultManagementURL, err := parseURL("Management URL", DefaultManagementURL) + if err != nil { + return nil, err + } + + parsedOldDefaultManagementURL, err := parseURL("Management URL", oldDefaultManagementURL) + if err != nil { + return nil, err + } + + if config.ManagementURL.Hostname() != defaultManagementURL.Hostname() && + config.ManagementURL.Hostname() != parsedOldDefaultManagementURL.Hostname() { + // only do the check for the NetBird's managed version + return config, nil + } + + var mgmTlsEnabled bool + if config.ManagementURL.Scheme == "https" { + mgmTlsEnabled = true + } + + if !mgmTlsEnabled { + // only do the check for HTTPs scheme (the hosted version of the Management service is always HTTPs) + return config, nil + } + + if config.ManagementURL.Port() != managementLegacyPortString && + config.ManagementURL.Hostname() == defaultManagementURL.Hostname() { + return config, nil + } + + newURL, err := parseURL("Management URL", fmt.Sprintf("%s://%s:%d", + config.ManagementURL.Scheme, defaultManagementURL.Hostname(), 443)) + if err != nil { + return nil, err + } + // here we check whether we could switch from the legacy 33073 port to the new 443 + log.Infof("attempting to switch from the legacy Management URL %s to the new one %s", + config.ManagementURL.String(), newURL.String()) + key, err := wgtypes.ParseKey(config.PrivateKey) + if err != nil { + log.Infof("couldn't switch to the new Management %s", newURL.String()) + return config, err + } + + client, err := mgm.NewClient(ctx, newURL.Host, key, mgmTlsEnabled) + if err != nil { + log.Infof("couldn't switch to the new Management %s", newURL.String()) + return config, err + } + defer func() { + err = client.Close() + if err != nil { + log.Warnf("failed to close the Management service client %v", err) + } + }() + + // gRPC check + _, err = client.GetServerPublicKey() + if err != nil { + log.Infof("couldn't switch to the new Management %s", newURL.String()) + return nil, err + } + + // everything is alright => update the config + newConfig, err := UpdateConfig(ConfigInput{ + ManagementURL: newURL.String(), + ConfigPath: configPath, + }) + if err != nil { + log.Infof("couldn't switch to the new Management %s", newURL.String()) + return config, fmt.Errorf("failed updating config file: %v", err) + } + log.Infof("successfully switched to the new Management URL: %s", newURL.String()) + + return newConfig, nil +} diff --git a/client/internal/config_test.go b/client/internal/config_test.go index eeec9b516b..7453c8fdf8 100644 --- a/client/internal/config_test.go +++ b/client/internal/config_test.go @@ -1,12 +1,14 @@ package internal import ( + "context" "errors" "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/netbirdio/netbird/util" ) @@ -120,3 +122,60 @@ func TestHiddenPreSharedKey(t *testing.T) { }) } } + +func TestUpdateOldManagementURL(t *testing.T) { + tests := []struct { + name string + previousManagementURL string + expectedManagementURL string + fileShouldNotChange bool + }{ + { + name: "Update old management URL with legacy port", + previousManagementURL: "https://api.wiretrustee.com:33073", + expectedManagementURL: DefaultManagementURL, + }, + { + name: "Update old management URL", + previousManagementURL: oldDefaultManagementURL, + expectedManagementURL: DefaultManagementURL, + }, + { + name: "No update needed when management URL is up to date", + previousManagementURL: DefaultManagementURL, + expectedManagementURL: DefaultManagementURL, + fileShouldNotChange: true, + }, + { + name: "No update needed when not using cloud management", + previousManagementURL: "https://netbird.example.com:33073", + expectedManagementURL: "https://netbird.example.com:33073", + fileShouldNotChange: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tempDir := t.TempDir() + configPath := filepath.Join(tempDir, "config.json") + config, err := UpdateOrCreateConfig(ConfigInput{ + ManagementURL: tt.previousManagementURL, + ConfigPath: configPath, + }) + require.NoError(t, err, "failed to create testing config") + previousStats, err := os.Stat(configPath) + require.NoError(t, err, "failed to create testing config stats") + resultConfig, err := UpdateOldManagementURL(context.TODO(), config, configPath) + require.NoError(t, err, "got error when updating old management url") + require.Equal(t, tt.expectedManagementURL, resultConfig.ManagementURL.String()) + newStats, err := os.Stat(configPath) + require.NoError(t, err, "failed to create testing config stats") + switch tt.fileShouldNotChange { + case true: + require.Equal(t, previousStats.ModTime(), newStats.ModTime(), "file should not change") + case false: + require.NotEqual(t, previousStats.ModTime(), newStats.ModTime(), "file should have changed") + } + }) + } +} diff --git a/client/internal/connect.go b/client/internal/connect.go index d8784c0c86..6c654ec491 100644 --- a/client/internal/connect.go +++ b/client/internal/connect.go @@ -283,83 +283,6 @@ func loginToManagement(ctx context.Context, client mgm.Client, pubSSHKey []byte) return loginResp, nil } -// UpdateOldManagementPort checks whether client can switch to the new Management port 443. -// If it can switch, then it updates the config and returns a new one. Otherwise, it returns the provided config. -// The check is performed only for the NetBird's managed version. -func UpdateOldManagementPort(ctx context.Context, config *Config, configPath string) (*Config, error) { - - defaultManagementURL, err := parseURL("Management URL", DefaultManagementURL) - if err != nil { - return nil, err - } - - if config.ManagementURL.Hostname() != defaultManagementURL.Hostname() { - // only do the check for the NetBird's managed version - return config, nil - } - - var mgmTlsEnabled bool - if config.ManagementURL.Scheme == "https" { - mgmTlsEnabled = true - } - - if !mgmTlsEnabled { - // only do the check for HTTPs scheme (the hosted version of the Management service is always HTTPs) - return config, nil - } - - if mgmTlsEnabled && config.ManagementURL.Port() == fmt.Sprintf("%d", ManagementLegacyPort) { - - newURL, err := parseURL("Management URL", fmt.Sprintf("%s://%s:%d", - config.ManagementURL.Scheme, config.ManagementURL.Hostname(), 443)) - if err != nil { - return nil, err - } - // here we check whether we could switch from the legacy 33073 port to the new 443 - log.Infof("attempting to switch from the legacy Management URL %s to the new one %s", - config.ManagementURL.String(), newURL.String()) - key, err := wgtypes.ParseKey(config.PrivateKey) - if err != nil { - log.Infof("couldn't switch to the new Management %s", newURL.String()) - return config, err - } - - client, err := mgm.NewClient(ctx, newURL.Host, key, mgmTlsEnabled) - if err != nil { - log.Infof("couldn't switch to the new Management %s", newURL.String()) - return config, err - } - defer func() { - err = client.Close() - if err != nil { - log.Warnf("failed to close the Management service client %v", err) - } - }() - - // gRPC check - _, err = client.GetServerPublicKey() - if err != nil { - log.Infof("couldn't switch to the new Management %s", newURL.String()) - return nil, err - } - - // everything is alright => update the config - newConfig, err := UpdateConfig(ConfigInput{ - ManagementURL: newURL.String(), - ConfigPath: configPath, - }) - if err != nil { - log.Infof("couldn't switch to the new Management %s", newURL.String()) - return config, fmt.Errorf("failed updating config file: %v", err) - } - log.Infof("successfully switched to the new Management URL: %s", newURL.String()) - - return newConfig, nil - } - - return config, nil -} - func statusRecorderToMgmConnStateNotifier(statusRecorder *peer.Status) mgm.ConnStateNotifier { var sri interface{} = statusRecorder mgmNotifier, _ := sri.(mgm.ConnStateNotifier) diff --git a/client/internal/dns/server_test.go b/client/internal/dns/server_test.go index 67d411df5a..5f67a6ece5 100644 --- a/client/internal/dns/server_test.go +++ b/client/internal/dns/server_test.go @@ -12,6 +12,7 @@ import ( "github.com/golang/mock/gomock" log "github.com/sirupsen/logrus" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "github.com/netbirdio/netbird/client/firewall/uspfilter" "github.com/netbirdio/netbird/client/internal/stdnet" @@ -250,11 +251,12 @@ func TestUpdateDNSServer(t *testing.T) { for n, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { + privKey, _ := wgtypes.GenerateKey() newNet, err := stdnet.NewNet(nil) if err != nil { t.Fatal(err) } - wgIface, err := iface.NewWGIFace(fmt.Sprintf("utun230%d", n), fmt.Sprintf("100.66.100.%d/32", n+1), iface.DefaultMTU, nil, newNet) + wgIface, err := iface.NewWGIFace(fmt.Sprintf("utun230%d", n), fmt.Sprintf("100.66.100.%d/32", n+1), 33100, privKey.String(), iface.DefaultMTU, newNet, nil) if err != nil { t.Fatal(err) } @@ -331,7 +333,8 @@ func TestDNSFakeResolverHandleUpdates(t *testing.T) { return } - wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.1/32", iface.DefaultMTU, nil, newNet) + privKey, _ := wgtypes.GeneratePrivateKey() + wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.1/32", 33100, privKey.String(), iface.DefaultMTU, newNet, nil) if err != nil { t.Errorf("build interface wireguard: %v", err) return @@ -782,7 +785,8 @@ func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) { return nil, err } - wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.2/24", iface.DefaultMTU, nil, newNet) + privKey, _ := wgtypes.GeneratePrivateKey() + wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.2/24", 33100, privKey.String(), iface.DefaultMTU, newNet, nil) if err != nil { t.Fatalf("build interface wireguard: %v", err) return nil, err diff --git a/client/internal/engine.go b/client/internal/engine.go index d811ad48cf..3c421aabd1 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -3,7 +3,6 @@ package internal import ( "context" "fmt" - "io" "math/rand" "net" "net/netip" @@ -32,7 +31,6 @@ import ( mgm "github.com/netbirdio/netbird/management/client" mgmProto "github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/route" - "github.com/netbirdio/netbird/sharedsock" signal "github.com/netbirdio/netbird/signal/client" sProto "github.com/netbirdio/netbird/signal/proto" "github.com/netbirdio/netbird/util" @@ -107,8 +105,7 @@ type Engine struct { wgInterface *iface.WGIface wgProxyFactory *wgproxy.Factory - udpMux *bind.UniversalUDPMuxDefault - udpMuxConn io.Closer + udpMux *bind.UniversalUDPMuxDefault // networkSerial is the latest CurrentSerial (state ID) of the network sent by the Management service networkSerial uint64 @@ -181,66 +178,26 @@ func (e *Engine) Start() error { e.syncMsgMux.Lock() defer e.syncMsgMux.Unlock() - wgIFaceName := e.config.WgIfaceName - wgAddr := e.config.WgAddr - myPrivateKey := e.config.WgPrivateKey - var err error - transportNet, err := e.newStdNet() + wgIface, err := e.newWgIface() if err != nil { - log.Errorf("failed to create pion's stdnet: %s", err) + log.Errorf("failed creating wireguard interface instance %s: [%s]", e.config.WgIfaceName, err.Error()) + return err } + e.wgInterface = wgIface - e.wgInterface, err = iface.NewWGIFace(wgIFaceName, wgAddr, iface.DefaultMTU, e.mobileDep.TunAdapter, transportNet) + initialRoutes, dnsServer, err := e.newDnsServer() if err != nil { - log.Errorf("failed creating wireguard interface instance %s: [%s]", wgIFaceName, err.Error()) + e.close() return err } + e.dnsServer = dnsServer - var routes []*route.Route - - switch runtime.GOOS { - case "android": - var dnsConfig *nbdns.Config - routes, dnsConfig, err = e.readInitialSettings() - if err != nil { - return err - } - if e.dnsServer == nil { - e.dnsServer = dns.NewDefaultServerPermanentUpstream(e.ctx, e.wgInterface, e.mobileDep.HostDNSAddresses, *dnsConfig, e.mobileDep.NetworkChangeListener) - go e.mobileDep.DnsReadyListener.OnReady() - } - case "ios": - if e.dnsServer == nil { - e.dnsServer = dns.NewDefaultServerIos(e.ctx, e.wgInterface, e.mobileDep.DnsManager) - } - default: - if e.dnsServer == nil { - e.dnsServer, err = dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress) - if err != nil { - e.close() - return err - } - } - } - - e.routeManager = routemanager.NewManager(e.ctx, e.config.WgPrivateKey.PublicKey().String(), e.wgInterface, e.statusRecorder, routes) + e.routeManager = routemanager.NewManager(e.ctx, e.config.WgPrivateKey.PublicKey().String(), e.wgInterface, e.statusRecorder, initialRoutes) e.routeManager.SetRouteChangeListener(e.mobileDep.NetworkChangeListener) - switch runtime.GOOS { - case "android": - err = e.wgInterface.CreateOnAndroid(iface.MobileIFaceArguments{ - Routes: e.routeManager.InitialRouteRange(), - Dns: e.dnsServer.DnsIP(), - SearchDomains: e.dnsServer.SearchDomains(), - }) - case "ios": - e.mobileDep.NetworkChangeListener.SetInterfaceIP(wgAddr) - err = e.wgInterface.CreateOniOS(e.mobileDep.FileDescriptor) - default: - err = e.wgInterface.Create() - } + err = e.wgInterfaceCreate() if err != nil { - log.Errorf("failed creating tunnel interface %s: [%s]", wgIFaceName, err.Error()) + log.Errorf("failed creating tunnel interface %s: [%s]", e.config.WgIfaceName, err.Error()) e.close() return err } @@ -258,33 +215,13 @@ func (e *Engine) Start() error { } } - err = e.wgInterface.Configure(myPrivateKey.String(), e.config.WgPort) + e.udpMux, err = e.wgInterface.Up() if err != nil { - log.Errorf("failed configuring Wireguard interface [%s]: %s", wgIFaceName, err.Error()) + log.Errorf("failed to pull up wgInterface [%s]: %s", e.wgInterface.Name(), err.Error()) 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 - log.Infof("using userspace bind mode %s", udpMux.LocalAddr().String()) - } else { - rawSock, err := sharedsock.Listen(e.config.WgPort, sharedsock.NewIncomingSTUNFilter()) - if err != nil { - return err - } - mux := bind.NewUniversalUDPMuxDefault(bind.UniversalUDPMuxParams{UDPConn: rawSock, Net: transportNet}) - go mux.ReadFromConn(e.ctx) - e.udpMuxConn = rawSock - e.udpMux = mux - } - if e.firewall != nil { e.acl = acl.NewDefaultManager(e.firewall) } @@ -1042,18 +979,6 @@ func (e *Engine) close() { } } - if e.udpMux != nil { - if err := e.udpMux.Close(); err != nil { - log.Debugf("close udp mux: %v", err) - } - } - - if e.udpMuxConn != nil { - if err := e.udpMuxConn.Close(); err != nil { - log.Debugf("close udp mux connection: %v", err) - } - } - if !isNil(e.sshServer) { err := e.sshServer.Stop() if err != nil { @@ -1087,6 +1012,68 @@ func (e *Engine) readInitialSettings() ([]*route.Route, *nbdns.Config, error) { return routes, &dnsCfg, nil } +func (e *Engine) newWgIface() (*iface.WGIface, error) { + transportNet, err := e.newStdNet() + if err != nil { + log.Errorf("failed to create pion's stdnet: %s", err) + } + + var mArgs *iface.MobileIFaceArguments + switch runtime.GOOS { + case "android": + mArgs = &iface.MobileIFaceArguments{ + TunAdapter: e.mobileDep.TunAdapter, + TunFd: int(e.mobileDep.FileDescriptor), + } + case "ios": + mArgs = &iface.MobileIFaceArguments{ + TunFd: int(e.mobileDep.FileDescriptor), + } + default: + } + + return iface.NewWGIFace(e.config.WgIfaceName, e.config.WgAddr, e.config.WgPort, e.config.WgPrivateKey.String(), iface.DefaultMTU, transportNet, mArgs) +} + +func (e *Engine) wgInterfaceCreate() (err error) { + switch runtime.GOOS { + case "android": + err = e.wgInterface.CreateOnAndroid(e.routeManager.InitialRouteRange(), e.dnsServer.DnsIP(), e.dnsServer.SearchDomains()) + case "ios": + e.mobileDep.NetworkChangeListener.SetInterfaceIP(e.config.WgAddr) + err = e.wgInterface.Create() + default: + err = e.wgInterface.Create() + } + return err +} + +func (e *Engine) newDnsServer() ([]*route.Route, dns.Server, error) { + // due to tests where we are using a mocked version of the DNS server + if e.dnsServer != nil { + return nil, e.dnsServer, nil + } + switch runtime.GOOS { + case "android": + routes, dnsConfig, err := e.readInitialSettings() + if err != nil { + return nil, nil, err + } + dnsServer := dns.NewDefaultServerPermanentUpstream(e.ctx, e.wgInterface, e.mobileDep.HostDNSAddresses, *dnsConfig, e.mobileDep.NetworkChangeListener) + go e.mobileDep.DnsReadyListener.OnReady() + return routes, dnsServer, nil + case "ios": + dnsServer := dns.NewDefaultServerIos(e.ctx, e.wgInterface, e.mobileDep.DnsManager) + return nil, dnsServer, nil + default: + dnsServer, err := dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress) + if err != nil { + return nil, nil, err + } + return nil, dnsServer, nil + } +} + func findIPFromInterfaceName(ifaceName string) (net.IP, error) { iface, err := net.InterfaceByName(ifaceName) if err != nil { diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go index 2de9b29f04..5dfc171a63 100644 --- a/client/internal/engine_test.go +++ b/client/internal/engine_test.go @@ -213,7 +213,7 @@ func TestEngine_UpdateNetworkMap(t *testing.T) { if err != nil { t.Fatal(err) } - engine.wgInterface, err = iface.NewWGIFace("utun102", "100.64.0.1/24", iface.DefaultMTU, nil, newNet) + engine.wgInterface, err = iface.NewWGIFace("utun102", "100.64.0.1/24", engine.config.WgPort, key.String(), iface.DefaultMTU, newNet, nil) if err != nil { t.Fatal(err) } @@ -567,7 +567,7 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) { if err != nil { t.Fatal(err) } - engine.wgInterface, err = iface.NewWGIFace(wgIfaceName, wgAddr, iface.DefaultMTU, nil, newNet) + engine.wgInterface, err = iface.NewWGIFace(wgIfaceName, wgAddr, engine.config.WgPort, key.String(), iface.DefaultMTU, newNet, nil) assert.NoError(t, err, "shouldn't return error") input := struct { inputSerial uint64 @@ -736,7 +736,7 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) { if err != nil { t.Fatal(err) } - engine.wgInterface, err = iface.NewWGIFace(wgIfaceName, wgAddr, iface.DefaultMTU, nil, newNet) + engine.wgInterface, err = iface.NewWGIFace(wgIfaceName, wgAddr, 33100, key.String(), iface.DefaultMTU, newNet, nil) assert.NoError(t, err, "shouldn't return error") mockRouteManager := &routemanager.MockManager{ diff --git a/client/internal/mobile_dependency.go b/client/internal/mobile_dependency.go index 1a2a4c2b2f..2355c67c3b 100644 --- a/client/internal/mobile_dependency.go +++ b/client/internal/mobile_dependency.go @@ -9,11 +9,14 @@ import ( // MobileDependency collect all dependencies for mobile platform type MobileDependency struct { + // Android only TunAdapter iface.TunAdapter IFaceDiscover stdnet.ExternalIFaceDiscover NetworkChangeListener listener.NetworkChangeListener HostDNSAddresses []string DnsReadyListener dns.ReadyListener - DnsManager dns.IosDnsManager - FileDescriptor int32 + + // iOS only + DnsManager dns.IosDnsManager + FileDescriptor int32 } diff --git a/client/internal/routemanager/manager_test.go b/client/internal/routemanager/manager_test.go index 1aa58c16b7..2e5cf6649d 100644 --- a/client/internal/routemanager/manager_test.go +++ b/client/internal/routemanager/manager_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/pion/transport/v3/stdnet" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "github.com/stretchr/testify/require" @@ -399,12 +400,12 @@ func TestManagerUpdateRoutes(t *testing.T) { for n, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - + peerPrivateKey, _ := wgtypes.GeneratePrivateKey() newNet, err := stdnet.NewNet() if err != nil { t.Fatal(err) } - wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun43%d", n), "100.65.65.2/24", iface.DefaultMTU, nil, newNet) + wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun43%d", n), "100.65.65.2/24", 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil) require.NoError(t, err, "should create testing WGIface interface") defer wgInterface.Close() diff --git a/client/internal/routemanager/notifier.go b/client/internal/routemanager/notifier.go index e27d08db57..ede8f02c4f 100644 --- a/client/internal/routemanager/notifier.go +++ b/client/internal/routemanager/notifier.go @@ -45,7 +45,7 @@ func (n *notifier) onNewRoutes(idMap map[string][]*route.Route) { } sort.Strings(newNets) - if !n.hasDiff(n.routeRangers, newNets) { + if !n.hasDiff(n.initialRouteRangers, newNets) { return } diff --git a/client/internal/routemanager/systemops_nonandroid_test.go b/client/internal/routemanager/systemops_nonandroid_test.go index f43a88eec6..6f32d9634b 100644 --- a/client/internal/routemanager/systemops_nonandroid_test.go +++ b/client/internal/routemanager/systemops_nonandroid_test.go @@ -14,6 +14,7 @@ import ( "github.com/pion/transport/v3/stdnet" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "github.com/netbirdio/netbird/iface" ) @@ -41,11 +42,12 @@ func TestAddRemoveRoutes(t *testing.T) { for n, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { + peerPrivateKey, _ := wgtypes.GeneratePrivateKey() newNet, err := stdnet.NewNet() if err != nil { t.Fatal(err) } - wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun53%d", n), "100.65.75.2/24", iface.DefaultMTU, nil, newNet) + wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun53%d", n), "100.65.75.2/24", 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil) require.NoError(t, err, "should create testing WGIface interface") defer wgInterface.Close() @@ -175,11 +177,12 @@ func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) { log.SetOutput(os.Stderr) }() t.Run(testCase.name, func(t *testing.T) { + peerPrivateKey, _ := wgtypes.GeneratePrivateKey() newNet, err := stdnet.NewNet() if err != nil { t.Fatal(err) } - wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun53%d", n), "100.65.75.2/24", iface.DefaultMTU, nil, newNet) + wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun53%d", n), "100.65.75.2/24", 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil) require.NoError(t, err, "should create testing WGIface interface") defer wgInterface.Close() diff --git a/client/netbird.wxs b/client/netbird.wxs index f9b2449bac..0e2be7b3ca 100644 --- a/client/netbird.wxs +++ b/client/netbird.wxs @@ -20,6 +20,7 @@ + 65535 { + log.Warnf("invalid socks5 listener port, it should be in the range 1-65535, falling back to default: %d", DefaultSocks5Port) + return listenAddr(DefaultSocks5Port) + } + + return listenAddr(port) +} + +func listenAddr(port int) string { + return fmt.Sprintf("0.0.0.0:%d", port) +} diff --git a/iface/netstack/proxy.go b/iface/netstack/proxy.go new file mode 100644 index 0000000000..a2120c642f --- /dev/null +++ b/iface/netstack/proxy.go @@ -0,0 +1,65 @@ +package netstack + +import ( + "net" + + "github.com/things-go/go-socks5" + + log "github.com/sirupsen/logrus" +) + +const ( + DefaultSocks5Port = 1080 +) + +// Proxy todo close server +type Proxy struct { + server *socks5.Server + + listener net.Listener + closed bool +} + +func NewSocks5(dialer Dialer) (*Proxy, error) { + server := socks5.NewServer( + socks5.WithDial(dialer.Dial), + ) + + return &Proxy{ + server: server, + }, nil +} + +func (s *Proxy) ListenAndServe(addr string) error { + listener, err := net.Listen("tcp", addr) + if err != nil { + log.Errorf("failed to create listener for socks5 proxy: %s", err) + return err + } + s.listener = listener + + for { + conn, err := listener.Accept() + if err != nil { + if s.closed { + return nil + } + return err + } + + go func() { + if err := s.server.ServeConn(conn); err != nil { + log.Errorf("failed to serve a connection: %s", err) + } + }() + } +} + +func (s *Proxy) Close() error { + if s.listener == nil { + return nil + } + + s.closed = true + return s.listener.Close() +} diff --git a/iface/netstack/tun.go b/iface/netstack/tun.go new file mode 100644 index 0000000000..8c7c3a8ff5 --- /dev/null +++ b/iface/netstack/tun.go @@ -0,0 +1,74 @@ +package netstack + +import ( + "net/netip" + + log "github.com/sirupsen/logrus" + "golang.zx2c4.com/wireguard/tun" + "golang.zx2c4.com/wireguard/tun/netstack" +) + +type NetStackTun struct { + address string + mtu int + listenAddress string + + proxy *Proxy + tundev tun.Device +} + +func NewNetStackTun(listenAddress string, address string, mtu int) *NetStackTun { + return &NetStackTun{ + address: address, + mtu: mtu, + listenAddress: listenAddress, + } +} + +func (t *NetStackTun) Create() (tun.Device, error) { + nsTunDev, tunNet, err := netstack.CreateNetTUN( + []netip.Addr{netip.MustParseAddr(t.address)}, + []netip.Addr{}, + t.mtu) + if err != nil { + return nil, err + } + t.tundev = nsTunDev + + dialer := NewNSDialer(tunNet) + t.proxy, err = NewSocks5(dialer) + if err != nil { + _ = t.tundev.Close() + return nil, err + } + + go func() { + err := t.proxy.ListenAndServe(t.listenAddress) + if err != nil { + log.Errorf("error in socks5 proxy serving: %s", err) + } + }() + + return nsTunDev, nil +} + +func (t *NetStackTun) Close() error { + var err error + if t.proxy != nil { + pErr := t.proxy.Close() + if pErr != nil { + log.Errorf("failed to close socks5 proxy: %s", pErr) + err = pErr + } + } + + if t.tundev != nil { + dErr := t.tundev.Close() + if dErr != nil { + log.Errorf("failed to close netstack tun device: %s", dErr) + err = dErr + } + } + + return err +} diff --git a/iface/tun.go b/iface/tun.go index ec8af4c322..b3c0f9d803 100644 --- a/iface/tun.go +++ b/iface/tun.go @@ -1,12 +1,18 @@ +//go:build !android +// +build !android + package iface -type MobileIFaceArguments struct { - Routes []string - Dns string - SearchDomains []string -} +import ( + "github.com/netbirdio/netbird/iface/bind" +) -// NetInterface represents a generic network tunnel interface -type NetInterface interface { +type wgTunDevice interface { + Create() (wgConfigurer, error) + Up() (*bind.UniversalUDPMuxDefault, error) + UpdateAddr(address WGAddress) error + WgAddress() WGAddress + DeviceName() string Close() error + Wrapper() *DeviceWrapper // todo eliminate this function } diff --git a/iface/tun_android.go b/iface/tun_android.go index 3600001bad..834b2cb42d 100644 --- a/iface/tun_android.go +++ b/iface/tun_android.go @@ -15,42 +15,50 @@ import ( "github.com/netbirdio/netbird/iface/bind" ) -type tunDevice struct { +// ignore the wgTunDevice interface on Android because the creation of the tun device is different on this platform +type wgTunDevice struct { address WGAddress + port int + key string mtu int - tunAdapter TunAdapter iceBind *bind.ICEBind + tunAdapter TunAdapter - fd int - name string - device *device.Device - wrapper *DeviceWrapper + name string + device *device.Device + wrapper *DeviceWrapper + udpMux *bind.UniversalUDPMuxDefault + configurer wgConfigurer } -func newTunDevice(address WGAddress, mtu int, tunAdapter TunAdapter, transportNet transport.Net) *tunDevice { - return &tunDevice{ +func newTunDevice(address WGAddress, port int, key string, mtu int, transportNet transport.Net, tunAdapter TunAdapter) wgTunDevice { + return wgTunDevice{ address: address, + port: port, + key: key, mtu: mtu, - tunAdapter: tunAdapter, iceBind: bind.NewICEBind(transportNet), + tunAdapter: tunAdapter, } } -func (t *tunDevice) Create(mIFaceArgs MobileIFaceArguments) error { +func (t *wgTunDevice) Create(routes []string, dns string, searchDomains []string) (wgConfigurer, error) { log.Info("create tun interface") - var err error - routesString := t.routesToString(mIFaceArgs.Routes) - searchDomainsToString := t.searchDomainsToString(mIFaceArgs.SearchDomains) - t.fd, err = t.tunAdapter.ConfigureInterface(t.address.String(), t.mtu, mIFaceArgs.Dns, searchDomainsToString, routesString) + + routesString := routesToString(routes) + searchDomainsToString := searchDomainsToString(searchDomains) + + fd, err := t.tunAdapter.ConfigureInterface(t.address.String(), t.mtu, dns, searchDomainsToString, routesString) if err != nil { log.Errorf("failed to create Android interface: %s", err) - return err + return nil, err } - tunDevice, name, err := tun.CreateUnmonitoredTUNFromFD(t.fd) + tunDevice, name, err := tun.CreateUnmonitoredTUNFromFD(fd) if err != nil { - unix.Close(t.fd) - return err + _ = unix.Close(fd) + log.Errorf("failed to create Android interface: %s", err) + return nil, err } t.name = name t.wrapper = newDeviceWrapper(tunDevice) @@ -61,44 +69,72 @@ func (t *tunDevice) Create(mIFaceArgs MobileIFaceArguments) error { // this helps with support for the older NetBird clients that had a hardcoded direct mode // t.device.DisableSomeRoamingForBrokenMobileSemantics() - err = t.device.Up() + t.configurer = newWGUSPConfigurer(t.device, t.name) + err = t.configurer.configureInterface(t.key, t.port) if err != nil { t.device.Close() - return err + t.configurer.close() + return nil, err } - log.Debugf("device is ready to use: %s", name) - return nil -} - -func (t *tunDevice) Device() *device.Device { - return t.device -} - -func (t *tunDevice) DeviceName() string { - return t.name + return t.configurer, nil } +func (t *wgTunDevice) Up() (*bind.UniversalUDPMuxDefault, error) { + err := t.device.Up() + if err != nil { + return nil, err + } -func (t *tunDevice) WgAddress() WGAddress { - return t.address + udpMux, err := t.iceBind.GetICEMux() + if err != nil { + return nil, err + } + t.udpMux = udpMux + log.Debugf("device is ready to use: %s", t.name) + return udpMux, nil } -func (t *tunDevice) UpdateAddr(addr WGAddress) error { +func (t *wgTunDevice) UpdateAddr(addr WGAddress) error { // todo implement return nil } -func (t *tunDevice) Close() (err error) { +func (t *wgTunDevice) Close() error { + if t.configurer != nil { + t.configurer.close() + } + if t.device != nil { t.device.Close() + t.device = nil } - return + if t.udpMux != nil { + return t.udpMux.Close() + + } + return nil +} + +func (t *wgTunDevice) Device() *device.Device { + return t.device +} + +func (t *wgTunDevice) DeviceName() string { + return t.name +} + +func (t *wgTunDevice) WgAddress() WGAddress { + return t.address +} + +func (t *wgTunDevice) Wrapper() *DeviceWrapper { + return t.wrapper } -func (t *tunDevice) routesToString(routes []string) string { +func routesToString(routes []string) string { return strings.Join(routes, ";") } -func (t *tunDevice) searchDomainsToString(searchDomains []string) string { +func searchDomainsToString(searchDomains []string) string { return strings.Join(searchDomains, ";") } diff --git a/iface/tun_args.go b/iface/tun_args.go new file mode 100644 index 0000000000..0eac2c4c0e --- /dev/null +++ b/iface/tun_args.go @@ -0,0 +1,6 @@ +package iface + +type MobileIFaceArguments struct { + TunAdapter TunAdapter // only for Android + TunFd int // only for iOS +} diff --git a/iface/tun_darwin.go b/iface/tun_darwin.go index 6e917e3748..bac14986f1 100644 --- a/iface/tun_darwin.go +++ b/iface/tun_darwin.go @@ -6,32 +6,129 @@ package iface import ( "os/exec" + "github.com/pion/transport/v3" log "github.com/sirupsen/logrus" + "golang.zx2c4.com/wireguard/device" + "golang.zx2c4.com/wireguard/tun" + + "github.com/netbirdio/netbird/iface/bind" ) -func (c *tunDevice) Create() error { - var err error - c.netInterface, err = c.createWithUserspace() +type tunDevice struct { + name string + address WGAddress + port int + key string + mtu int + iceBind *bind.ICEBind + + device *device.Device + wrapper *DeviceWrapper + udpMux *bind.UniversalUDPMuxDefault + configurer wgConfigurer +} + +func newTunDevice(name string, address WGAddress, port int, key string, mtu int, transportNet transport.Net) wgTunDevice { + return &tunDevice{ + name: name, + address: address, + port: port, + key: key, + mtu: mtu, + iceBind: bind.NewICEBind(transportNet), + } +} + +func (t *tunDevice) Create() (wgConfigurer, error) { + tunDevice, err := tun.CreateTUN(t.name, t.mtu) if err != nil { - return err + return nil, err + } + t.wrapper = newDeviceWrapper(tunDevice) + + // We need to create a wireguard-go device and listen to configuration requests + t.device = device.NewDevice( + t.wrapper, + t.iceBind, + device.NewLogger(device.LogLevelSilent, "[netbird] "), + ) + + err = t.assignAddr() + if err != nil { + t.device.Close() + return nil, err + } + + t.configurer = newWGUSPConfigurer(t.device, t.name) + err = t.configurer.configureInterface(t.key, t.port) + if err != nil { + t.device.Close() + t.configurer.close() + return nil, err + } + return t.configurer, nil +} + +func (t *tunDevice) Up() (*bind.UniversalUDPMuxDefault, error) { + err := t.device.Up() + if err != nil { + return nil, err } - return c.assignAddr() + udpMux, err := t.iceBind.GetICEMux() + if err != nil { + return nil, err + } + t.udpMux = udpMux + log.Debugf("device is ready to use: %s", t.name) + return udpMux, nil +} + +func (t *tunDevice) UpdateAddr(address WGAddress) error { + t.address = address + return t.assignAddr() +} + +func (t *tunDevice) Close() error { + if t.configurer != nil { + t.configurer.close() + } + + if t.device != nil { + t.device.Close() + t.device = nil + } + + if t.udpMux != nil { + return t.udpMux.Close() + } + return nil +} + +func (t *tunDevice) WgAddress() WGAddress { + return t.address +} + +func (t *tunDevice) DeviceName() string { + return t.name +} + +func (t *tunDevice) Wrapper() *DeviceWrapper { + return t.wrapper } // assignAddr Adds IP address to the tunnel interface and network route based on the range provided -func (c *tunDevice) assignAddr() error { - cmd := exec.Command("ifconfig", c.name, "inet", c.address.IP.String(), c.address.IP.String()) +func (t *tunDevice) assignAddr() error { + cmd := exec.Command("ifconfig", t.name, "inet", t.address.IP.String(), t.address.IP.String()) if out, err := cmd.CombinedOutput(); err != nil { log.Infof(`adding address command "%v" failed with output %s and error: `, cmd.String(), out) return err } - routeCmd := exec.Command("route", "add", "-net", c.address.Network.String(), "-interface", c.name) + routeCmd := exec.Command("route", "add", "-net", t.address.Network.String(), "-interface", t.name) if out, err := routeCmd.CombinedOutput(); err != nil { log.Printf(`adding route command "%v" failed with output %s and error: `, routeCmd.String(), out) return err } - return nil } diff --git a/iface/tun_ios.go b/iface/tun_ios.go index 7a9ce5622e..ea980818d7 100644 --- a/iface/tun_ios.go +++ b/iface/tun_ios.go @@ -6,7 +6,7 @@ package iface import ( "os" - "github.com/pion/transport/v2" + "github.com/pion/transport/v3" log "github.com/sirupsen/logrus" "golang.org/x/sys/unix" "golang.zx2c4.com/wireguard/device" @@ -16,63 +16,82 @@ import ( ) type tunDevice struct { - address WGAddress - mtu int - tunAdapter TunAdapter - iceBind *bind.ICEBind - - fd int name string - device *device.Device - wrapper *DeviceWrapper + address WGAddress + port int + key string + iceBind *bind.ICEBind + tunFd int + + device *device.Device + wrapper *DeviceWrapper + udpMux *bind.UniversalUDPMuxDefault + configurer wgConfigurer } -func newTunDevice(name string, address WGAddress, mtu int, tunAdapter TunAdapter, transportNet transport.Net) *tunDevice { +func newTunDevice(name string, address WGAddress, port int, key string, transportNet transport.Net, tunFd int) *tunDevice { return &tunDevice{ - name: name, - address: address, - mtu: mtu, - tunAdapter: tunAdapter, - iceBind: bind.NewICEBind(transportNet), + name: name, + address: address, + port: port, + key: key, + iceBind: bind.NewICEBind(transportNet), + tunFd: tunFd, } } -func (t *tunDevice) Create(tunFd int32) error { +func (t *tunDevice) Create() (wgConfigurer, error) { log.Infof("create tun interface") - dupTunFd, err := unix.Dup(int(tunFd)) + dupTunFd, err := unix.Dup(t.tunFd) if err != nil { log.Errorf("Unable to dup tun fd: %v", err) - return err + return nil, err } err = unix.SetNonblock(dupTunFd, true) if err != nil { log.Errorf("Unable to set tun fd as non blocking: %v", err) - unix.Close(dupTunFd) - return err + _ = unix.Close(dupTunFd) + return nil, err } - tun, err := tun.CreateTUNFromFile(os.NewFile(uintptr(dupTunFd), "/dev/tun"), 0) + tunDevice, err := tun.CreateTUNFromFile(os.NewFile(uintptr(dupTunFd), "/dev/tun"), 0) if err != nil { log.Errorf("Unable to create new tun device from fd: %v", err) - unix.Close(dupTunFd) - return err + _ = unix.Close(dupTunFd) + return nil, err } - t.wrapper = newDeviceWrapper(tun) + t.wrapper = newDeviceWrapper(tunDevice) log.Debug("Attaching to interface") t.device = device.NewDevice(t.wrapper, t.iceBind, device.NewLogger(device.LogLevelSilent, "[wiretrustee] ")) // without this property mobile devices can discover remote endpoints if the configured one was wrong. // this helps with support for the older NetBird clients that had a hardcoded direct mode // t.device.DisableSomeRoamingForBrokenMobileSemantics() - err = t.device.Up() + t.configurer = newWGUSPConfigurer(t.device, t.name) + err = t.configurer.configureInterface(t.key, t.port) if err != nil { t.device.Close() - return err + t.configurer.close() + return nil, err + } + return t.configurer, nil +} + +func (t *tunDevice) Up() (*bind.UniversalUDPMuxDefault, error) { + err := t.device.Up() + if err != nil { + return nil, err + } + + udpMux, err := t.iceBind.GetICEMux() + if err != nil { + return nil, err } + t.udpMux = udpMux log.Debugf("device is ready to use: %s", t.name) - return nil + return udpMux, nil } func (t *tunDevice) Device() *device.Device { @@ -83,6 +102,23 @@ func (t *tunDevice) DeviceName() string { return t.name } +func (t *tunDevice) Close() error { + if t.configurer != nil { + t.configurer.close() + } + + if t.device != nil { + t.device.Close() + t.device = nil + } + + if t.udpMux != nil { + return t.udpMux.Close() + + } + return nil +} + func (t *tunDevice) WgAddress() WGAddress { return t.address } @@ -92,10 +128,6 @@ func (t *tunDevice) UpdateAddr(addr WGAddress) error { return nil } -func (t *tunDevice) Close() (err error) { - if t.device != nil { - t.device.Close() - } - - return +func (t *tunDevice) Wrapper() *DeviceWrapper { + return t.wrapper } diff --git a/iface/tun_kernel_linux.go b/iface/tun_kernel_linux.go new file mode 100644 index 0000000000..12adcdf737 --- /dev/null +++ b/iface/tun_kernel_linux.go @@ -0,0 +1,209 @@ +//go:build linux && !android + +package iface + +import ( + "context" + "fmt" + "net" + "os" + + "github.com/pion/transport/v3" + log "github.com/sirupsen/logrus" + "github.com/vishvananda/netlink" + + "github.com/netbirdio/netbird/iface/bind" + "github.com/netbirdio/netbird/sharedsock" +) + +type tunKernelDevice struct { + name string + address WGAddress + wgPort int + key string + mtu int + ctx context.Context + ctxCancel context.CancelFunc + transportNet transport.Net + + link *wgLink + udpMuxConn net.PacketConn + udpMux *bind.UniversalUDPMuxDefault +} + +func newTunDevice(name string, address WGAddress, wgPort int, key string, mtu int, transportNet transport.Net) wgTunDevice { + ctx, cancel := context.WithCancel(context.Background()) + return &tunKernelDevice{ + ctx: ctx, + ctxCancel: cancel, + name: name, + address: address, + wgPort: wgPort, + key: key, + mtu: mtu, + transportNet: transportNet, + } +} + +func (t *tunKernelDevice) Create() (wgConfigurer, error) { + link := newWGLink(t.name) + + // check if interface exists + l, err := netlink.LinkByName(t.name) + if err != nil { + switch err.(type) { + case netlink.LinkNotFoundError: + break + default: + return nil, err + } + } + + // remove if interface exists + if l != nil { + err = netlink.LinkDel(link) + if err != nil { + return nil, err + } + } + + log.Debugf("adding device: %s", t.name) + err = netlink.LinkAdd(link) + if os.IsExist(err) { + log.Infof("interface %s already exists. Will reuse.", t.name) + } else if err != nil { + return nil, err + } + + t.link = link + + err = t.assignAddr() + if err != nil { + return nil, err + } + + // todo do a discovery + log.Debugf("setting MTU: %d interface: %s", t.mtu, t.name) + err = netlink.LinkSetMTU(link, t.mtu) + if err != nil { + log.Errorf("error setting MTU on interface: %s", t.name) + return nil, err + } + + configurer := newWGConfigurer(t.name) + err = configurer.configureInterface(t.key, t.wgPort) + if err != nil { + return nil, err + } + return configurer, nil +} + +func (t *tunKernelDevice) Up() (*bind.UniversalUDPMuxDefault, error) { + if t.udpMux != nil { + return t.udpMux, nil + } + + if t.link == nil { + return nil, fmt.Errorf("device is not ready yet") + } + + log.Debugf("bringing up interface: %s", t.name) + err := netlink.LinkSetUp(t.link) + if err != nil { + log.Errorf("error bringing up interface: %s", t.name) + return nil, err + } + + rawSock, err := sharedsock.Listen(t.wgPort, sharedsock.NewIncomingSTUNFilter()) + if err != nil { + return nil, err + } + bindParams := bind.UniversalUDPMuxParams{ + UDPConn: rawSock, + Net: t.transportNet, + } + mux := bind.NewUniversalUDPMuxDefault(bindParams) + go mux.ReadFromConn(t.ctx) + t.udpMuxConn = rawSock + t.udpMux = mux + + log.Debugf("device is ready to use: %s", t.name) + return t.udpMux, nil +} + +func (t *tunKernelDevice) UpdateAddr(address WGAddress) error { + t.address = address + return t.assignAddr() +} + +func (t *tunKernelDevice) Close() error { + if t.link == nil { + return nil + } + + t.ctxCancel() + + var closErr error + if err := t.link.Close(); err != nil { + log.Debugf("failed to close link: %s", err) + closErr = err + } + + if t.udpMux != nil { + if err := t.udpMux.Close(); err != nil { + log.Debugf("failed to close udp mux: %s", err) + closErr = err + } + + if err := t.udpMuxConn.Close(); err != nil { + log.Debugf("failed to close udp mux connection: %s", err) + closErr = err + } + } + + return closErr +} + +func (t *tunKernelDevice) WgAddress() WGAddress { + return t.address +} + +func (t *tunKernelDevice) DeviceName() string { + return t.name +} + +func (t *tunKernelDevice) Wrapper() *DeviceWrapper { + return nil +} + +// assignAddr Adds IP address to the tunnel interface +func (t *tunKernelDevice) assignAddr() error { + link := newWGLink(t.name) + + //delete existing addresses + list, err := netlink.AddrList(link, 0) + if err != nil { + return err + } + if len(list) > 0 { + for _, a := range list { + addr := a + err = netlink.AddrDel(link, &addr) + if err != nil { + return err + } + } + } + + log.Debugf("adding address %s to interface: %s", t.address.String(), t.name) + addr, _ := netlink.ParseAddr(t.address.String()) + err = netlink.AddrAdd(link, addr) + if os.IsExist(err) { + log.Infof("interface %s already has the address: %s", t.name, t.address.String()) + } else if err != nil { + return err + } + // On linux, the link must be brought up + err = netlink.LinkSetUp(link) + return err +} diff --git a/iface/tun_link_linux.go b/iface/tun_link_linux.go new file mode 100644 index 0000000000..ab28b7e387 --- /dev/null +++ b/iface/tun_link_linux.go @@ -0,0 +1,33 @@ +//go:build linux && !android + +package iface + +import "github.com/vishvananda/netlink" + +type wgLink struct { + attrs *netlink.LinkAttrs +} + +func newWGLink(name string) *wgLink { + attrs := netlink.NewLinkAttrs() + attrs.Name = name + + return &wgLink{ + attrs: &attrs, + } +} + +// Attrs returns the Wireguard's default attributes +func (l *wgLink) Attrs() *netlink.LinkAttrs { + return l.attrs +} + +// Type returns the interface type +func (l *wgLink) Type() string { + return "wireguard" +} + +// Close deletes the link interface +func (l *wgLink) Close() error { + return netlink.LinkDel(l) +} diff --git a/iface/tun_linux.go b/iface/tun_linux.go deleted file mode 100644 index 1a3537394a..0000000000 --- a/iface/tun_linux.go +++ /dev/null @@ -1,149 +0,0 @@ -//go:build linux && !android - -package iface - -import ( - "fmt" - "os" - - log "github.com/sirupsen/logrus" - "github.com/vishvananda/netlink" -) - -func (c *tunDevice) Create() error { - if WireGuardModuleIsLoaded() { - log.Infof("create tun interface with kernel WireGuard support: %s", c.DeviceName()) - return c.createWithKernel() - } - - if !tunModuleIsLoaded() { - return fmt.Errorf("couldn't check or load tun module") - } - log.Infof("create tun interface with userspace WireGuard support: %s", c.DeviceName()) - var err error - c.netInterface, err = c.createWithUserspace() - if err != nil { - return err - } - - return c.assignAddr() - -} - -// createWithKernel Creates a new WireGuard interface using kernel WireGuard module. -// Works for Linux and offers much better network performance -func (c *tunDevice) createWithKernel() error { - - link := newWGLink(c.name) - - // check if interface exists - l, err := netlink.LinkByName(c.name) - if err != nil { - switch err.(type) { - case netlink.LinkNotFoundError: - break - default: - return err - } - } - - // remove if interface exists - if l != nil { - err = netlink.LinkDel(link) - if err != nil { - return err - } - } - - log.Debugf("adding device: %s", c.name) - err = netlink.LinkAdd(link) - if os.IsExist(err) { - log.Infof("interface %s already exists. Will reuse.", c.name) - } else if err != nil { - return err - } - - c.netInterface = link - - err = c.assignAddr() - if err != nil { - return err - } - - // todo do a discovery - log.Debugf("setting MTU: %d interface: %s", c.mtu, c.name) - err = netlink.LinkSetMTU(link, c.mtu) - if err != nil { - log.Errorf("error setting MTU on interface: %s", c.name) - return err - } - - log.Debugf("bringing up interface: %s", c.name) - err = netlink.LinkSetUp(link) - if err != nil { - log.Errorf("error bringing up interface: %s", c.name) - return err - } - - return nil -} - -// assignAddr Adds IP address to the tunnel interface -func (c *tunDevice) assignAddr() error { - link := newWGLink(c.name) - - //delete existing addresses - list, err := netlink.AddrList(link, 0) - if err != nil { - return err - } - if len(list) > 0 { - for _, a := range list { - addr := a - err = netlink.AddrDel(link, &addr) - if err != nil { - return err - } - } - } - - log.Debugf("adding address %s to interface: %s", c.address.String(), c.name) - addr, _ := netlink.ParseAddr(c.address.String()) - err = netlink.AddrAdd(link, addr) - if os.IsExist(err) { - log.Infof("interface %s already has the address: %s", c.name, c.address.String()) - } else if err != nil { - return err - } - // On linux, the link must be brought up - err = netlink.LinkSetUp(link) - return err -} - -type wgLink struct { - attrs *netlink.LinkAttrs -} - -func newWGLink(name string) *wgLink { - attrs := netlink.NewLinkAttrs() - attrs.Name = name - - return &wgLink{ - attrs: &attrs, - } -} - -// Attrs returns the Wireguard's default attributes -func (l *wgLink) Attrs() *netlink.LinkAttrs { - return l.attrs -} - -// Type returns the interface type -func (l *wgLink) Type() string { - return "wireguard" -} - -// Close deletes the link interface -func (l *wgLink) Close() error { - return netlink.LinkDel(l) -} diff --git a/iface/tun_netstack.go b/iface/tun_netstack.go new file mode 100644 index 0000000000..e1d01ecc90 --- /dev/null +++ b/iface/tun_netstack.go @@ -0,0 +1,119 @@ +//go:build !android +// +build !android + +package iface + +import ( + "fmt" + + "github.com/pion/transport/v3" + log "github.com/sirupsen/logrus" + "golang.zx2c4.com/wireguard/device" + + "github.com/netbirdio/netbird/iface/bind" + "github.com/netbirdio/netbird/iface/netstack" +) + +type tunNetstackDevice struct { + name string + address WGAddress + port int + key string + mtu int + listenAddress string + iceBind *bind.ICEBind + + device *device.Device + wrapper *DeviceWrapper + nsTun *netstack.NetStackTun + udpMux *bind.UniversalUDPMuxDefault + configurer wgConfigurer +} + +func newTunNetstackDevice(name string, address WGAddress, wgPort int, key string, mtu int, transportNet transport.Net, listenAddress string) wgTunDevice { + return &tunNetstackDevice{ + name: name, + address: address, + port: wgPort, + key: key, + mtu: mtu, + listenAddress: listenAddress, + iceBind: bind.NewICEBind(transportNet), + } +} + +func (t *tunNetstackDevice) Create() (wgConfigurer, error) { + log.Info("create netstack tun interface") + t.nsTun = netstack.NewNetStackTun(t.listenAddress, t.address.IP.String(), t.mtu) + tunIface, err := t.nsTun.Create() + if err != nil { + return nil, err + } + t.wrapper = newDeviceWrapper(tunIface) + + t.device = device.NewDevice( + t.wrapper, + t.iceBind, + device.NewLogger(device.LogLevelSilent, "[netbird] "), + ) + + t.configurer = newWGUSPConfigurer(t.device, t.name) + err = t.configurer.configureInterface(t.key, t.port) + if err != nil { + _ = tunIface.Close() + return nil, err + } + + log.Debugf("device has been created: %s", t.name) + return t.configurer, nil +} + +func (t *tunNetstackDevice) Up() (*bind.UniversalUDPMuxDefault, error) { + if t.device == nil { + return nil, fmt.Errorf("device is not ready yet") + } + + err := t.device.Up() + if err != nil { + return nil, err + } + + udpMux, err := t.iceBind.GetICEMux() + if err != nil { + return nil, err + } + t.udpMux = udpMux + log.Debugf("netstack device is ready to use") + return udpMux, nil +} + +func (t *tunNetstackDevice) UpdateAddr(WGAddress) error { + return nil +} + +func (t *tunNetstackDevice) Close() error { + if t.configurer != nil { + t.configurer.close() + } + + if t.device != nil { + t.device.Close() + } + + if t.udpMux != nil { + return t.udpMux.Close() + } + return nil +} + +func (t *tunNetstackDevice) WgAddress() WGAddress { + return t.address +} + +func (t *tunNetstackDevice) DeviceName() string { + return t.name +} + +func (t *tunNetstackDevice) Wrapper() *DeviceWrapper { + return t.wrapper +} diff --git a/iface/tun_unix.go b/iface/tun_unix.go deleted file mode 100644 index bc2d8d0196..0000000000 --- a/iface/tun_unix.go +++ /dev/null @@ -1,145 +0,0 @@ -//go:build (linux || darwin) && !android && !ios - -package iface - -import ( - "net" - "os" - - "github.com/pion/transport/v3" - "golang.zx2c4.com/wireguard/ipc" - - "github.com/netbirdio/netbird/iface/bind" - - log "github.com/sirupsen/logrus" - "golang.zx2c4.com/wireguard/device" - "golang.zx2c4.com/wireguard/tun" -) - -type tunDevice struct { - name string - address WGAddress - mtu int - netInterface NetInterface - iceBind *bind.ICEBind - uapi net.Listener - wrapper *DeviceWrapper - close chan struct{} -} - -func newTunDevice(name string, address WGAddress, mtu int, transportNet transport.Net) *tunDevice { - return &tunDevice{ - name: name, - address: address, - mtu: mtu, - iceBind: bind.NewICEBind(transportNet), - close: make(chan struct{}), - } -} - -func (c *tunDevice) UpdateAddr(address WGAddress) error { - c.address = address - return c.assignAddr() -} - -func (c *tunDevice) WgAddress() WGAddress { - return c.address -} - -func (c *tunDevice) DeviceName() string { - return c.name -} - -func (c *tunDevice) Close() error { - - select { - case c.close <- struct{}{}: - default: - } - - var err1, err2, err3 error - if c.netInterface != nil { - err1 = c.netInterface.Close() - } - - if c.uapi != nil { - err2 = c.uapi.Close() - } - - sockPath := "/var/run/wireguard/" + c.name + ".sock" - if _, statErr := os.Stat(sockPath); statErr == nil { - statErr = os.Remove(sockPath) - if statErr != nil { - err3 = statErr - } - } - - if err1 != nil { - return err1 - } - - if err2 != nil { - return err2 - } - - return err3 -} - -// createWithUserspace Creates a new Wireguard interface, using wireguard-go userspace implementation -func (c *tunDevice) createWithUserspace() (NetInterface, error) { - tunIface, err := tun.CreateTUN(c.name, c.mtu) - if err != nil { - return nil, err - } - c.wrapper = newDeviceWrapper(tunIface) - - // We need to create a wireguard-go device and listen to configuration requests - tunDev := device.NewDevice( - c.wrapper, - c.iceBind, - device.NewLogger(device.LogLevelSilent, "[netbird] "), - ) - err = tunDev.Up() - if err != nil { - _ = tunIface.Close() - return nil, err - } - - c.uapi, err = c.getUAPI(c.name) - if err != nil { - _ = tunIface.Close() - return nil, err - } - - go func() { - for { - select { - case <-c.close: - log.Debugf("exit uapi.Accept()") - return - default: - } - uapiConn, uapiErr := c.uapi.Accept() - if uapiErr != nil { - log.Traceln("uapi Accept failed with error: ", uapiErr) - continue - } - go func() { - tunDev.IpcHandle(uapiConn) - log.Debugf("exit tunDevice.IpcHandle") - }() - } - }() - - log.Debugln("UAPI listener started") - return tunIface, nil -} - -// getUAPI returns a Listener -func (c *tunDevice) getUAPI(iface string) (net.Listener, error) { - tunSock, err := ipc.UAPIOpen(iface) - if err != nil { - return nil, err - } - return ipc.UAPIListen(iface, tunSock) -} diff --git a/iface/tun_usp_linux.go b/iface/tun_usp_linux.go new file mode 100644 index 0000000000..3ed518d522 --- /dev/null +++ b/iface/tun_usp_linux.go @@ -0,0 +1,157 @@ +//go:build linux && !android + +package iface + +import ( + "fmt" + "os" + + "github.com/pion/transport/v3" + log "github.com/sirupsen/logrus" + "github.com/vishvananda/netlink" + "golang.zx2c4.com/wireguard/device" + "golang.zx2c4.com/wireguard/tun" + + "github.com/netbirdio/netbird/iface/bind" +) + +type tunUSPDevice struct { + name string + address WGAddress + port int + key string + mtu int + iceBind *bind.ICEBind + + device *device.Device + wrapper *DeviceWrapper + udpMux *bind.UniversalUDPMuxDefault + configurer wgConfigurer +} + +func newTunUSPDevice(name string, address WGAddress, port int, key string, mtu int, transportNet transport.Net) wgTunDevice { + log.Infof("using userspace bind mode") + return &tunUSPDevice{ + name: name, + address: address, + port: port, + key: key, + mtu: mtu, + iceBind: bind.NewICEBind(transportNet), + } +} + +func (t *tunUSPDevice) Create() (wgConfigurer, error) { + log.Info("create tun interface") + tunIface, err := tun.CreateTUN(t.name, t.mtu) + if err != nil { + return nil, err + } + t.wrapper = newDeviceWrapper(tunIface) + + // We need to create a wireguard-go device and listen to configuration requests + t.device = device.NewDevice( + t.wrapper, + t.iceBind, + device.NewLogger(device.LogLevelSilent, "[netbird] "), + ) + + err = t.assignAddr() + if err != nil { + t.device.Close() + return nil, err + } + + t.configurer = newWGUSPConfigurer(t.device, t.name) + err = t.configurer.configureInterface(t.key, t.port) + if err != nil { + t.device.Close() + t.configurer.close() + return nil, err + } + return t.configurer, nil +} + +func (t *tunUSPDevice) Up() (*bind.UniversalUDPMuxDefault, error) { + if t.device == nil { + return nil, fmt.Errorf("device is not ready yet") + } + + err := t.device.Up() + if err != nil { + return nil, err + } + + udpMux, err := t.iceBind.GetICEMux() + if err != nil { + return nil, err + } + t.udpMux = udpMux + + log.Debugf("device is ready to use: %s", t.name) + return udpMux, nil +} + +func (t *tunUSPDevice) UpdateAddr(address WGAddress) error { + t.address = address + return t.assignAddr() +} + +func (t *tunUSPDevice) Close() error { + if t.configurer != nil { + t.configurer.close() + } + + if t.device != nil { + t.device.Close() + } + + if t.udpMux != nil { + return t.udpMux.Close() + } + return nil +} + +func (t *tunUSPDevice) WgAddress() WGAddress { + return t.address +} + +func (t *tunUSPDevice) DeviceName() string { + return t.name +} + +func (t *tunUSPDevice) Wrapper() *DeviceWrapper { + return t.wrapper +} + +// assignAddr Adds IP address to the tunnel interface +func (t *tunUSPDevice) assignAddr() error { + link := newWGLink(t.name) + + //delete existing addresses + list, err := netlink.AddrList(link, 0) + if err != nil { + return err + } + if len(list) > 0 { + for _, a := range list { + addr := a + err = netlink.AddrDel(link, &addr) + if err != nil { + return err + } + } + } + + log.Debugf("adding address %s to interface: %s", t.address.String(), t.name) + addr, _ := netlink.ParseAddr(t.address.String()) + err = netlink.AddrAdd(link, addr) + if os.IsExist(err) { + log.Infof("interface %s already has the address: %s", t.name, t.address.String()) + } else if err != nil { + return err + } + // On linux, the link must be brought up + err = netlink.LinkSetUp(link) + return err +} diff --git a/iface/tun_windows.go b/iface/tun_windows.go index a4ddf1d859..900e62fc3e 100644 --- a/iface/tun_windows.go +++ b/iface/tun_windows.go @@ -2,14 +2,12 @@ package iface import ( "fmt" - "net" "net/netip" "github.com/pion/transport/v3" log "github.com/sirupsen/logrus" "golang.org/x/sys/windows" "golang.zx2c4.com/wireguard/device" - "golang.zx2c4.com/wireguard/ipc" "golang.zx2c4.com/wireguard/tun" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" @@ -17,139 +15,131 @@ import ( ) type tunDevice struct { - name string - address WGAddress - netInterface NetInterface - iceBind *bind.ICEBind - mtu int - uapi net.Listener - wrapper *DeviceWrapper - close chan struct{} + name string + address WGAddress + port int + key string + mtu int + iceBind *bind.ICEBind + + device *device.Device + nativeTunDevice *tun.NativeTun + wrapper *DeviceWrapper + udpMux *bind.UniversalUDPMuxDefault + configurer wgConfigurer } -func newTunDevice(name string, address WGAddress, mtu int, transportNet transport.Net) *tunDevice { +func newTunDevice(name string, address WGAddress, port int, key string, mtu int, transportNet transport.Net) wgTunDevice { return &tunDevice{ name: name, address: address, + port: port, + key: key, mtu: mtu, iceBind: bind.NewICEBind(transportNet), - close: make(chan struct{}), } } -func (c *tunDevice) Create() error { - var err error - c.netInterface, err = c.createWithUserspace() - if err != nil { - return err - } - - return c.assignAddr() -} - -// createWithUserspace Creates a new Wireguard interface, using wireguard-go userspace implementation -func (c *tunDevice) createWithUserspace() (NetInterface, error) { - tunIface, err := tun.CreateTUN(c.name, c.mtu) +func (t *tunDevice) Create() (wgConfigurer, error) { + tunDevice, err := tun.CreateTUN(t.name, t.mtu) if err != nil { return nil, err } - c.wrapper = newDeviceWrapper(tunIface) + t.nativeTunDevice = tunDevice.(*tun.NativeTun) + t.wrapper = newDeviceWrapper(tunDevice) // We need to create a wireguard-go device and listen to configuration requests - tunDev := device.NewDevice(c.wrapper, c.iceBind, device.NewLogger(device.LogLevelSilent, "[netbird] ")) - err = tunDev.Up() - if err != nil { - _ = tunIface.Close() - return nil, err - } + t.device = device.NewDevice( + t.wrapper, + t.iceBind, + device.NewLogger(device.LogLevelSilent, "[netbird] "), + ) - luid := winipcfg.LUID(tunIface.(*tun.NativeTun).LUID()) + luid := winipcfg.LUID(t.nativeTunDevice.LUID()) nbiface, err := luid.IPInterface(windows.AF_INET) if err != nil { - _ = tunIface.Close() + t.device.Close() return nil, fmt.Errorf("got error when getting ip interface %s", err) } - nbiface.NLMTU = uint32(c.mtu) + nbiface.NLMTU = uint32(t.mtu) err = nbiface.Set() if err != nil { - _ = tunIface.Close() + t.device.Close() return nil, fmt.Errorf("got error when getting setting the interface mtu: %s", err) } - - c.uapi, err = c.getUAPI(c.name) + err = t.assignAddr() if err != nil { - _ = tunIface.Close() + t.device.Close() return nil, err } - go func() { - for { - select { - case <-c.close: - log.Debugf("exit uapi.Accept()") - return - default: - } - uapiConn, uapiErr := c.uapi.Accept() - if uapiErr != nil { - log.Traceln("uapi Accept failed with error: ", uapiErr) - continue - } - go func() { - tunDev.IpcHandle(uapiConn) - log.Debugf("exit tunDevice.IpcHandle") - }() - } - }() - - log.Debugln("UAPI listener started") - return tunIface, nil + t.configurer = newWGUSPConfigurer(t.device, t.name) + err = t.configurer.configureInterface(t.key, t.port) + if err != nil { + t.device.Close() + t.configurer.close() + return nil, err + } + return t.configurer, nil } -func (c *tunDevice) UpdateAddr(address WGAddress) error { - c.address = address - return c.assignAddr() -} +func (t *tunDevice) Up() (*bind.UniversalUDPMuxDefault, error) { + err := t.device.Up() + if err != nil { + return nil, err + } -func (c *tunDevice) WgAddress() WGAddress { - return c.address + udpMux, err := t.iceBind.GetICEMux() + if err != nil { + return nil, err + } + t.udpMux = udpMux + log.Debugf("device is ready to use: %s", t.name) + return udpMux, nil } -func (c *tunDevice) DeviceName() string { - return c.name +func (t *tunDevice) UpdateAddr(address WGAddress) error { + t.address = address + return t.assignAddr() } -func (c *tunDevice) Close() error { - select { - case c.close <- struct{}{}: - default: +func (t *tunDevice) Close() error { + if t.configurer != nil { + t.configurer.close() } - var err1, err2 error - if c.netInterface != nil { - err1 = c.netInterface.Close() + if t.device != nil { + t.device.Close() + t.device = nil } - if c.uapi != nil { - err2 = c.uapi.Close() - } + if t.udpMux != nil { + return t.udpMux.Close() - if err1 != nil { - return err1 } + return nil +} +func (t *tunDevice) WgAddress() WGAddress { + return t.address +} + +func (t *tunDevice) DeviceName() string { + return t.name +} - return err2 +func (t *tunDevice) Wrapper() *DeviceWrapper { + return t.wrapper } -func (c *tunDevice) getInterfaceGUIDString() (string, error) { - if c.netInterface == nil { +func (t *tunDevice) getInterfaceGUIDString() (string, error) { + if t.nativeTunDevice == nil { return "", fmt.Errorf("interface has not been initialized yet") } - windowsDevice := c.netInterface.(*tun.NativeTun) - luid := winipcfg.LUID(windowsDevice.LUID()) + + luid := winipcfg.LUID(t.nativeTunDevice.LUID()) guid, err := luid.GUID() if err != nil { return "", err @@ -158,14 +148,8 @@ func (c *tunDevice) getInterfaceGUIDString() (string, error) { } // assignAddr Adds IP address to the tunnel interface and network route based on the range provided -func (c *tunDevice) assignAddr() error { - tunDev := c.netInterface.(*tun.NativeTun) - luid := winipcfg.LUID(tunDev.LUID()) - log.Debugf("adding address %s to interface: %s", c.address.IP, c.name) - return luid.SetIPAddresses([]netip.Prefix{netip.MustParsePrefix(c.address.String())}) -} - -// getUAPI returns a Listener -func (c *tunDevice) getUAPI(iface string) (net.Listener, error) { - return ipc.UAPIListen(iface) +func (t *tunDevice) assignAddr() error { + luid := winipcfg.LUID(t.nativeTunDevice.LUID()) + log.Debugf("adding address %s to interface: %s", t.address.IP, t.name) + return luid.SetIPAddresses([]netip.Prefix{netip.MustParsePrefix(t.address.String())}) } diff --git a/iface/uapi.go b/iface/uapi.go new file mode 100644 index 0000000000..d7ff52e7b3 --- /dev/null +++ b/iface/uapi.go @@ -0,0 +1,26 @@ +//go:build !windows + +package iface + +import ( + "net" + + log "github.com/sirupsen/logrus" + "golang.zx2c4.com/wireguard/ipc" +) + +func openUAPI(deviceName string) (net.Listener, error) { + uapiSock, err := ipc.UAPIOpen(deviceName) + if err != nil { + log.Errorf("failed to open uapi socket: %v", err) + return nil, err + } + + listener, err := ipc.UAPIListen(deviceName, uapiSock) + if err != nil { + log.Errorf("failed to listen on uapi socket: %v", err) + return nil, err + } + + return listener, nil +} diff --git a/iface/uapi_windows.go b/iface/uapi_windows.go new file mode 100644 index 0000000000..e1f4663642 --- /dev/null +++ b/iface/uapi_windows.go @@ -0,0 +1,11 @@ +package iface + +import ( + "net" + + "golang.zx2c4.com/wireguard/ipc" +) + +func openUAPI(deviceName string) (net.Listener, error) { + return ipc.UAPIListen(deviceName) +} diff --git a/iface/wg_configurer.go b/iface/wg_configurer.go new file mode 100644 index 0000000000..b56d75084f --- /dev/null +++ b/iface/wg_configurer.go @@ -0,0 +1,17 @@ +package iface + +import ( + "net" + "time" + + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +type wgConfigurer interface { + configureInterface(privateKey string, port int) error + updatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error + removePeer(peerKey string) error + addAllowedIP(peerKey string, allowedIP string) error + removeAllowedIP(peerKey string, allowedIP string) error + close() +} diff --git a/iface/wg_configurer_nonmobile.go b/iface/wg_configurer_kernel.go similarity index 83% rename from iface/wg_configurer_nonmobile.go rename to iface/wg_configurer_kernel.go index c09dda9adf..3192f5a2b5 100644 --- a/iface/wg_configurer_nonmobile.go +++ b/iface/wg_configurer_kernel.go @@ -1,4 +1,4 @@ -//go:build !android && !ios +//go:build linux && !android package iface @@ -12,17 +12,18 @@ import ( "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) -type wGConfigurer struct { +type wgKernelConfigurer struct { deviceName string } -func newWGConfigurer(deviceName string) wGConfigurer { - return wGConfigurer{ +func newWGConfigurer(deviceName string) wgConfigurer { + wgc := &wgKernelConfigurer{ deviceName: deviceName, } + return wgc } -func (c *wGConfigurer) configureInterface(privateKey string, port int) error { +func (c *wgKernelConfigurer) configureInterface(privateKey string, port int) error { log.Debugf("adding Wireguard private key") key, err := wgtypes.ParseKey(privateKey) if err != nil { @@ -43,7 +44,7 @@ func (c *wGConfigurer) configureInterface(privateKey string, port int) error { return nil } -func (c *wGConfigurer) updatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error { +func (c *wgKernelConfigurer) updatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error { // parse allowed ips _, ipNet, err := net.ParseCIDR(allowedIps) if err != nil { @@ -73,7 +74,7 @@ func (c *wGConfigurer) updatePeer(peerKey string, allowedIps string, keepAlive t return nil } -func (c *wGConfigurer) removePeer(peerKey string) error { +func (c *wgKernelConfigurer) removePeer(peerKey string) error { peerKeyParsed, err := wgtypes.ParseKey(peerKey) if err != nil { return err @@ -94,7 +95,7 @@ func (c *wGConfigurer) removePeer(peerKey string) error { return nil } -func (c *wGConfigurer) addAllowedIP(peerKey string, allowedIP string) error { +func (c *wgKernelConfigurer) addAllowedIP(peerKey string, allowedIP string) error { _, ipNet, err := net.ParseCIDR(allowedIP) if err != nil { return err @@ -121,7 +122,7 @@ func (c *wGConfigurer) addAllowedIP(peerKey string, allowedIP string) error { return nil } -func (c *wGConfigurer) removeAllowedIP(peerKey string, allowedIP string) error { +func (c *wgKernelConfigurer) removeAllowedIP(peerKey string, allowedIP string) error { _, ipNet, err := net.ParseCIDR(allowedIP) if err != nil { return err @@ -163,7 +164,7 @@ func (c *wGConfigurer) removeAllowedIP(peerKey string, allowedIP string) error { return nil } -func (c *wGConfigurer) getPeer(ifaceName, peerPubKey string) (wgtypes.Peer, error) { +func (c *wgKernelConfigurer) getPeer(ifaceName, peerPubKey string) (wgtypes.Peer, error) { wg, err := wgctrl.New() if err != nil { return wgtypes.Peer{}, err @@ -187,7 +188,7 @@ func (c *wGConfigurer) getPeer(ifaceName, peerPubKey string) (wgtypes.Peer, erro return wgtypes.Peer{}, fmt.Errorf("peer not found") } -func (c *wGConfigurer) configure(config wgtypes.Config) error { +func (c *wgKernelConfigurer) configure(config wgtypes.Config) error { wg, err := wgctrl.New() if err != nil { return err @@ -203,3 +204,6 @@ func (c *wGConfigurer) configure(config wgtypes.Config) error { return wg.ConfigureDevice(c.deviceName, config) } + +func (c *wgKernelConfigurer) close() { +} diff --git a/iface/wg_configurer_mobile.go b/iface/wg_configurer_mobile.go deleted file mode 100644 index 7f6e5595da..0000000000 --- a/iface/wg_configurer_mobile.go +++ /dev/null @@ -1,165 +0,0 @@ -//go:build ios || android -// +build ios android - -package iface - -import ( - "encoding/hex" - "errors" - "fmt" - "net" - "strings" - "time" - - log "github.com/sirupsen/logrus" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" -) - -var ( - errFuncNotImplemented = errors.New("function not implemented") -) - -type wGConfigurer struct { - tunDevice *tunDevice -} - -func newWGConfigurer(tunDevice *tunDevice) wGConfigurer { - return wGConfigurer{ - tunDevice: tunDevice, - } -} - -func (c *wGConfigurer) configureInterface(privateKey string, port int) error { - log.Debugf("adding Wireguard private key") - key, err := wgtypes.ParseKey(privateKey) - if err != nil { - return err - } - fwmark := 0 - config := wgtypes.Config{ - PrivateKey: &key, - ReplacePeers: true, - FirewallMark: &fwmark, - ListenPort: &port, - } - - return c.tunDevice.Device().IpcSet(toWgUserspaceString(config)) -} - -func (c *wGConfigurer) updatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error { - // parse allowed ips - _, ipNet, err := net.ParseCIDR(allowedIps) - if err != nil { - return err - } - - peerKeyParsed, err := wgtypes.ParseKey(peerKey) - if err != nil { - return err - } - peer := wgtypes.PeerConfig{ - PublicKey: peerKeyParsed, - ReplaceAllowedIPs: true, - AllowedIPs: []net.IPNet{*ipNet}, - PersistentKeepaliveInterval: &keepAlive, - PresharedKey: preSharedKey, - Endpoint: endpoint, - } - - config := wgtypes.Config{ - Peers: []wgtypes.PeerConfig{peer}, - } - - return c.tunDevice.Device().IpcSet(toWgUserspaceString(config)) -} - -func (c *wGConfigurer) removePeer(peerKey string) error { - peerKeyParsed, err := wgtypes.ParseKey(peerKey) - if err != nil { - return err - } - - peer := wgtypes.PeerConfig{ - PublicKey: peerKeyParsed, - Remove: true, - } - - config := wgtypes.Config{ - Peers: []wgtypes.PeerConfig{peer}, - } - return c.tunDevice.Device().IpcSet(toWgUserspaceString(config)) -} - -func (c *wGConfigurer) addAllowedIP(peerKey string, allowedIP string) error { - _, ipNet, err := net.ParseCIDR(allowedIP) - if err != nil { - return err - } - - peerKeyParsed, err := wgtypes.ParseKey(peerKey) - if err != nil { - return err - } - peer := wgtypes.PeerConfig{ - PublicKey: peerKeyParsed, - UpdateOnly: true, - ReplaceAllowedIPs: false, - AllowedIPs: []net.IPNet{*ipNet}, - } - - config := wgtypes.Config{ - Peers: []wgtypes.PeerConfig{peer}, - } - - return c.tunDevice.Device().IpcSet(toWgUserspaceString(config)) -} - -func (c *wGConfigurer) removeAllowedIP(peerKey string, ip string) error { - ipc, err := c.tunDevice.Device().IpcGet() - if err != nil { - return err - } - - peerKeyParsed, err := wgtypes.ParseKey(peerKey) - hexKey := hex.EncodeToString(peerKeyParsed[:]) - - lines := strings.Split(ipc, "\n") - - output := "" - foundPeer := false - removedAllowedIP := false - for _, line := range lines { - line = strings.TrimSpace(line) - - // If we're within the details of the found peer and encounter another public key, - // this means we're starting another peer's details. So, reset the flag. - if strings.HasPrefix(line, "public_key=") && foundPeer { - foundPeer = false - } - - // Identify the peer with the specific public key - if line == fmt.Sprintf("public_key=%s", hexKey) { - foundPeer = true - } - - // If we're within the details of the found peer and find the specific allowed IP, skip this line - if foundPeer && line == "allowed_ip="+ip { - removedAllowedIP = true - continue - } - - // Append the line to the output string - if strings.HasPrefix(line, "private_key=") || strings.HasPrefix(line, "listen_port=") || - strings.HasPrefix(line, "public_key=") || strings.HasPrefix(line, "preshared_key=") || - strings.HasPrefix(line, "endpoint=") || strings.HasPrefix(line, "persistent_keepalive_interval=") || - strings.HasPrefix(line, "allowed_ip=") { - output += line + "\n" - } - } - - if !removedAllowedIP { - return fmt.Errorf("allowedIP not found") - } else { - return c.tunDevice.Device().IpcSet(output) - } -} diff --git a/iface/wg_configurer_usp.go b/iface/wg_configurer_usp.go new file mode 100644 index 0000000000..cf12b99004 --- /dev/null +++ b/iface/wg_configurer_usp.go @@ -0,0 +1,259 @@ +package iface + +import ( + "encoding/hex" + "fmt" + "net" + "os" + "runtime" + "strings" + "time" + + log "github.com/sirupsen/logrus" + "golang.zx2c4.com/wireguard/device" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +type wgUSPConfigurer struct { + device *device.Device + deviceName string + + uapiListener net.Listener +} + +func newWGUSPConfigurer(device *device.Device, deviceName string) wgConfigurer { + wgCfg := &wgUSPConfigurer{ + device: device, + deviceName: deviceName, + } + wgCfg.startUAPI() + return wgCfg +} + +func (c *wgUSPConfigurer) configureInterface(privateKey string, port int) error { + log.Debugf("adding Wireguard private key") + key, err := wgtypes.ParseKey(privateKey) + if err != nil { + return err + } + fwmark := 0 + config := wgtypes.Config{ + PrivateKey: &key, + ReplacePeers: true, + FirewallMark: &fwmark, + ListenPort: &port, + } + + return c.device.IpcSet(toWgUserspaceString(config)) +} + +func (c *wgUSPConfigurer) updatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error { + // parse allowed ips + _, ipNet, err := net.ParseCIDR(allowedIps) + if err != nil { + return err + } + + peerKeyParsed, err := wgtypes.ParseKey(peerKey) + if err != nil { + return err + } + peer := wgtypes.PeerConfig{ + PublicKey: peerKeyParsed, + ReplaceAllowedIPs: true, + AllowedIPs: []net.IPNet{*ipNet}, + PersistentKeepaliveInterval: &keepAlive, + PresharedKey: preSharedKey, + Endpoint: endpoint, + } + + config := wgtypes.Config{ + Peers: []wgtypes.PeerConfig{peer}, + } + + return c.device.IpcSet(toWgUserspaceString(config)) +} + +func (c *wgUSPConfigurer) removePeer(peerKey string) error { + peerKeyParsed, err := wgtypes.ParseKey(peerKey) + if err != nil { + return err + } + + peer := wgtypes.PeerConfig{ + PublicKey: peerKeyParsed, + Remove: true, + } + + config := wgtypes.Config{ + Peers: []wgtypes.PeerConfig{peer}, + } + return c.device.IpcSet(toWgUserspaceString(config)) +} + +func (c *wgUSPConfigurer) addAllowedIP(peerKey string, allowedIP string) error { + _, ipNet, err := net.ParseCIDR(allowedIP) + if err != nil { + return err + } + + peerKeyParsed, err := wgtypes.ParseKey(peerKey) + if err != nil { + return err + } + peer := wgtypes.PeerConfig{ + PublicKey: peerKeyParsed, + UpdateOnly: true, + ReplaceAllowedIPs: false, + AllowedIPs: []net.IPNet{*ipNet}, + } + + config := wgtypes.Config{ + Peers: []wgtypes.PeerConfig{peer}, + } + + return c.device.IpcSet(toWgUserspaceString(config)) +} + +func (c *wgUSPConfigurer) removeAllowedIP(peerKey string, ip string) error { + ipc, err := c.device.IpcGet() + if err != nil { + return err + } + + peerKeyParsed, err := wgtypes.ParseKey(peerKey) + if err != nil { + return err + } + hexKey := hex.EncodeToString(peerKeyParsed[:]) + + lines := strings.Split(ipc, "\n") + + output := "" + foundPeer := false + removedAllowedIP := false + for _, line := range lines { + line = strings.TrimSpace(line) + + // If we're within the details of the found peer and encounter another public key, + // this means we're starting another peer's details. So, reset the flag. + if strings.HasPrefix(line, "public_key=") && foundPeer { + foundPeer = false + } + + // Identify the peer with the specific public key + if line == fmt.Sprintf("public_key=%s", hexKey) { + foundPeer = true + } + + // If we're within the details of the found peer and find the specific allowed IP, skip this line + if foundPeer && line == "allowed_ip="+ip { + removedAllowedIP = true + continue + } + + // Append the line to the output string + if strings.HasPrefix(line, "private_key=") || strings.HasPrefix(line, "listen_port=") || + strings.HasPrefix(line, "public_key=") || strings.HasPrefix(line, "preshared_key=") || + strings.HasPrefix(line, "endpoint=") || strings.HasPrefix(line, "persistent_keepalive_interval=") || + strings.HasPrefix(line, "allowed_ip=") { + output += line + "\n" + } + } + + if !removedAllowedIP { + return fmt.Errorf("allowedIP not found") + } else { + return c.device.IpcSet(output) + } +} + +// startUAPI starts the UAPI listener for managing the WireGuard interface via external tool +func (t *wgUSPConfigurer) startUAPI() { + var err error + t.uapiListener, err = openUAPI(t.deviceName) + if err != nil { + log.Errorf("failed to open uapi listener: %v", err) + return + } + + go func(uapi net.Listener) { + for { + uapiConn, uapiErr := uapi.Accept() + if uapiErr != nil { + log.Tracef("%s", uapiErr) + return + } + go func() { + t.device.IpcHandle(uapiConn) + }() + } + }(t.uapiListener) +} + +func (t *wgUSPConfigurer) close() { + if t.uapiListener != nil { + err := t.uapiListener.Close() + if err != nil { + log.Errorf("failed to close uapi listener: %v", err) + } + } + + if runtime.GOOS == "linux" { + sockPath := "/var/run/wireguard/" + t.deviceName + ".sock" + if _, statErr := os.Stat(sockPath); statErr == nil { + _ = os.Remove(sockPath) + } + } +} + +func toWgUserspaceString(wgCfg wgtypes.Config) string { + var sb strings.Builder + if wgCfg.PrivateKey != nil { + hexKey := hex.EncodeToString(wgCfg.PrivateKey[:]) + sb.WriteString(fmt.Sprintf("private_key=%s\n", hexKey)) + } + + if wgCfg.ListenPort != nil { + sb.WriteString(fmt.Sprintf("listen_port=%d\n", *wgCfg.ListenPort)) + } + + if wgCfg.ReplacePeers { + sb.WriteString("replace_peers=true\n") + } + + if wgCfg.FirewallMark != nil { + sb.WriteString(fmt.Sprintf("fwmark=%d\n", *wgCfg.FirewallMark)) + } + + for _, p := range wgCfg.Peers { + hexKey := hex.EncodeToString(p.PublicKey[:]) + sb.WriteString(fmt.Sprintf("public_key=%s\n", hexKey)) + + if p.PresharedKey != nil { + preSharedHexKey := hex.EncodeToString(p.PresharedKey[:]) + sb.WriteString(fmt.Sprintf("preshared_key=%s\n", preSharedHexKey)) + } + + if p.Remove { + sb.WriteString("remove=true") + } + + if p.ReplaceAllowedIPs { + sb.WriteString("replace_allowed_ips=true\n") + } + + for _, aip := range p.AllowedIPs { + sb.WriteString(fmt.Sprintf("allowed_ip=%s\n", aip.String())) + } + + if p.Endpoint != nil { + sb.WriteString(fmt.Sprintf("endpoint=%s\n", p.Endpoint.String())) + } + + if p.PersistentKeepaliveInterval != nil { + sb.WriteString(fmt.Sprintf("persistent_keepalive_interval=%d\n", int(p.PersistentKeepaliveInterval.Seconds()))) + } + } + return sb.String() +} diff --git a/infrastructure_files/getting-started-with-zitadel.sh b/infrastructure_files/getting-started-with-zitadel.sh index 330a47a2d7..90f623cbaf 100644 --- a/infrastructure_files/getting-started-with-zitadel.sh +++ b/infrastructure_files/getting-started-with-zitadel.sh @@ -312,7 +312,7 @@ delete_auto_service_user() { init_zitadel() { echo -e "\nInitializing Zitadel with NetBird's applications\n" - INSTANCE_URL="$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT" + INSTANCE_URL="$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN" TOKEN_PATH=./machinekey/zitadel-admin-sa.token @@ -569,7 +569,7 @@ initEnvironment() { echo -e "\nStarting NetBird services\n" $DOCKER_COMPOSE_COMMAND up -d echo -e "\nDone!\n" - echo "You can access the NetBird dashboard at $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT" + echo "You can access the NetBird dashboard at $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN" echo "Login with the following credentials:" echo "Username: $ZITADEL_ADMIN_USERNAME" | tee .env echo "Password: $ZITADEL_ADMIN_PASSWORD" | tee -a .env @@ -709,14 +709,14 @@ renderManagementJson() { "IdpManagerConfig": { "ManagerType": "zitadel", "ClientConfig": { - "Issuer": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT", - "TokenEndpoint": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT/oauth/v2/token", + "Issuer": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN", + "TokenEndpoint": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth/v2/token", "ClientID": "$NETBIRD_IDP_MGMT_CLIENT_ID", "ClientSecret": "$NETBIRD_IDP_MGMT_CLIENT_SECRET", "GrantType": "client_credentials" }, "ExtraConfig": { - "ManagementEndpoint": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT/management/v1" + "ManagementEndpoint": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/management/v1" } }, "PKCEAuthorizationFlow": { @@ -734,12 +734,12 @@ EOF renderDashboardEnv() { cat <