From aa164c93cfb16c8b48af2e31c52fc7a9d2eddf44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Papp?= Date: Sun, 31 May 2026 21:01:07 +0200 Subject: [PATCH] [ios] compute route connection status in the bridge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The iOS bridge exposed a route's Network as a possibly comma-joined string ("0.0.0.0/0, ::/0" for a merged exit node) but no connection status, forcing the UI to infer status by string-matching that joined value against peer routes — which never matched for the merged exit node, leaving it stuck as not-connected. Android already computes status in the core (findBestRoutePeer). Mirror that here: add a Status field to RoutesSelectionInfo and compute it from the connected peers' route tables, matching the route's primary prefix, a merged exit node's extra v6 prefix, or a dynamic route's domain pattern (the key the route manager records). The UI can now read the status directly. --- client/ios/NetBirdSDK/client.go | 50 +++++++++++++++++++++++++++++++++ client/ios/NetBirdSDK/routes.go | 1 + 2 files changed, 51 insertions(+) diff --git a/client/ios/NetBirdSDK/client.go b/client/ios/NetBirdSDK/client.go index bafbb0031..bfcef6331 100644 --- a/client/ios/NetBirdSDK/client.go +++ b/client/ios/NetBirdSDK/client.go @@ -54,6 +54,7 @@ type selectRoute struct { Network netip.Prefix Domains domain.List Selected bool + Status string extraNetworks []netip.Prefix } @@ -377,9 +378,57 @@ func (c *Client) GetRoutesSelectionDetails() (*RoutesSelectionDetails, error) { routes := buildSelectRoutes(routesMap, routeSelector.IsSelected, v6ExitMerged) resolvedDomains := c.recorder.GetResolvedDomainsStates() + // Compute each route's connection status in the core (mirroring the Android + // bridge), so the UI doesn't have to infer it by string-matching the joined + // Network value against peer routes. For a merged exit node the status reflects + // whichever of the v4/v6 prefixes is served by a connected peer; for dynamic + // (DNS) routes the peer route key is the domain pattern (see dynamic.Route.String). + connectedRoutes := c.connectedRouteSet() + for _, r := range routes { + r.Status = routeStatus(r, connectedRoutes) + } + return prepareRouteSelectionDetails(routes, resolvedDomains), nil } +// connectedRouteSet returns the set of route keys (as strings) currently served by a +// connected peer, gathered across all connected peers' route tables. The keys match +// what the route manager records: a prefix string for static routes (e.g. "0.0.0.0/0") +// and the domain pattern for dynamic routes (e.g. "*.example.com"). +func (c *Client) connectedRouteSet() map[string]struct{} { + connected := map[string]struct{}{} + for _, p := range c.recorder.GetFullStatus().Peers { + if p.ConnStatus != peer.StatusConnected { + continue + } + for r := range p.GetRoutes() { + connected[r] = struct{}{} + } + } + return connected +} + +// routeStatus reports "Connected" if any of the route's keys is served by a connected +// peer: the primary Network prefix, an extra v6 network of a merged exit node, or the +// domain pattern for a dynamic DNS route. Otherwise "Idle". +func routeStatus(r *selectRoute, connectedRoutes map[string]struct{}) string { + keys := make([]string, 0, 1+len(r.extraNetworks)) + if len(r.Domains) > 0 { + keys = append(keys, r.Domains.SafeString()) + } else { + keys = append(keys, r.Network.String()) + } + for _, extra := range r.extraNetworks { + keys = append(keys, extra.String()) + } + for _, k := range keys { + if _, ok := connectedRoutes[k]; ok { + return peer.StatusConnected.String() + } + } + return peer.StatusIdle.String() +} + func buildSelectRoutes(routesMap map[route.NetID][]*route.Route, isSelected func(route.NetID) bool, v6Merged map[route.NetID]struct{}) []*selectRoute { var routes []*selectRoute for id, rt := range routesMap { @@ -462,6 +511,7 @@ func prepareRouteSelectionDetails(routes []*selectRoute, resolvedDomains map[dom Network: netStr, Domains: &domainDetails, Selected: r.Selected, + Status: r.Status, }) } diff --git a/client/ios/NetBirdSDK/routes.go b/client/ios/NetBirdSDK/routes.go index 025313bfa..56af2a1ad 100644 --- a/client/ios/NetBirdSDK/routes.go +++ b/client/ios/NetBirdSDK/routes.go @@ -20,6 +20,7 @@ type RoutesSelectionInfo struct { Network string Domains *DomainDetails Selected bool + Status string } type DomainCollection interface {