Skip to content

Feat/split tunneling - #244

Merged
pappz merged 3 commits into
netbirdio:ux/ios-style-redesignfrom
eYey343:feat/split-tunneling
Sep 6, 2026
Merged

Feat/split tunneling#244
pappz merged 3 commits into
netbirdio:ux/ios-style-redesignfrom
eYey343:feat/split-tunneling

Conversation

@eYey343

@eYey343 eYey343 commented Aug 20, 2026

Copy link
Copy Markdown

Let the user choose which apps the tunnel carries

What

Adds a split tunnelling screen under Settings, letting the user decide which
applications the tunnel carries. Three modes:

  • Off — every app uses the VPN, the current behaviour.
  • Exclude — the picked apps bypass the tunnel, everything else stays in it.
  • Include — only the picked apps use the tunnel.

Until now IFace.createTun kept four applications out of the tunnel with no way
for the user to add their own. Those four remain the floor of Exclude, so
turning split tunnelling on never silently pulls them back in.

How

Android's VpnService.Builder takes an allow list or a deny list, never both on
the same builder, so the two selections are stored apart and a mode says which
one is live. SplitTunnelConfig holds that decision — plain Java, no Android
types, so the rules are covered by JVM unit tests.

A change to the selection leaves routes and search domains untouched, which the
existing recreateTUN guard reads as "nothing to do". The renewal request now
carries a force flag so the rebuild that applies the new filter actually runs,
and the change takes hold without asking the user to reconnect.

Two rules are worth calling out:

  • An empty Include selection would allow no app at all, leaving a tunnel that
    carries nothing and reads as broken rather than configured. It falls back to
    carrying everything, and the screen says so.
  • Include always carries this app. The Go engine's own sockets bypass the tunnel
    through protectSocket, but the built-in SSH client has to reach peers
    through it.

The app list resolves the launcher intent rather than asking for
QUERY_ALL_PACKAGES, which Play treats as a sensitive permission. The trade-off
is that applications without a launcher entry are not listed.

Tests

  • SplitTunnelConfigUnitTest — 11 JVM tests covering mode resolution, the empty
    allowlist fallback, self-inclusion, and defensive copying of the stored sets.
  • PreferencesInstrumentedTest — 4 new cases for persistence, including keeping
    the inactive selection across a mode change.

Verified by hand on an API 30 emulator with the tunnel up, watching the filter
follow the mode (disallow 4disallow 5allow 2) and the tunnel rebuild
on every change without a reconnection.

Screenshots

Settings entry Mode picker
Exclude mode Empty allowlist

Note for reviewers

The branch also carries Implement the ConnectionListener state callback, kept
as a separate commit. The netbird bump in 1b7f067 added OnStateChanged to the
interface without the Java side following, which leaves the branch unable to
compile. Drop that commit if it lands upstream first.

@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: cbb14350-bb1f-4b50-80f0-ed24a8f1e79a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pappz pappz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In pruneUninstalled(), call save() instead of persist() (or call serviceAccessor.applySplitTunneling() after a successful persist), so a prune that changes the effective resolution is applied to the live tunnel.

@pappz pappz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Synchronize access to tunCreator (one lock shared by queueTUNRenewal and onDestroy), or create the thread eagerly in onCreate and only post messages afterwards.

User toggles an app in the split-tunneling screen at the same moment the Go engine reports a route change, before any TUN renewal has ever been queued: both threads see tunCreator == null, both create and start a TUNCreatorLooperThread. One is orphaned and never quit, and the tunnel can be rebuilt twice back to back. Or a route change lands while onDestroy is tearing down, the Go thread passes the null check, onDestroy sets the field to null, the Go thread dereferences it and crashes the process.

Adds a split tunnelling screen with three modes: off, exclude the picked
apps, or include only them. The two selections are stored apart, since
Android's builder takes an allow list or a deny list but never both.
A change forces a TUN rebuild, so it applies without reconnecting.
Moves the mode and both app selections out of SharedPreferences and into
the profile's preferences on the Go side, so they follow the active
profile instead of being shared by every profile on the device.

SplitTunnelConfig is unchanged and still owns the VpnService rules: allow
list against deny list, the historic exclusions, and the fallback when an
include selection is empty. The new SplitTunnelStore only translates
between it and the Go settings, and resolves the active profile.

Needs the matching submodule change.
The submodule bump also carries the split tunnel store the previous commit needed.
@eYey343
eYey343 force-pushed the feat/split-tunneling branch from 00c7252 to 94a679f Compare September 5, 2026 22:06
@pappz
pappz merged commit 8e5125c into netbirdio:ux/ios-style-redesign Sep 6, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants