mirror of
https://github.com/netbirdio/netbird.git
synced 2024-12-14 19:00:50 +01:00
[client] Add support for state manager on iOS (#2996)
This commit is contained in:
parent
ff330e644e
commit
e40a29ba17
@ -46,6 +46,7 @@ type ConfigInput struct {
|
|||||||
ManagementURL string
|
ManagementURL string
|
||||||
AdminURL string
|
AdminURL string
|
||||||
ConfigPath string
|
ConfigPath string
|
||||||
|
StateFilePath string
|
||||||
PreSharedKey *string
|
PreSharedKey *string
|
||||||
ServerSSHAllowed *bool
|
ServerSSHAllowed *bool
|
||||||
NATExternalIPs []string
|
NATExternalIPs []string
|
||||||
@ -105,10 +106,10 @@ type Config struct {
|
|||||||
|
|
||||||
// DNSRouteInterval is the interval in which the DNS routes are updated
|
// DNSRouteInterval is the interval in which the DNS routes are updated
|
||||||
DNSRouteInterval time.Duration
|
DNSRouteInterval time.Duration
|
||||||
//Path to a certificate used for mTLS authentication
|
// Path to a certificate used for mTLS authentication
|
||||||
ClientCertPath string
|
ClientCertPath string
|
||||||
|
|
||||||
//Path to corresponding private key of ClientCertPath
|
// Path to corresponding private key of ClientCertPath
|
||||||
ClientCertKeyPath string
|
ClientCertKeyPath string
|
||||||
|
|
||||||
ClientCertKeyPair *tls.Certificate `json:"-"`
|
ClientCertKeyPair *tls.Certificate `json:"-"`
|
||||||
@ -116,7 +117,7 @@ type Config struct {
|
|||||||
|
|
||||||
// ReadConfig read config file and return with Config. If it is not exists create a new with default values
|
// ReadConfig read config file and return with Config. If it is not exists create a new with default values
|
||||||
func ReadConfig(configPath string) (*Config, error) {
|
func ReadConfig(configPath string) (*Config, error) {
|
||||||
if configFileIsExists(configPath) {
|
if fileExists(configPath) {
|
||||||
err := util.EnforcePermission(configPath)
|
err := util.EnforcePermission(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to enforce permission on config dir: %v", err)
|
log.Errorf("failed to enforce permission on config dir: %v", err)
|
||||||
@ -149,7 +150,7 @@ func ReadConfig(configPath string) (*Config, error) {
|
|||||||
|
|
||||||
// UpdateConfig update existing configuration according to input configuration and return with the configuration
|
// UpdateConfig update existing configuration according to input configuration and return with the configuration
|
||||||
func UpdateConfig(input ConfigInput) (*Config, error) {
|
func UpdateConfig(input ConfigInput) (*Config, error) {
|
||||||
if !configFileIsExists(input.ConfigPath) {
|
if !fileExists(input.ConfigPath) {
|
||||||
return nil, status.Errorf(codes.NotFound, "config file doesn't exist")
|
return nil, status.Errorf(codes.NotFound, "config file doesn't exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +159,7 @@ func UpdateConfig(input ConfigInput) (*Config, error) {
|
|||||||
|
|
||||||
// UpdateOrCreateConfig reads existing config or generates a new one
|
// UpdateOrCreateConfig reads existing config or generates a new one
|
||||||
func UpdateOrCreateConfig(input ConfigInput) (*Config, error) {
|
func UpdateOrCreateConfig(input ConfigInput) (*Config, error) {
|
||||||
if !configFileIsExists(input.ConfigPath) {
|
if !fileExists(input.ConfigPath) {
|
||||||
log.Infof("generating new config %s", input.ConfigPath)
|
log.Infof("generating new config %s", input.ConfigPath)
|
||||||
cfg, err := createNewConfig(input)
|
cfg, err := createNewConfig(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -472,11 +473,19 @@ func isPreSharedKeyHidden(preSharedKey *string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func configFileIsExists(path string) bool {
|
func fileExists(path string) bool {
|
||||||
_, err := os.Stat(path)
|
_, err := os.Stat(path)
|
||||||
return !os.IsNotExist(err)
|
return !os.IsNotExist(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createFile(path string) error {
|
||||||
|
file, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateOldManagementURL checks whether client can switch to the new Management URL with port 443 and the management domain.
|
// 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.
|
// 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.
|
// The check is performed only for the NetBird's managed version.
|
||||||
|
@ -91,6 +91,7 @@ func (c *ConnectClient) RunOniOS(
|
|||||||
fileDescriptor int32,
|
fileDescriptor int32,
|
||||||
networkChangeListener listener.NetworkChangeListener,
|
networkChangeListener listener.NetworkChangeListener,
|
||||||
dnsManager dns.IosDnsManager,
|
dnsManager dns.IosDnsManager,
|
||||||
|
stateFilePath string,
|
||||||
) error {
|
) error {
|
||||||
// Set GC percent to 5% to reduce memory usage as iOS only allows 50MB of memory for the extension.
|
// Set GC percent to 5% to reduce memory usage as iOS only allows 50MB of memory for the extension.
|
||||||
debug.SetGCPercent(5)
|
debug.SetGCPercent(5)
|
||||||
@ -99,6 +100,7 @@ func (c *ConnectClient) RunOniOS(
|
|||||||
FileDescriptor: fileDescriptor,
|
FileDescriptor: fileDescriptor,
|
||||||
NetworkChangeListener: networkChangeListener,
|
NetworkChangeListener: networkChangeListener,
|
||||||
DnsManager: dnsManager,
|
DnsManager: dnsManager,
|
||||||
|
StateFilePath: stateFilePath,
|
||||||
}
|
}
|
||||||
return c.run(mobileDependency, nil, nil)
|
return c.run(mobileDependency, nil, nil)
|
||||||
}
|
}
|
||||||
|
@ -243,6 +243,17 @@ func NewEngineWithProbes(
|
|||||||
probes: probes,
|
probes: probes,
|
||||||
checks: checks,
|
checks: checks,
|
||||||
}
|
}
|
||||||
|
if runtime.GOOS == "ios" {
|
||||||
|
if !fileExists(mobileDep.StateFilePath) {
|
||||||
|
err := createFile(mobileDep.StateFilePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to create state file: %v", err)
|
||||||
|
// we are not exiting as we can run without the state manager
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.stateManager = statemanager.New(mobileDep.StateFilePath)
|
||||||
|
}
|
||||||
if path := statemanager.GetDefaultStatePath(); path != "" {
|
if path := statemanager.GetDefaultStatePath(); path != "" {
|
||||||
engine.stateManager = statemanager.New(path)
|
engine.stateManager = statemanager.New(path)
|
||||||
}
|
}
|
||||||
|
@ -19,4 +19,5 @@ type MobileDependency struct {
|
|||||||
// iOS only
|
// iOS only
|
||||||
DnsManager dns.IosDnsManager
|
DnsManager dns.IosDnsManager
|
||||||
FileDescriptor int32
|
FileDescriptor int32
|
||||||
|
StateFilePath string
|
||||||
}
|
}
|
||||||
|
@ -273,3 +273,88 @@ func TestRouteSelector_FilterSelected(t *testing.T) {
|
|||||||
"route2|192.168.0.0/16": {},
|
"route2|192.168.0.0/16": {},
|
||||||
}, filtered)
|
}, filtered)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRouteSelector_NewRoutesBehavior(t *testing.T) {
|
||||||
|
initialRoutes := []route.NetID{"route1", "route2", "route3"}
|
||||||
|
newRoutes := []route.NetID{"route1", "route2", "route3", "route4", "route5"}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
initialState func(rs *routeselector.RouteSelector) error // Setup initial state
|
||||||
|
wantNewSelected []route.NetID // Expected selected routes after new routes appear
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "New routes with initial selectAll state",
|
||||||
|
initialState: func(rs *routeselector.RouteSelector) error {
|
||||||
|
rs.SelectAllRoutes()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
// When selectAll is true, all routes including new ones should be selected
|
||||||
|
wantNewSelected: []route.NetID{"route1", "route2", "route3", "route4", "route5"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "New routes after specific selection",
|
||||||
|
initialState: func(rs *routeselector.RouteSelector) error {
|
||||||
|
return rs.SelectRoutes([]route.NetID{"route1", "route2"}, false, initialRoutes)
|
||||||
|
},
|
||||||
|
// When specific routes were selected, new routes should remain unselected
|
||||||
|
wantNewSelected: []route.NetID{"route1", "route2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "New routes after deselect all",
|
||||||
|
initialState: func(rs *routeselector.RouteSelector) error {
|
||||||
|
rs.DeselectAllRoutes()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
// After deselect all, new routes should remain unselected
|
||||||
|
wantNewSelected: []route.NetID{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "New routes after deselecting specific routes",
|
||||||
|
initialState: func(rs *routeselector.RouteSelector) error {
|
||||||
|
rs.SelectAllRoutes()
|
||||||
|
return rs.DeselectRoutes([]route.NetID{"route1"}, initialRoutes)
|
||||||
|
},
|
||||||
|
// After deselecting specific routes, new routes should remain unselected
|
||||||
|
wantNewSelected: []route.NetID{"route2", "route3"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "New routes after selecting with append",
|
||||||
|
initialState: func(rs *routeselector.RouteSelector) error {
|
||||||
|
return rs.SelectRoutes([]route.NetID{"route1"}, true, initialRoutes)
|
||||||
|
},
|
||||||
|
// When routes were appended, new routes should remain unselected
|
||||||
|
wantNewSelected: []route.NetID{"route1"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
rs := routeselector.NewRouteSelector()
|
||||||
|
|
||||||
|
// Setup initial state
|
||||||
|
err := tt.initialState(rs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify selection state with new routes
|
||||||
|
for _, id := range newRoutes {
|
||||||
|
assert.Equal(t, rs.IsSelected(id), slices.Contains(tt.wantNewSelected, id),
|
||||||
|
"Route %s selection state incorrect", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional verification using FilterSelected
|
||||||
|
routes := route.HAMap{
|
||||||
|
"route1|10.0.0.0/8": {},
|
||||||
|
"route2|192.168.0.0/16": {},
|
||||||
|
"route3|172.16.0.0/12": {},
|
||||||
|
"route4|10.10.0.0/16": {},
|
||||||
|
"route5|192.168.1.0/24": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered := rs.FilterSelected(routes)
|
||||||
|
expectedLen := len(tt.wantNewSelected)
|
||||||
|
assert.Equal(t, expectedLen, len(filtered),
|
||||||
|
"FilterSelected returned wrong number of routes, got %d want %d", len(filtered), expectedLen)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -59,6 +59,7 @@ func init() {
|
|||||||
// Client struct manage the life circle of background service
|
// Client struct manage the life circle of background service
|
||||||
type Client struct {
|
type Client struct {
|
||||||
cfgFile string
|
cfgFile string
|
||||||
|
stateFile string
|
||||||
recorder *peer.Status
|
recorder *peer.Status
|
||||||
ctxCancel context.CancelFunc
|
ctxCancel context.CancelFunc
|
||||||
ctxCancelLock *sync.Mutex
|
ctxCancelLock *sync.Mutex
|
||||||
@ -73,9 +74,10 @@ type Client struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewClient instantiate a new Client
|
// NewClient instantiate a new Client
|
||||||
func NewClient(cfgFile, deviceName string, osVersion string, osName string, networkChangeListener NetworkChangeListener, dnsManager DnsManager) *Client {
|
func NewClient(cfgFile, stateFile, deviceName string, osVersion string, osName string, networkChangeListener NetworkChangeListener, dnsManager DnsManager) *Client {
|
||||||
return &Client{
|
return &Client{
|
||||||
cfgFile: cfgFile,
|
cfgFile: cfgFile,
|
||||||
|
stateFile: stateFile,
|
||||||
deviceName: deviceName,
|
deviceName: deviceName,
|
||||||
osName: osName,
|
osName: osName,
|
||||||
osVersion: osVersion,
|
osVersion: osVersion,
|
||||||
@ -91,7 +93,8 @@ func (c *Client) Run(fd int32, interfaceName string) error {
|
|||||||
log.Infof("Starting NetBird client")
|
log.Infof("Starting NetBird client")
|
||||||
log.Debugf("Tunnel uses interface: %s", interfaceName)
|
log.Debugf("Tunnel uses interface: %s", interfaceName)
|
||||||
cfg, err := internal.UpdateOrCreateConfig(internal.ConfigInput{
|
cfg, err := internal.UpdateOrCreateConfig(internal.ConfigInput{
|
||||||
ConfigPath: c.cfgFile,
|
ConfigPath: c.cfgFile,
|
||||||
|
StateFilePath: c.stateFile,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -124,7 +127,7 @@ func (c *Client) Run(fd int32, interfaceName string) error {
|
|||||||
cfg.WgIface = interfaceName
|
cfg.WgIface = interfaceName
|
||||||
|
|
||||||
c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder)
|
c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder)
|
||||||
return c.connectClient.RunOniOS(fd, c.networkChangeListener, c.dnsManager)
|
return c.connectClient.RunOniOS(fd, c.networkChangeListener, c.dnsManager, c.stateFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop the internal client and free the resources
|
// Stop the internal client and free the resources
|
||||||
|
@ -10,9 +10,10 @@ type Preferences struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewPreferences create new Preferences instance
|
// NewPreferences create new Preferences instance
|
||||||
func NewPreferences(configPath string) *Preferences {
|
func NewPreferences(configPath string, stateFilePath string) *Preferences {
|
||||||
ci := internal.ConfigInput{
|
ci := internal.ConfigInput{
|
||||||
ConfigPath: configPath,
|
ConfigPath: configPath,
|
||||||
|
StateFilePath: stateFilePath,
|
||||||
}
|
}
|
||||||
return &Preferences{ci}
|
return &Preferences{ci}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,8 @@ import (
|
|||||||
|
|
||||||
func TestPreferences_DefaultValues(t *testing.T) {
|
func TestPreferences_DefaultValues(t *testing.T) {
|
||||||
cfgFile := filepath.Join(t.TempDir(), "netbird.json")
|
cfgFile := filepath.Join(t.TempDir(), "netbird.json")
|
||||||
p := NewPreferences(cfgFile)
|
stateFile := filepath.Join(t.TempDir(), "state.json")
|
||||||
|
p := NewPreferences(cfgFile, stateFile)
|
||||||
defaultVar, err := p.GetAdminURL()
|
defaultVar, err := p.GetAdminURL()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to read default value: %s", err)
|
t.Fatalf("failed to read default value: %s", err)
|
||||||
@ -42,7 +43,8 @@ func TestPreferences_DefaultValues(t *testing.T) {
|
|||||||
func TestPreferences_ReadUncommitedValues(t *testing.T) {
|
func TestPreferences_ReadUncommitedValues(t *testing.T) {
|
||||||
exampleString := "exampleString"
|
exampleString := "exampleString"
|
||||||
cfgFile := filepath.Join(t.TempDir(), "netbird.json")
|
cfgFile := filepath.Join(t.TempDir(), "netbird.json")
|
||||||
p := NewPreferences(cfgFile)
|
stateFile := filepath.Join(t.TempDir(), "state.json")
|
||||||
|
p := NewPreferences(cfgFile, stateFile)
|
||||||
|
|
||||||
p.SetAdminURL(exampleString)
|
p.SetAdminURL(exampleString)
|
||||||
resp, err := p.GetAdminURL()
|
resp, err := p.GetAdminURL()
|
||||||
@ -79,7 +81,8 @@ func TestPreferences_Commit(t *testing.T) {
|
|||||||
exampleURL := "https://myurl.com:443"
|
exampleURL := "https://myurl.com:443"
|
||||||
examplePresharedKey := "topsecret"
|
examplePresharedKey := "topsecret"
|
||||||
cfgFile := filepath.Join(t.TempDir(), "netbird.json")
|
cfgFile := filepath.Join(t.TempDir(), "netbird.json")
|
||||||
p := NewPreferences(cfgFile)
|
stateFile := filepath.Join(t.TempDir(), "state.json")
|
||||||
|
p := NewPreferences(cfgFile, stateFile)
|
||||||
|
|
||||||
p.SetAdminURL(exampleURL)
|
p.SetAdminURL(exampleURL)
|
||||||
p.SetManagementURL(exampleURL)
|
p.SetManagementURL(exampleURL)
|
||||||
@ -90,7 +93,7 @@ func TestPreferences_Commit(t *testing.T) {
|
|||||||
t.Fatalf("failed to save changes: %s", err)
|
t.Fatalf("failed to save changes: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
p = NewPreferences(cfgFile)
|
p = NewPreferences(cfgFile, stateFile)
|
||||||
resp, err := p.GetAdminURL()
|
resp, err := p.GetAdminURL()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to read admin url: %s", err)
|
t.Fatalf("failed to read admin url: %s", err)
|
||||||
|
Loading…
Reference in New Issue
Block a user