From 7f7e10121ddd6d5b9b97021ee9c922038580f421 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Wed, 9 Aug 2023 15:21:53 +0200 Subject: [PATCH] starting engine by passing file descriptor on engine start --- client/internal/connect.go | 13 ++ client/internal/engine.go | 13 +- client/internal/mobile_dependency.go | 1 + client/ios/netbird/client.go | 166 ++++++++++++++++++ client/ios/netbird/dns_list.go | 26 +++ client/ios/netbird/dns_list_test.go | 24 +++ client/ios/netbird/gomobile.go | 5 + client/ios/netbird/login.go | 222 +++++++++++++++++++++++++ client/ios/netbird/peer_notifier.go | 36 ++++ client/ios/netbird/preferences.go | 78 +++++++++ client/ios/netbird/preferences_test.go | 120 +++++++++++++ client/system/info_darwin.go | 3 + client/system/info_ios.go | 30 ++++ iface/iface_android.go | 10 +- iface/iface_ios.go | 51 ++++++ iface/iface_nonandroid.go | 12 +- iface/tun_android.go | 5 +- iface/tun_darwin.go | 3 + iface/tun_ios.go | 105 ++++++++++++ iface/tun_unix.go | 2 +- 20 files changed, 913 insertions(+), 12 deletions(-) create mode 100644 client/ios/netbird/client.go create mode 100644 client/ios/netbird/dns_list.go create mode 100644 client/ios/netbird/dns_list_test.go create mode 100644 client/ios/netbird/gomobile.go create mode 100644 client/ios/netbird/login.go create mode 100644 client/ios/netbird/peer_notifier.go create mode 100644 client/ios/netbird/preferences.go create mode 100644 client/ios/netbird/preferences_test.go create mode 100644 client/system/info_ios.go create mode 100644 iface/iface_ios.go create mode 100644 iface/tun_ios.go diff --git a/client/internal/connect.go b/client/internal/connect.go index 87aab0b54..3416d7aae 100644 --- a/client/internal/connect.go +++ b/client/internal/connect.go @@ -42,6 +42,19 @@ func RunClientMobile(ctx context.Context, config *Config, statusRecorder *peer.S return runClient(ctx, config, statusRecorder, mobileDependency) } +func RunClientiOS(ctx context.Context, config *Config, statusRecorder *peer.Status, fileDescriptor int32, iFaceDiscover stdnet.ExternalIFaceDiscover, routeListener routemanager.RouteListener, dnsAddresses []string, dnsReadyListener dns.ReadyListener) error { + // func RunClientiOS(ctx context.Context, config *Config, statusRecorder *peer.Status, iFaceDiscover stdnet.ExternalIFaceDiscover, routeListener routemanager.RouteListener, dnsAddresses []string, dnsReadyListener dns.ReadyListener) error { + mobileDependency := MobileDependency{ + TunAdapter: nil, + FileDescriptor: fileDescriptor, + IFaceDiscover: iFaceDiscover, + RouteListener: routeListener, + HostDNSAddresses: dnsAddresses, + DnsReadyListener: dnsReadyListener, + } + return runClient(ctx, config, statusRecorder, mobileDependency) +} + func runClient(ctx context.Context, config *Config, statusRecorder *peer.Status, mobileDependency MobileDependency) error { backOff := &backoff.ExponentialBackOff{ InitialInterval: time.Second, diff --git a/client/internal/engine.go b/client/internal/engine.go index 038f39e5c..8276aef56 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -217,13 +217,16 @@ func (e *Engine) Start() error { e.routeManager = routemanager.NewManager(e.ctx, e.config.WgPrivateKey.PublicKey().String(), e.wgInterface, e.statusRecorder, routes) e.routeManager.SetRouteChangeListener(e.mobileDep.RouteListener) - if runtime.GOOS != "android" { - err = e.wgInterface.Create() - } else { - err = e.wgInterface.CreateOnMobile(iface.MobileIFaceArguments{ + switch runtime.GOOS { + case "android": + err = e.wgInterface.CreateOnAndroid(iface.MobileIFaceArguments{ Routes: e.routeManager.InitialRouteRange(), Dns: e.dnsServer.DnsIP(), }) + case "ios": + err = e.wgInterface.CreateOniOS(e.mobileDep.FileDescriptor) + default: + err = e.wgInterface.Create() } if err != nil { log.Errorf("failed creating tunnel interface %s: [%s]", wgIFaceName, err.Error()) @@ -466,7 +469,7 @@ func (e *Engine) updateSSH(sshConf *mgmProto.SSHConfig) error { } // start SSH server if it wasn't running if isNil(e.sshServer) { - //nil sshServer means it has not yet been started + // nil sshServer means it has not yet been started var err error e.sshServer, err = e.sshServerFunc(e.config.SSHKey, fmt.Sprintf("%s:%d", e.wgInterface.Address().IP.String(), nbssh.DefaultSSHPort)) diff --git a/client/internal/mobile_dependency.go b/client/internal/mobile_dependency.go index fc8fa61ce..0b02a63ec 100644 --- a/client/internal/mobile_dependency.go +++ b/client/internal/mobile_dependency.go @@ -14,4 +14,5 @@ type MobileDependency struct { RouteListener routemanager.RouteListener HostDNSAddresses []string DnsReadyListener dns.ReadyListener + FileDescriptor int32 } diff --git a/client/ios/netbird/client.go b/client/ios/netbird/client.go new file mode 100644 index 000000000..46ac734df --- /dev/null +++ b/client/ios/netbird/client.go @@ -0,0 +1,166 @@ +package netbird + +import ( + "context" + "sync" + + log "github.com/sirupsen/logrus" + + "github.com/netbirdio/netbird/client/internal" + "github.com/netbirdio/netbird/client/internal/dns" + "github.com/netbirdio/netbird/client/internal/peer" + "github.com/netbirdio/netbird/client/internal/routemanager" + "github.com/netbirdio/netbird/client/internal/stdnet" + "github.com/netbirdio/netbird/client/system" + "github.com/netbirdio/netbird/formatter" + "github.com/netbirdio/netbird/iface" +) + +// ConnectionListener export internal Listener for mobile +type ConnectionListener interface { + peer.Listener +} + +// TunAdapter export internal TunAdapter for mobile +type TunAdapter interface { + iface.TunAdapter +} + +// IFaceDiscover export internal IFaceDiscover for mobile +type IFaceDiscover interface { + stdnet.ExternalIFaceDiscover +} + +// RouteListener export internal RouteListener for mobile +type RouteListener interface { + routemanager.RouteListener +} + +// DnsReadyListener export internal dns ReadyListener for mobile +type DnsReadyListener interface { + dns.ReadyListener +} + +// CustomLogger export internal CustomLogger for mobile +type CustomLogger interface { + Debug(message string) + Info(message string) + Error(message string) +} + +func init() { + formatter.SetLogcatFormatter(log.StandardLogger()) +} + +// Client struct manage the life circle of background service +type Client struct { + cfgFile string + iFaceDiscover IFaceDiscover + recorder *peer.Status + ctxCancel context.CancelFunc + ctxCancelLock *sync.Mutex + deviceName string + routeListener routemanager.RouteListener + logger CustomLogger + onHostDnsFn func([]string) +} + +// NewClient instantiate a new Client +func NewClient(cfgFile, deviceName string, iFaceDiscover IFaceDiscover, routeListener RouteListener, logger CustomLogger) *Client { + return &Client{ + cfgFile: cfgFile, + deviceName: deviceName, + iFaceDiscover: iFaceDiscover, + recorder: peer.NewRecorder(""), + ctxCancelLock: &sync.Mutex{}, + logger: logger, + routeListener: routeListener, + } +} + +// Run start the internal client. It is a blocker function +// func (c *Client) Run(fd int32, dns *DNSList, dnsReadyListener DnsReadyListener) error { +func (c *Client) Run(fd int32, dns *DNSList, dnsReadyListener DnsReadyListener) error { + c.logger.Info("Starting NetBird client") + cfg, err := internal.UpdateOrCreateConfig(internal.ConfigInput{ + ConfigPath: c.cfgFile, + }) + if err != nil { + return err + } + c.recorder.UpdateManagementAddress(cfg.ManagementURL.String()) + + var ctx context.Context + //nolint + ctxWithValues := context.WithValue(context.Background(), system.DeviceNameCtxKey, c.deviceName) + c.ctxCancelLock.Lock() + ctx, c.ctxCancel = context.WithCancel(ctxWithValues) + defer c.ctxCancel() + c.ctxCancelLock.Unlock() + + auth := NewAuthWithConfig(ctx, cfg) + // err = auth.login(urlOpener) + auth.loginWithSetupKeyAndSaveConfig("E9EEBA50-76A1-4C72-A939-C10655C1CC09", "iPhone") + if err != nil { + return err + } + + // todo do not throw error in case of cancelled context + ctx = internal.CtxInitState(ctx) + c.onHostDnsFn = func([]string) {} + return internal.RunClientiOS(ctx, cfg, c.recorder, fd, c.iFaceDiscover, c.routeListener, dns.items, dnsReadyListener) +} + +// Stop the internal client and free the resources +func (c *Client) Stop() { + c.ctxCancelLock.Lock() + defer c.ctxCancelLock.Unlock() + if c.ctxCancel == nil { + return + } + + c.ctxCancel() +} + +// ÏSetTraceLogLevel configure the logger to trace level +func (c *Client) SetTraceLogLevel() { + log.SetLevel(log.TraceLevel) +} + +// PeersList return with the list of the PeerInfos +func (c *Client) PeersList() *PeerInfoArray { + + fullStatus := c.recorder.GetFullStatus() + + peerInfos := make([]PeerInfo, len(fullStatus.Peers)) + for n, p := range fullStatus.Peers { + pi := PeerInfo{ + p.IP, + p.FQDN, + p.ConnStatus.String(), + } + peerInfos[n] = pi + } + return &PeerInfoArray{items: peerInfos} +} + +// OnUpdatedHostDNS update the DNS servers addresses for root zones +func (c *Client) OnUpdatedHostDNS(list *DNSList) error { + dnsServer, err := dns.GetServerDns() + if err != nil { + return err + } + + dnsServer.OnUpdatedHostDNSServer(list.items) + return nil +} + +// SetConnectionListener set the network connection listener +func (c *Client) SetConnectionListener(listener ConnectionListener) { + c.recorder.SetConnectionListener(listener) +} + +// RemoveConnectionListener remove connection listener +func (c *Client) RemoveConnectionListener() { + c.recorder.RemoveConnectionListener() +} diff --git a/client/ios/netbird/dns_list.go b/client/ios/netbird/dns_list.go new file mode 100644 index 000000000..49e3b2f71 --- /dev/null +++ b/client/ios/netbird/dns_list.go @@ -0,0 +1,26 @@ +package netbird + +import "fmt" + +// DNSList is a wrapper of []string +type DNSList struct { + items []string +} + +// Add new DNS address to the collection +func (array *DNSList) Add(s string) { + array.items = append(array.items, s) +} + +// Get return an element of the collection +func (array *DNSList) Get(i int) (string, error) { + if i >= len(array.items) || i < 0 { + return "", fmt.Errorf("out of range") + } + return array.items[i], nil +} + +// Size return with the size of the collection +func (array *DNSList) Size() int { + return len(array.items) +} diff --git a/client/ios/netbird/dns_list_test.go b/client/ios/netbird/dns_list_test.go new file mode 100644 index 000000000..d88373dd8 --- /dev/null +++ b/client/ios/netbird/dns_list_test.go @@ -0,0 +1,24 @@ +package netbird + +import "testing" + +func TestDNSList_Get(t *testing.T) { + l := DNSList{ + items: make([]string, 1), + } + + _, err := l.Get(0) + if err != nil { + t.Errorf("invalid error: %s", err) + } + + _, err = l.Get(-1) + if err == nil { + t.Errorf("expected error but got nil") + } + + _, err = l.Get(1) + if err == nil { + t.Errorf("expected error but got nil") + } +} diff --git a/client/ios/netbird/gomobile.go b/client/ios/netbird/gomobile.go new file mode 100644 index 000000000..76b4f7e35 --- /dev/null +++ b/client/ios/netbird/gomobile.go @@ -0,0 +1,5 @@ +package netbird + +import _ "golang.org/x/mobile/bind" + +// to keep our CI/CD that checks go.mod and go.sum files happy, we need to import the package above diff --git a/client/ios/netbird/login.go b/client/ios/netbird/login.go new file mode 100644 index 000000000..da893ba5e --- /dev/null +++ b/client/ios/netbird/login.go @@ -0,0 +1,222 @@ +package netbird + +import ( + "context" + "fmt" + "time" + + "github.com/cenkalti/backoff/v4" + log "github.com/sirupsen/logrus" + "google.golang.org/grpc/codes" + gstatus "google.golang.org/grpc/status" + + "github.com/netbirdio/netbird/client/cmd" + "github.com/netbirdio/netbird/client/internal" + "github.com/netbirdio/netbird/client/internal/auth" + "github.com/netbirdio/netbird/client/system" +) + +// SSOListener is async listener for mobile framework +type SSOListener interface { + OnSuccess(bool) + OnError(error) +} + +// ErrListener is async listener for mobile framework +type ErrListener interface { + OnSuccess() + OnError(error) +} + +// URLOpener it is a callback interface. The Open function will be triggered if +// the backend want to show an url for the user +type URLOpener interface { + Open(string) +} + +// Auth can register or login new client +type Auth struct { + ctx context.Context + config *internal.Config + cfgPath string +} + +// NewAuth instantiate Auth struct and validate the management URL +func NewAuth(cfgPath string, mgmURL string) (*Auth, error) { + inputCfg := internal.ConfigInput{ + ManagementURL: mgmURL, + } + + cfg, err := internal.CreateInMemoryConfig(inputCfg) + if err != nil { + return nil, err + } + + return &Auth{ + ctx: context.Background(), + config: cfg, + cfgPath: cfgPath, + }, nil +} + +// NewAuthWithConfig instantiate Auth based on existing config +func NewAuthWithConfig(ctx context.Context, config *internal.Config) *Auth { + return &Auth{ + ctx: ctx, + config: config, + } +} + +// SaveConfigIfSSOSupported test the connectivity with the management server by retrieving the server device flow info. +// If it returns a flow info than save the configuration and return true. If it gets a codes.NotFound, it means that SSO +// is not supported and returns false without saving the configuration. For other errors return false. +func (a *Auth) SaveConfigIfSSOSupported(listener SSOListener) { + go func() { + sso, err := a.saveConfigIfSSOSupported() + if err != nil { + listener.OnError(err) + } else { + listener.OnSuccess(sso) + } + }() +} + +func (a *Auth) saveConfigIfSSOSupported() (bool, error) { + supportsSSO := true + err := a.withBackOff(a.ctx, func() (err error) { + _, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL) + if s, ok := gstatus.FromError(err); ok && s.Code() == codes.NotFound { + _, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL) + if s, ok := gstatus.FromError(err); ok && s.Code() == codes.NotFound { + supportsSSO = false + err = nil + } + + return err + } + + return err + }) + + if !supportsSSO { + return false, nil + } + + if err != nil { + return false, fmt.Errorf("backoff cycle failed: %v", err) + } + + err = internal.WriteOutConfig(a.cfgPath, a.config) + return true, err +} + +// LoginWithSetupKeyAndSaveConfig test the connectivity with the management server with the setup key. +func (a *Auth) LoginWithSetupKeyAndSaveConfig(resultListener ErrListener, setupKey string, deviceName string) { + go func() { + err := a.loginWithSetupKeyAndSaveConfig(setupKey, deviceName) + if err != nil { + resultListener.OnError(err) + } else { + resultListener.OnSuccess() + } + }() +} + +func (a *Auth) loginWithSetupKeyAndSaveConfig(setupKey string, deviceName string) error { + //nolint + ctxWithValues := context.WithValue(a.ctx, system.DeviceNameCtxKey, deviceName) + + err := a.withBackOff(a.ctx, func() error { + backoffErr := internal.Login(ctxWithValues, a.config, setupKey, "") + if s, ok := gstatus.FromError(backoffErr); ok && (s.Code() == codes.PermissionDenied) { + // we got an answer from management, exit backoff earlier + return backoff.Permanent(backoffErr) + } + return backoffErr + }) + if err != nil { + return fmt.Errorf("backoff cycle failed: %v", err) + } + + return internal.WriteOutConfig(a.cfgPath, a.config) +} + +// Login try register the client on the server +func (a *Auth) Login(resultListener ErrListener, urlOpener URLOpener) { + go func() { + err := a.login(urlOpener) + if err != nil { + resultListener.OnError(err) + } else { + resultListener.OnSuccess() + } + }() +} + +func (a *Auth) login(urlOpener URLOpener) error { + var needsLogin bool + + // check if we need to generate JWT token + err := a.withBackOff(a.ctx, func() (err error) { + needsLogin, err = internal.IsLoginRequired(a.ctx, a.config.PrivateKey, a.config.ManagementURL, a.config.SSHKey) + return + }) + if err != nil { + return fmt.Errorf("backoff cycle failed: %v", err) + } + + jwtToken := "" + if needsLogin { + tokenInfo, err := a.foregroundGetTokenInfo(urlOpener) + if err != nil { + return fmt.Errorf("interactive sso login failed: %v", err) + } + jwtToken = tokenInfo.GetTokenToUse() + } + + err = a.withBackOff(a.ctx, func() error { + err := internal.Login(a.ctx, a.config, "", jwtToken) + if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.InvalidArgument || s.Code() == codes.PermissionDenied) { + return nil + } + return err + }) + if err != nil { + return fmt.Errorf("backoff cycle failed: %v", err) + } + + return nil +} + +func (a *Auth) foregroundGetTokenInfo(urlOpener URLOpener) (*auth.TokenInfo, error) { + oAuthFlow, err := auth.NewOAuthFlow(a.ctx, a.config) + if err != nil { + return nil, err + } + + flowInfo, err := oAuthFlow.RequestAuthInfo(context.TODO()) + if err != nil { + return nil, fmt.Errorf("getting a request OAuth flow info failed: %v", err) + } + + go urlOpener.Open(flowInfo.VerificationURIComplete) + + waitTimeout := time.Duration(flowInfo.ExpiresIn) + waitCTX, cancel := context.WithTimeout(a.ctx, waitTimeout*time.Second) + defer cancel() + tokenInfo, err := oAuthFlow.WaitToken(waitCTX, flowInfo) + if err != nil { + return nil, fmt.Errorf("waiting for browser login failed: %v", err) + } + + return &tokenInfo, nil +} + +func (a *Auth) withBackOff(ctx context.Context, bf func() error) error { + return backoff.RetryNotify( + bf, + backoff.WithContext(cmd.CLIBackOffSettings, ctx), + func(err error, duration time.Duration) { + log.Warnf("retrying Login to the Management service in %v due to error %v", duration, err) + }) +} diff --git a/client/ios/netbird/peer_notifier.go b/client/ios/netbird/peer_notifier.go new file mode 100644 index 000000000..a23af2a29 --- /dev/null +++ b/client/ios/netbird/peer_notifier.go @@ -0,0 +1,36 @@ +package netbird + +// PeerInfo describe information about the peers. It designed for the UI usage +type PeerInfo struct { + IP string + FQDN string + ConnStatus string // Todo replace to enum +} + +// PeerInfoCollection made for Java layer to get non default types as collection +type PeerInfoCollection interface { + Add(s string) PeerInfoCollection + Get(i int) string + Size() int +} + +// PeerInfoArray is the implementation of the PeerInfoCollection +type PeerInfoArray struct { + items []PeerInfo +} + +// Add new PeerInfo to the collection +func (array PeerInfoArray) Add(s PeerInfo) PeerInfoArray { + array.items = append(array.items, s) + return array +} + +// Get return an element of the collection +func (array PeerInfoArray) Get(i int) *PeerInfo { + return &array.items[i] +} + +// Size return with the size of the collection +func (array PeerInfoArray) Size() int { + return len(array.items) +} diff --git a/client/ios/netbird/preferences.go b/client/ios/netbird/preferences.go new file mode 100644 index 000000000..d565029e7 --- /dev/null +++ b/client/ios/netbird/preferences.go @@ -0,0 +1,78 @@ +package netbird + +import ( + "github.com/netbirdio/netbird/client/internal" +) + +// Preferences export a subset of the internal config for gomobile +type Preferences struct { + configInput internal.ConfigInput +} + +// NewPreferences create new Preferences instance +func NewPreferences(configPath string) *Preferences { + ci := internal.ConfigInput{ + ConfigPath: configPath, + } + return &Preferences{ci} +} + +// GetManagementURL read url from config file +func (p *Preferences) GetManagementURL() (string, error) { + if p.configInput.ManagementURL != "" { + return p.configInput.ManagementURL, nil + } + + cfg, err := internal.ReadConfig(p.configInput.ConfigPath) + if err != nil { + return "", err + } + return cfg.ManagementURL.String(), err +} + +// SetManagementURL store the given url and wait for commit +func (p *Preferences) SetManagementURL(url string) { + p.configInput.ManagementURL = url +} + +// GetAdminURL read url from config file +func (p *Preferences) GetAdminURL() (string, error) { + if p.configInput.AdminURL != "" { + return p.configInput.AdminURL, nil + } + + cfg, err := internal.ReadConfig(p.configInput.ConfigPath) + if err != nil { + return "", err + } + return cfg.AdminURL.String(), err +} + +// SetAdminURL store the given url and wait for commit +func (p *Preferences) SetAdminURL(url string) { + p.configInput.AdminURL = url +} + +// GetPreSharedKey read preshared key from config file +func (p *Preferences) GetPreSharedKey() (string, error) { + if p.configInput.PreSharedKey != nil { + return *p.configInput.PreSharedKey, nil + } + + cfg, err := internal.ReadConfig(p.configInput.ConfigPath) + if err != nil { + return "", err + } + return cfg.PreSharedKey, err +} + +// SetPreSharedKey store the given key and wait for commit +func (p *Preferences) SetPreSharedKey(key string) { + p.configInput.PreSharedKey = &key +} + +// Commit write out the changes into config file +func (p *Preferences) Commit() error { + _, err := internal.UpdateOrCreateConfig(p.configInput) + return err +} diff --git a/client/ios/netbird/preferences_test.go b/client/ios/netbird/preferences_test.go new file mode 100644 index 000000000..ee7e1c029 --- /dev/null +++ b/client/ios/netbird/preferences_test.go @@ -0,0 +1,120 @@ +package netbird + +import ( + "path/filepath" + "testing" + + "github.com/netbirdio/netbird/client/internal" +) + +func TestPreferences_DefaultValues(t *testing.T) { + cfgFile := filepath.Join(t.TempDir(), "netbird.json") + p := NewPreferences(cfgFile) + defaultVar, err := p.GetAdminURL() + if err != nil { + t.Fatalf("failed to read default value: %s", err) + } + + if defaultVar != internal.DefaultAdminURL { + t.Errorf("invalid default admin url: %s", defaultVar) + } + + defaultVar, err = p.GetManagementURL() + if err != nil { + t.Fatalf("failed to read default management URL: %s", err) + } + + if defaultVar != internal.DefaultManagementURL { + t.Errorf("invalid default management url: %s", defaultVar) + } + + var preSharedKey string + preSharedKey, err = p.GetPreSharedKey() + if err != nil { + t.Fatalf("failed to read default preshared key: %s", err) + } + + if preSharedKey != "" { + t.Errorf("invalid preshared key: %s", preSharedKey) + } +} + +func TestPreferences_ReadUncommitedValues(t *testing.T) { + exampleString := "exampleString" + cfgFile := filepath.Join(t.TempDir(), "netbird.json") + p := NewPreferences(cfgFile) + + p.SetAdminURL(exampleString) + resp, err := p.GetAdminURL() + if err != nil { + t.Fatalf("failed to read admin url: %s", err) + } + + if resp != exampleString { + t.Errorf("unexpected admin url: %s", resp) + } + + p.SetManagementURL(exampleString) + resp, err = p.GetManagementURL() + if err != nil { + t.Fatalf("failed to read managmenet url: %s", err) + } + + if resp != exampleString { + t.Errorf("unexpected managemenet url: %s", resp) + } + + p.SetPreSharedKey(exampleString) + resp, err = p.GetPreSharedKey() + if err != nil { + t.Fatalf("failed to read preshared key: %s", err) + } + + if resp != exampleString { + t.Errorf("unexpected preshared key: %s", resp) + } +} + +func TestPreferences_Commit(t *testing.T) { + exampleURL := "https://myurl.com:443" + examplePresharedKey := "topsecret" + cfgFile := filepath.Join(t.TempDir(), "netbird.json") + p := NewPreferences(cfgFile) + + p.SetAdminURL(exampleURL) + p.SetManagementURL(exampleURL) + p.SetPreSharedKey(examplePresharedKey) + + err := p.Commit() + if err != nil { + t.Fatalf("failed to save changes: %s", err) + } + + p = NewPreferences(cfgFile) + resp, err := p.GetAdminURL() + if err != nil { + t.Fatalf("failed to read admin url: %s", err) + } + + if resp != exampleURL { + t.Errorf("unexpected admin url: %s", resp) + } + + resp, err = p.GetManagementURL() + if err != nil { + t.Fatalf("failed to read managmenet url: %s", err) + } + + if resp != exampleURL { + t.Errorf("unexpected managemenet url: %s", resp) + } + + resp, err = p.GetPreSharedKey() + if err != nil { + t.Fatalf("failed to read preshared key: %s", err) + } + + if resp != examplePresharedKey { + t.Errorf("unexpected preshared key: %s", resp) + } +} diff --git a/client/system/info_darwin.go b/client/system/info_darwin.go index e153539bb..71fe03f09 100644 --- a/client/system/info_darwin.go +++ b/client/system/info_darwin.go @@ -1,3 +1,6 @@ +//go:build !ios +// +build !ios + package system import ( diff --git a/client/system/info_ios.go b/client/system/info_ios.go new file mode 100644 index 000000000..967c69a83 --- /dev/null +++ b/client/system/info_ios.go @@ -0,0 +1,30 @@ +//go:build ios +// +build ios + +package system + +import ( + "context" + "os" + "runtime" + + "github.com/netbirdio/netbird/version" +) + +// GetInfo retrieves and parses the system information +func GetInfo(ctx context.Context) *Info { + + // Convert fixed-size byte arrays to Go strings + sysName := "iOS" + machine := "machine" + release := "release" + swversion := "swversion" + + gio := &Info{Kernel: sysName, OSVersion: swversion, Core: release, Platform: machine, OS: sysName, GoOS: runtime.GOOS, CPUs: runtime.NumCPU()} + systemHostname, _ := os.Hostname() + gio.Hostname = extractDeviceName(ctx, systemHostname) + gio.WiretrusteeVersion = version.NetbirdVersion() + gio.UIVersion = extractUserAgent(ctx) + + return gio +} diff --git a/iface/iface_android.go b/iface/iface_android.go index 208eff7a8..6afa2d580 100644 --- a/iface/iface_android.go +++ b/iface/iface_android.go @@ -28,14 +28,20 @@ func NewWGIFace(ifaceName string, address string, mtu int, tunAdapter TunAdapter return wgIFace, nil } -// CreateOnMobile creates a new Wireguard interface, sets a given IP and brings it up. +// CreateOnAndroid creates a new Wireguard interface, sets a given IP and brings it up. // Will reuse an existing one. -func (w *WGIface) CreateOnMobile(mIFaceArgs MobileIFaceArguments) error { +func (w *WGIface) CreateOnAndroid(mIFaceArgs MobileIFaceArguments) error { w.mu.Lock() defer w.mu.Unlock() return w.tun.Create(mIFaceArgs) } +// CreateOniOS creates a new Wireguard interface, sets a given IP and brings it up. +// Will reuse an existing one. +func (w *WGIface) CreateOniOS(tunFd int32) error { + return fmt.Errorf("this function has not implemented on mobile") +} + // Create this function make sense on mobile only func (w *WGIface) Create() error { return fmt.Errorf("this function has not implemented on mobile") diff --git a/iface/iface_ios.go b/iface/iface_ios.go new file mode 100644 index 000000000..62432f7ed --- /dev/null +++ b/iface/iface_ios.go @@ -0,0 +1,51 @@ +//go:build ios +// +build ios + +package iface + +import ( + "fmt" + "sync" + + "github.com/pion/transport/v2" +) + +// 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 + } + + tun := newTunDevice(wgAddress, mtu, tunAdapter, transportNet) + wgIFace.tun = tun + + wgIFace.configurer = newWGConfigurer(tun.name) + + wgIFace.userspaceBind = !WireGuardModuleIsLoaded() + + return wgIFace, nil +} + +// CreateOniOS creates a new Wireguard interface, sets a given IP and brings it up. +// Will reuse an existing one. +func (w *WGIface) CreateOniOS(tunFd int32) error { + w.mu.Lock() + defer w.mu.Unlock() + return w.tun.Create(tunFd) +} + +// CreateOnAndroid creates a new Wireguard interface, sets a given IP and brings it up. +// Will reuse an existing one. +func (w *WGIface) CreateOnAndroid(mIFaceArgs MobileIFaceArguments) error { + return fmt.Errorf("this function has not implemented on mobile") +} + +// Create this function make sense on mobile only +func (w *WGIface) Create() error { + return fmt.Errorf("this function has not implemented on mobile") +} diff --git a/iface/iface_nonandroid.go b/iface/iface_nonandroid.go index da4ef13fd..80a9ab769 100644 --- a/iface/iface_nonandroid.go +++ b/iface/iface_nonandroid.go @@ -1,4 +1,5 @@ -//go:build !android +//go:build !android && !ios +// +build !android,!ios package iface @@ -27,8 +28,13 @@ func NewWGIFace(iFaceName string, address string, mtu int, tunAdapter TunAdapter return wgIFace, nil } -// CreateOnMobile this function make sense on mobile only -func (w *WGIface) CreateOnMobile(mIFaceArgs MobileIFaceArguments) error { +// CreateOnAndroid this function make sense on mobile only +func (w *WGIface) CreateOnAndroid(mIFaceArgs MobileIFaceArguments) error { + return fmt.Errorf("this function has not implemented on non mobile") +} + +// CreateOniOS this function make sense on mobile only +func (w *WGIface) CreateOniOS(tunFd int32) error { return fmt.Errorf("this function has not implemented on non mobile") } diff --git a/iface/tun_android.go b/iface/tun_android.go index d45f05282..6f13296bd 100644 --- a/iface/tun_android.go +++ b/iface/tun_android.go @@ -1,3 +1,6 @@ +//go:build android +// +build android + package iface import ( @@ -55,7 +58,7 @@ func (t *tunDevice) Create(mIFaceArgs MobileIFaceArguments) error { 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() + // t.device.DisableSomeRoamingForBrokenMobileSemantics() err = t.device.Up() if err != nil { diff --git a/iface/tun_darwin.go b/iface/tun_darwin.go index 4cf3712bd..7735479b6 100644 --- a/iface/tun_darwin.go +++ b/iface/tun_darwin.go @@ -1,3 +1,6 @@ +//go:build !ios +// +build !ios + package iface import ( diff --git a/iface/tun_ios.go b/iface/tun_ios.go new file mode 100644 index 000000000..eb39bf787 --- /dev/null +++ b/iface/tun_ios.go @@ -0,0 +1,105 @@ +//go:build ios +// +build ios + +package iface + +import ( + "os" + "strings" + + "github.com/pion/transport/v2" + log "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" + "golang.zx2c4.com/wireguard/device" + "golang.zx2c4.com/wireguard/tun" + + "github.com/netbirdio/netbird/iface/bind" +) + +type tunDevice struct { + address WGAddress + mtu int + tunAdapter TunAdapter + iceBind *bind.ICEBind + + fd int + name string + device *device.Device + wrapper *DeviceWrapper +} + +func newTunDevice(address WGAddress, mtu int, tunAdapter TunAdapter, transportNet transport.Net) *tunDevice { + return &tunDevice{ + address: address, + mtu: mtu, + tunAdapter: tunAdapter, + iceBind: bind.NewICEBind(transportNet), + } +} + +func (t *tunDevice) Create(tunFd int32) error { + log.Info("create tun interface") + + dupTunFd, err := unix.Dup(int(tunFd)) + if err != nil { + log.Errorf("Unable to dup tun fd: %v", err) + return 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 + } + tun, 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 + } + + t.wrapper = newDeviceWrapper(tun) + 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() + if err != nil { + t.device.Close() + return err + } + log.Debugf("device is ready to use: %s", t.name) + return nil +} + +func (t *tunDevice) Device() *device.Device { + return t.device +} + +func (t *tunDevice) DeviceName() string { + return t.name +} + +func (t *tunDevice) WgAddress() WGAddress { + return t.address +} + +func (t *tunDevice) UpdateAddr(addr WGAddress) error { + // todo implement + return nil +} + +func (t *tunDevice) Close() (err error) { + if t.device != nil { + t.device.Close() + } + + return +} + +func (t *tunDevice) routesToString(routes []string) string { + return strings.Join(routes, ";") +} diff --git a/iface/tun_unix.go b/iface/tun_unix.go index f923362a4..627814fc7 100644 --- a/iface/tun_unix.go +++ b/iface/tun_unix.go @@ -1,4 +1,4 @@ -//go:build (linux || darwin) && !android +//go:build (linux || darwin) && !android && !ios package iface