evgeniyChepelevandClaude Opus 5 46ba1cb106 Fix exit node (#226)
* Fix exit node

* Bound the route IPC round-trip so its completion always fires

sendProviderMessage only calls its reply handler if the extension answers.
An extension that never does - wedged in a synchronous Go call, gone, or
returning out of handleAppMessage before replying - stranded the completion
that getRoutes/selectRoutes/deselectRoutes promise on every path. That also
hung the DispatchGroup RoutesViewModel.selectRoute balances around the
sibling deselects, so the pending select was never sent and the optimistic
exit node selection was never reverted.

Bound the wait with a DispatchWorkItem and deliver exactly one outcome
through an NSLock-guarded latch, cancelling the timeout when a reply or a
send error completes the request - the same pattern fetchData already uses
for the status round-trip. Adds RouteRequestError.timedOut.

Also promotes sendSelectAndReconcile's comment to a doc comment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Keep the On Demand preference when a re-arm is refused

rearmOnDemandIfNeeded wrote connectOnDemand = false on .failed, guarded only
by `guard self.connectOnDemand`. But true is exactly the value a newer,
successful re-arm - or the user's own Settings toggle - leaves behind, so
that guard cannot tell a stale failure from a fresh success. Nothing pinned
the completion to the request that started it.

The window is wide: applyOnDemandState only reports .failed after
recoverFromFailedOnDemandWrite has run a full loadFromPreferences round-trip.
Two overlapping re-arms are enough to hit it with no user involved -
applyStatusSideEffects(.connected) runs from both applyExtensionStatus and
startActivation, and the very reload that clears configurationStale is what
lets the second attempt succeed. The late failure from the first then
clobbered rules that were genuinely armed.

The write was also wrong in kind. Unlike setConnectOnDemand it makes no
optimistic write of its own, so there is no previous value to roll back to;
and false closes the guard, so a single transient NEVPNError.configurationStale
would discard the user's setting for good instead of letting the next connect
retry. "Preference on, rules not armed yet" is a state the app already creates
on purpose - see checkLoginRequiredFlag().

Log the refusal and leave the intent standing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 21:26:02 +02:00
2026-09-16 21:26:02 +02:00
2026-09-09 15:45:20 +02:00
2026-09-16 21:26:02 +02:00
2024-02-28 19:22:16 +01:00
2024-02-28 19:22:16 +01:00
2024-02-28 19:22:16 +01:00
2024-02-28 19:22:16 +01:00


Start using NetBird at netbird.io
See Documentation
Join our Slack channel


NetBird iOS & tvOS Client

The NetBird iOS/tvOS client allows connections from mobile devices running iOS 14.0+ and Apple TV running tvOS 17.0+ to private resources in the NetBird network.

Install

You can download and install the app from the App Store:

Download on the App Store

Screenshots

mainscreen peer-overview menu

Code structure

The code is divided into 4 parts:

  • The main netbird Go code, included as a git submodule at /netbird-core from the NetBird repo, compiled into an xcframework. This contains most of the client logic.
  • The network extension /NetbirdNetworkExtension that is running in the background where the compiled Go code is executed.
  • The UI and app code under /NetBird
  • The /NetbirdKit/NetworkExtensionAdapter that controls the extension and builds the link between UI and extension

Requirements

  • iOS 14.0+ / tvOS 17.0+
  • Xcode 16.1+
  • Go (version determined by the netbird submodule's go.mod)
  • gomobile (for iOS builds)
  • gomobile-netbird (for tvOS builds — NetBird's fork with tvOS support)

gomobile

For iOS-only builds, install the standard gomobile:

go install golang.org/x/mobile/cmd/gomobile@latest

gomobile-netbird

For tvOS builds, install gomobile-netbird, NetBird's fork of gomobile that adds tvOS support. See: https://github.com/netbirdio/gomobile-tvos-fork

go install github.com/netbirdio/gomobile-tvos-fork/cmd/gomobile-netbird@latest

Run locally

The main netbird Go code is included as a git submodule at netbird-core/.

git clone --recursive https://github.com/netbirdio/ios-client.git
cd ios-client

If you already cloned without --recursive:

git submodule update --init --recursive

Build the XCFramework for iOS:

./build-go-lib.sh

Or for tvOS (includes iOS, iOS Simulator, tvOS, and tvOS Simulator targets):

./build-go-lib.sh --tvos

You can also pass an explicit version:

./build-go-lib.sh 0.2.0
./build-go-lib.sh --tvos 0.2.0

Open the Xcode project, and we are ready to go.

Updating the netbird submodule

To update to a newer netbird version:

cd netbird-core
git fetch --tags
git checkout v0.x.y   # or any branch/commit
cd ..
git add netbird-core
git commit -m "update netbird submodule to v0.x.y"

Running on iOS Device

Note: The app cannot run in the iOS simulator. To test the app, a physical device needs to be connected to Xcode via cable and set as the run destination.

Running on Apple TV

Note: The app cannot run in the tvOS simulator. To test the app, a physical device running tvOS 17.0 or later needs to be paired with Xcode.

Firebase Configuration (Optional)

The app supports Firebase for analytics and crash reporting. To enable it, add your GoogleService-Info.plist file to the project root. The app will work without Firebase configuration.

Other project repositories

NetBird project is composed of multiple repositories:

Languages
Swift 98%
C 1.1%
Shell 0.5%
Objective-C 0.4%