starting engine by passing file descriptor on engine start

This commit is contained in:
Pascal Fischer
2023-08-09 15:21:53 +02:00
parent a9b9b3fa0a
commit 7f7e10121d
20 changed files with 913 additions and 12 deletions

View File

@@ -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,

View File

@@ -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))

View File

@@ -14,4 +14,5 @@ type MobileDependency struct {
RouteListener routemanager.RouteListener
HostDNSAddresses []string
DnsReadyListener dns.ReadyListener
FileDescriptor int32
}

View File

@@ -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()
}

View File

@@ -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)
}

View File

@@ -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")
}
}

View File

@@ -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

222
client/ios/netbird/login.go Normal file
View File

@@ -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)
})
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -1,3 +1,6 @@
//go:build !ios
// +build !ios
package system
import (

30
client/system/info_ios.go Normal file
View File

@@ -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
}