mirror of
https://github.com/netbirdio/netbird.git
synced 2024-12-13 18:31:18 +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
|
||||
AdminURL string
|
||||
ConfigPath string
|
||||
StateFilePath string
|
||||
PreSharedKey *string
|
||||
ServerSSHAllowed *bool
|
||||
NATExternalIPs []string
|
||||
@ -105,10 +106,10 @@ type Config struct {
|
||||
|
||||
// DNSRouteInterval is the interval in which the DNS routes are updated
|
||||
DNSRouteInterval time.Duration
|
||||
//Path to a certificate used for mTLS authentication
|
||||
// Path to a certificate used for mTLS authentication
|
||||
ClientCertPath string
|
||||
|
||||
//Path to corresponding private key of ClientCertPath
|
||||
// Path to corresponding private key of ClientCertPath
|
||||
ClientCertKeyPath string
|
||||
|
||||
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
|
||||
func ReadConfig(configPath string) (*Config, error) {
|
||||
if configFileIsExists(configPath) {
|
||||
if fileExists(configPath) {
|
||||
err := util.EnforcePermission(configPath)
|
||||
if err != nil {
|
||||
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
|
||||
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")
|
||||
}
|
||||
|
||||
@ -158,7 +159,7 @@ func UpdateConfig(input ConfigInput) (*Config, error) {
|
||||
|
||||
// UpdateOrCreateConfig reads existing config or generates a new one
|
||||
func UpdateOrCreateConfig(input ConfigInput) (*Config, error) {
|
||||
if !configFileIsExists(input.ConfigPath) {
|
||||
if !fileExists(input.ConfigPath) {
|
||||
log.Infof("generating new config %s", input.ConfigPath)
|
||||
cfg, err := createNewConfig(input)
|
||||
if err != nil {
|
||||
@ -472,11 +473,19 @@ func isPreSharedKeyHidden(preSharedKey *string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func configFileIsExists(path string) bool {
|
||||
func fileExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
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.
|
||||
// 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.
|
||||
|
@ -91,6 +91,7 @@ func (c *ConnectClient) RunOniOS(
|
||||
fileDescriptor int32,
|
||||
networkChangeListener listener.NetworkChangeListener,
|
||||
dnsManager dns.IosDnsManager,
|
||||
stateFilePath string,
|
||||
) error {
|
||||
// Set GC percent to 5% to reduce memory usage as iOS only allows 50MB of memory for the extension.
|
||||
debug.SetGCPercent(5)
|
||||
@ -99,6 +100,7 @@ func (c *ConnectClient) RunOniOS(
|
||||
FileDescriptor: fileDescriptor,
|
||||
NetworkChangeListener: networkChangeListener,
|
||||
DnsManager: dnsManager,
|
||||
StateFilePath: stateFilePath,
|
||||
}
|
||||
return c.run(mobileDependency, nil, nil)
|
||||
}
|
||||
|
@ -243,6 +243,17 @@ func NewEngineWithProbes(
|
||||
probes: probes,
|
||||
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 != "" {
|
||||
engine.stateManager = statemanager.New(path)
|
||||
}
|
||||
|
@ -19,4 +19,5 @@ type MobileDependency struct {
|
||||
// iOS only
|
||||
DnsManager dns.IosDnsManager
|
||||
FileDescriptor int32
|
||||
StateFilePath string
|
||||
}
|
||||
|
@ -273,3 +273,88 @@ func TestRouteSelector_FilterSelected(t *testing.T) {
|
||||
"route2|192.168.0.0/16": {},
|
||||
}, 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
|
||||
type Client struct {
|
||||
cfgFile string
|
||||
stateFile string
|
||||
recorder *peer.Status
|
||||
ctxCancel context.CancelFunc
|
||||
ctxCancelLock *sync.Mutex
|
||||
@ -73,9 +74,10 @@ type Client struct {
|
||||
}
|
||||
|
||||
// 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{
|
||||
cfgFile: cfgFile,
|
||||
stateFile: stateFile,
|
||||
deviceName: deviceName,
|
||||
osName: osName,
|
||||
osVersion: osVersion,
|
||||
@ -92,6 +94,7 @@ func (c *Client) Run(fd int32, interfaceName string) error {
|
||||
log.Debugf("Tunnel uses interface: %s", interfaceName)
|
||||
cfg, err := internal.UpdateOrCreateConfig(internal.ConfigInput{
|
||||
ConfigPath: c.cfgFile,
|
||||
StateFilePath: c.stateFile,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@ -124,7 +127,7 @@ func (c *Client) Run(fd int32, interfaceName string) error {
|
||||
cfg.WgIface = interfaceName
|
||||
|
||||
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
|
||||
|
@ -10,9 +10,10 @@ type Preferences struct {
|
||||
}
|
||||
|
||||
// NewPreferences create new Preferences instance
|
||||
func NewPreferences(configPath string) *Preferences {
|
||||
func NewPreferences(configPath string, stateFilePath string) *Preferences {
|
||||
ci := internal.ConfigInput{
|
||||
ConfigPath: configPath,
|
||||
StateFilePath: stateFilePath,
|
||||
}
|
||||
return &Preferences{ci}
|
||||
}
|
||||
|
@ -9,7 +9,8 @@ import (
|
||||
|
||||
func TestPreferences_DefaultValues(t *testing.T) {
|
||||
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()
|
||||
if err != nil {
|
||||
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) {
|
||||
exampleString := "exampleString"
|
||||
cfgFile := filepath.Join(t.TempDir(), "netbird.json")
|
||||
p := NewPreferences(cfgFile)
|
||||
stateFile := filepath.Join(t.TempDir(), "state.json")
|
||||
p := NewPreferences(cfgFile, stateFile)
|
||||
|
||||
p.SetAdminURL(exampleString)
|
||||
resp, err := p.GetAdminURL()
|
||||
@ -79,7 +81,8 @@ func TestPreferences_Commit(t *testing.T) {
|
||||
exampleURL := "https://myurl.com:443"
|
||||
examplePresharedKey := "topsecret"
|
||||
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.SetManagementURL(exampleURL)
|
||||
@ -90,7 +93,7 @@ func TestPreferences_Commit(t *testing.T) {
|
||||
t.Fatalf("failed to save changes: %s", err)
|
||||
}
|
||||
|
||||
p = NewPreferences(cfgFile)
|
||||
p = NewPreferences(cfgFile, stateFile)
|
||||
resp, err := p.GetAdminURL()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read admin url: %s", err)
|
||||
|
Loading…
Reference in New Issue
Block a user