[client] Add support for state manager on iOS (#2996)

This commit is contained in:
Pascal Fischer
2024-12-06 16:51:42 +01:00
committed by GitHub
parent ff330e644e
commit e40a29ba17
8 changed files with 130 additions and 15 deletions
+15 -6
View File
@@ -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.
+2
View File
@@ -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)
}
+11
View File
@@ -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)
}
+1
View File
@@ -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)
})
}
}