From 23d4587ef5c61b9ad9bb79fc883e836fc7e94d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Papp?= Date: Thu, 30 Jul 2026 14:20:54 +0200 Subject: [PATCH] [client] Drop the initial GetNetworkMap fetch on Android startup Android startup opened a throwaway Sync stream to management before creating the TUN device, only to learn the initial routes, DNS config and the DNS feature flag. Server side this computed a full network map and broadcast a false connect/disconnect pair to every peer in the account on every Android start; client side it put a blocking network round trip on the critical startup path and failed the whole engine start when management was unreachable. None of its outputs are needed upfront anymore: the TUN is created empty and the first sync triggers a rebuild that pulls the fresh route and search domain state, the permanent DNS server starts with an empty config that the first sync populates, and the fake IP manager is created lazily when the DNS feature flag turns on. Remove readInitialSettings and its plumbing: the InitialRoutes and DNSFeatureFlag manager config fields, the android construction-time route setup, the initial-route bookkeeping in the notifiers and the now-unused GetNetworkMap client method. --- client/internal/engine.go | 51 ++-------------- client/internal/routemanager/manager.go | 60 +------------------ client/internal/routemanager/mock.go | 5 -- .../routemanager/notifier/notifier_android.go | 27 --------- .../routemanager/notifier/notifier_ios.go | 4 -- .../routemanager/notifier/notifier_other.go | 8 --- shared/management/client/client.go | 1 - shared/management/client/grpc.go | 43 ------------- shared/management/client/mock.go | 5 -- 9 files changed, 5 insertions(+), 199 deletions(-) diff --git a/client/internal/engine.go b/client/internal/engine.go index 617892e43..f4f47992f 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -572,12 +572,7 @@ func (e *Engine) Start(netbirdConfig *mgmProto.NetbirdConfig, mgmtURL *url.URL) } e.stateManager.Start() - initialRoutes, dnsConfig, dnsFeatureFlag, err := e.readInitialSettings() - if err != nil { - return fmt.Errorf("read initial settings: %w", err) - } - - dnsServer, err := e.newDnsServer(dnsConfig) + dnsServer, err := e.newDnsServer() if err != nil { return fmt.Errorf("create dns server: %w", err) } @@ -595,10 +590,8 @@ func (e *Engine) Start(netbirdConfig *mgmProto.NetbirdConfig, mgmtURL *url.URL) WGInterface: e.wgInterface, StatusRecorder: e.statusRecorder, RelayManager: e.relayManager, - InitialRoutes: initialRoutes, StateManager: e.stateManager, DNSServer: dnsServer, - DNSFeatureFlag: dnsFeatureFlag, PeerStore: e.peerStore, DisableClientRoutes: e.config.DisableClientRoutes, DisableServerRoutes: e.config.DisableServerRoutes, @@ -2102,42 +2095,6 @@ func (e *Engine) close() { } } -func (e *Engine) readInitialSettings() ([]*route.Route, *nbdns.Config, bool, error) { - if runtime.GOOS != "android" { - // nolint:nilnil - return nil, nil, false, nil - } - - info := system.GetInfo(e.ctx) - info.SetFlags( - e.config.RosenpassEnabled, - e.config.RosenpassPermissive, - &e.config.ServerSSHAllowed, - e.config.DisableClientRoutes, - e.config.DisableServerRoutes, - e.config.DisableDNS, - e.config.DisableFirewall, - e.config.BlockLANAccess, - e.config.BlockInbound, - e.config.DisableIPv6, - e.config.SyncMessageVersion, - e.config.EnableSSHRoot, - e.config.EnableSSHSFTP, - e.config.EnableSSHLocalPortForwarding, - e.config.EnableSSHRemotePortForwarding, - e.config.DisableSSHAuth, - ) - - netMap, err := e.mgmClient.GetNetworkMap(info) - if err != nil { - return nil, nil, false, err - } - routes := toRoutes(netMap.GetRoutes()) - dnsCfg := toDNSConfig(netMap.GetDNSConfig(), e.wgInterface.Address()) - dnsFeatureFlag := toDNSFeatureFlag(netMap) - return routes, &dnsCfg, dnsFeatureFlag, nil -} - func (e *Engine) newWgIface() (*iface.WGIface, error) { transportNet, err := e.newStdNet() if err != nil { @@ -2172,7 +2129,7 @@ func (e *Engine) newWgIface() (*iface.WGIface, error) { func (e *Engine) wgInterfaceCreate() (err error) { switch runtime.GOOS { case "android": - err = e.wgInterface.CreateOnAndroid(e.routeManager.InitialRouteRange(), e.dnsServer.DnsIP().String(), e.dnsServer.SearchDomains()) + err = e.wgInterface.CreateOnAndroid(e.routeManager.CurrentRouteRange(), e.dnsServer.DnsIP().String(), e.dnsServer.SearchDomains()) case "ios": e.mobileDep.NetworkChangeListener.SetInterfaceIP(e.config.WgAddr.String()) if e.config.WgAddr.HasIPv6() { @@ -2185,7 +2142,7 @@ func (e *Engine) wgInterfaceCreate() (err error) { return err } -func (e *Engine) newDnsServer(dnsConfig *nbdns.Config) (dns.Server, error) { +func (e *Engine) newDnsServer() (dns.Server, error) { // due to tests where we are using a mocked version of the DNS server if e.dnsServer != nil { return e.dnsServer, nil @@ -2197,7 +2154,7 @@ func (e *Engine) newDnsServer(dnsConfig *nbdns.Config) (dns.Server, error) { e.ctx, e.wgInterface, e.mobileDep.HostDNSAddresses, - *dnsConfig, + nbdns.Config{}, e.mobileDep.NetworkChangeListener, e.statusRecorder, e.config.DisableDNS, diff --git a/client/internal/routemanager/manager.go b/client/internal/routemanager/manager.go index eb48d97cf..0ccfa83ac 100644 --- a/client/internal/routemanager/manager.go +++ b/client/internal/routemanager/manager.go @@ -8,7 +8,6 @@ import ( "net/netip" "net/url" "runtime" - "slices" "sort" "strings" "sync" @@ -16,7 +15,6 @@ import ( "syscall" "time" - "github.com/google/uuid" "github.com/hashicorp/go-multierror" log "github.com/sirupsen/logrus" "golang.org/x/exp/maps" @@ -63,7 +61,6 @@ type Manager interface { GetActiveClientRoutes() route.HAMap GetClientRoutesWithNetID() map[route.NetID][]*route.Route SetRouteChangeListener(listener listener.NetworkChangeListener) - InitialRouteRange() []string CurrentRouteRange() []string SetFirewall(firewall.Manager) error SetDNSForwarderPort(port uint16) @@ -78,10 +75,8 @@ type ManagerConfig struct { WGInterface iface.WGIface StatusRecorder *peer.Status RelayManager *relayClient.Manager - InitialRoutes []*route.Route StateManager *statemanager.Manager DNSServer dns.Server - DNSFeatureFlag bool PeerStore *peerstore.Store DisableClientRoutes bool DisableServerRoutes bool @@ -151,50 +146,12 @@ func NewManager(config ManagerConfig) *DefaultManager { useNoop := netstack.IsEnabled() || config.DisableClientRoutes dm.setupRefCounters(useNoop) - // don't proceed with client routes if it is disabled - if config.DisableClientRoutes { - return dm - } - - if runtime.GOOS == "android" { - dm.setupAndroidRoutes(config) - } return dm } -func (m *DefaultManager) setupAndroidRoutes(config ManagerConfig) { - cr := m.initialClientRoutes(config.InitialRoutes) - routesForComparison := slices.Clone(cr) - - if config.DNSFeatureFlag { - cr = append(cr, m.enableFakeIPRoutes()...) - } - - m.notifier.SetInitialClientRoutes(cr, routesForComparison) -} - -func (m *DefaultManager) enableFakeIPRoutes() []*route.Route { +func (m *DefaultManager) enableFakeIPRoutes() { m.fakeIPManager = fakeip.NewManager() - - v4ID := uuid.NewString() - fakeIPRoute := &route.Route{ - ID: route.ID(v4ID), - Network: m.fakeIPManager.GetFakeIPBlock(), - NetID: route.NetID(v4ID), - Peer: m.pubKey, - NetworkType: route.IPv4Network, - } - v6ID := uuid.NewString() - fakeIPv6Route := &route.Route{ - ID: route.ID(v6ID), - Network: m.fakeIPManager.GetFakeIPv6Block(), - NetID: route.NetID(v6ID), - Peer: m.pubKey, - NetworkType: route.IPv6Network, - } - fakeRoutes := []*route.Route{fakeIPRoute, fakeIPv6Route} m.notifier.NotifyRouteChange() - return fakeRoutes } func (m *DefaultManager) setupRefCounters(useNoop bool) { @@ -510,11 +467,6 @@ func (m *DefaultManager) SetRouteChangeListener(listener listener.NetworkChangeL m.notifier.SetListener(listener) } -// InitialRouteRange return the list of initial routes. It used by mobile systems -func (m *DefaultManager) InitialRouteRange() []string { - return m.notifier.GetInitialRouteRanges() -} - // CurrentRouteRange returns the current TUN route list. It is used by mobile systems func (m *DefaultManager) CurrentRouteRange() []string { m.mux.Lock() @@ -738,16 +690,6 @@ func (m *DefaultManager) ClassifyRoutes(newRoutes []*route.Route) (map[route.ID] return newServerRoutesMap, newClientRoutesIDMap } -func (m *DefaultManager) initialClientRoutes(initialRoutes []*route.Route) []*route.Route { - _, crMap := m.ClassifyRoutes(initialRoutes) - rs := make([]*route.Route, 0, len(crMap)) - for _, routes := range crMap { - rs = append(rs, routes...) - } - - return rs -} - func isRouteSupported(route *route.Route) bool { if netstack.IsEnabled() || !nbnet.CustomRoutingDisabled() || route.IsDynamic() { return true diff --git a/client/internal/routemanager/mock.go b/client/internal/routemanager/mock.go index 28f97fc0a..2a8398b95 100644 --- a/client/internal/routemanager/mock.go +++ b/client/internal/routemanager/mock.go @@ -30,11 +30,6 @@ func (m *MockManager) Init() error { return nil } -// InitialRouteRange mock implementation of InitialRouteRange from Manager interface -func (m *MockManager) InitialRouteRange() []string { - return nil -} - // CurrentRouteRange mock implementation of CurrentRouteRange from Manager interface func (m *MockManager) CurrentRouteRange() []string { return nil diff --git a/client/internal/routemanager/notifier/notifier_android.go b/client/internal/routemanager/notifier/notifier_android.go index c9db0a4a0..5fa329310 100644 --- a/client/internal/routemanager/notifier/notifier_android.go +++ b/client/internal/routemanager/notifier/notifier_android.go @@ -15,7 +15,6 @@ import ( type Notifier struct { mu sync.Mutex - initialRoutes []*route.Route // currentRoutes is the last announced route set. It exists only to // suppress noise: without it every network map sync would trigger the // Java side, even when the routes did not change. The actual TUN route @@ -35,14 +34,6 @@ func (n *Notifier) SetListener(listener listener.NetworkChangeListener) { n.listener = listener } -// SetInitialClientRoutes stores the initial route sets for TUN configuration. -func (n *Notifier) SetInitialClientRoutes(initialRoutes []*route.Route, routesForComparison []*route.Route) { - n.mu.Lock() - defer n.mu.Unlock() - n.initialRoutes = filterStatic(initialRoutes) - n.currentRoutes = filterStatic(routesForComparison) -} - func (n *Notifier) NotifyRouteChange() { n.mu.Lock() defer n.mu.Unlock() @@ -81,28 +72,10 @@ func (n *Notifier) notifyLocked() { n.listener.OnNetworkChanged("") } -func (n *Notifier) GetInitialRouteRanges() []string { - n.mu.Lock() - defer n.mu.Unlock() - initialStrings := routesToStrings(n.initialRoutes) - sort.Strings(initialStrings) - return initialStrings -} - func (n *Notifier) Close() { // unused } -func filterStatic(routes []*route.Route) []*route.Route { - out := make([]*route.Route, 0, len(routes)) - for _, r := range routes { - if !r.IsDynamic() { - out = append(out, r) - } - } - return out -} - func routesToStrings(routes []*route.Route) []string { nets := make([]string, 0, len(routes)) for _, r := range routes { diff --git a/client/internal/routemanager/notifier/notifier_ios.go b/client/internal/routemanager/notifier/notifier_ios.go index 8d41dce64..d663dd471 100644 --- a/client/internal/routemanager/notifier/notifier_ios.go +++ b/client/internal/routemanager/notifier/notifier_ios.go @@ -29,10 +29,6 @@ func (n *Notifier) SetListener(listener listener.NetworkChangeListener) { n.listener = listener } -func (n *Notifier) SetInitialClientRoutes([]*route.Route, []*route.Route) { - // iOS doesn't care about initial routes -} - func (n *Notifier) NotifyRouteChange() { // Not used on iOS } diff --git a/client/internal/routemanager/notifier/notifier_other.go b/client/internal/routemanager/notifier/notifier_other.go index 5730eb65a..fe48e07b3 100644 --- a/client/internal/routemanager/notifier/notifier_other.go +++ b/client/internal/routemanager/notifier/notifier_other.go @@ -19,10 +19,6 @@ func (n *Notifier) SetListener(listener listener.NetworkChangeListener) { // Not used on non-mobile platforms } -func (n *Notifier) SetInitialClientRoutes([]*route.Route, []*route.Route) { - // Not used on non-mobile platforms -} - func (n *Notifier) NotifyRouteChange() { // Not used on non-mobile platforms } @@ -35,10 +31,6 @@ func (n *Notifier) OnNewPrefixes(prefixes []netip.Prefix) { // Not used on non-mobile platforms } -func (n *Notifier) GetInitialRouteRanges() []string { - return []string{} -} - func (n *Notifier) Close() { // unused } diff --git a/shared/management/client/client.go b/shared/management/client/client.go index 8205e3a4f..c48e1ed3e 100644 --- a/shared/management/client/client.go +++ b/shared/management/client/client.go @@ -22,7 +22,6 @@ type Client interface { ExtendAuthSession(sysInfo *system.Info, jwtToken string) (*proto.ExtendAuthSessionResponse, error) GetDeviceAuthorizationFlow() (*proto.DeviceAuthorizationFlow, error) GetPKCEAuthorizationFlow() (*proto.PKCEAuthorizationFlow, error) - GetNetworkMap(sysInfo *system.Info) (*proto.NetworkMap, error) GetServerURL() string // IsHealthy returns the current connection status without blocking. // Used by the engine to monitor connectivity in the background. diff --git a/shared/management/client/grpc.go b/shared/management/client/grpc.go index bd2d0da1f..81f25900a 100644 --- a/shared/management/client/grpc.go +++ b/shared/management/client/grpc.go @@ -436,49 +436,6 @@ func (c *GrpcClient) handleSyncStream(ctx context.Context, serverPubKey wgtypes. return nil } -// GetNetworkMap return with the network map -func (c *GrpcClient) GetNetworkMap(sysInfo *system.Info) (*proto.NetworkMap, error) { - serverPubKey, err := c.getServerPublicKey() - if err != nil { - log.Debugf("failed getting Management Service public key: %s", err) - return nil, err - } - - ctx, cancelStream := context.WithCancel(c.ctx) - defer cancelStream() - stream, err := c.connectToSyncStream(ctx, *serverPubKey, sysInfo) - if err != nil { - log.Debugf("failed to open Management Service stream: %s", err) - return nil, err - } - defer func() { - _ = stream.CloseSend() - }() - - update, err := stream.Recv() - if err == io.EOF { - log.Debugf("Management stream has been closed by server: %s", err) - return nil, err - } - if err != nil { - log.Debugf("disconnected from Management Service sync stream: %v", err) - return nil, err - } - - decryptedResp := &proto.SyncResponse{} - err = encryption.DecryptMessage(*serverPubKey, c.key, update.Body, decryptedResp) - if err != nil { - log.Errorf("failed decrypting update message from Management Service: %s", err) - return nil, err - } - - if decryptedResp.GetNetworkMap() == nil { - return nil, fmt.Errorf("invalid msg, required network map") - } - - return decryptedResp.GetNetworkMap(), nil -} - func (c *GrpcClient) connectToSyncStream(ctx context.Context, serverPubKey wgtypes.Key, sysInfo *system.Info) (proto.ManagementService_SyncClient, error) { req := &proto.SyncRequest{Meta: infoToMetaData(sysInfo)} diff --git a/shared/management/client/mock.go b/shared/management/client/mock.go index ba156a225..e57e314da 100644 --- a/shared/management/client/mock.go +++ b/shared/management/client/mock.go @@ -94,11 +94,6 @@ func (m *MockClient) HealthCheck() error { return m.HealthCheckFunc() } -// GetNetworkMap mock implementation of GetNetworkMap from Client interface. -func (m *MockClient) GetNetworkMap(_ *system.Info) (*proto.NetworkMap, error) { - return nil, nil -} - // GetServerURL mock implementation of GetServerURL from mgm.Client interface func (m *MockClient) GetServerURL() string { if m.GetServerURLFunc == nil {