Skip to content

[client] Add IPv6 dual-stack UI support#79

Open
lixmal wants to merge 9 commits intomainfrom
feature/ipv6-support
Open

[client] Add IPv6 dual-stack UI support#79
lixmal wants to merge 9 commits intomainfrom
feature/ipv6-support

Conversation

@lixmal
Copy link
Copy Markdown
Contributor

@lixmal lixmal commented Mar 30, 2026

Describe your changes

Add IPv6 support to the iOS and tvOS client UI, complementing the Go-side changes in netbirdio/netbird#5738.

  • Display IPv6 address in peer cards and peer detail sheets
  • Add "Disable IPv6" toggle to iOS advanced settings and tvOS settings
  • Configure NEIPv6Settings on the tunnel interface when an IPv6 address is assigned
  • Handle IPv6 address change notifications from the Go SDK
  • Persist disable-IPv6 preference via ConfigurationProvider (App Group on iOS, JSON config on tvOS)

Checklist

  • I have performed a self-review of my own code
  • Is a feature enhancement

Summary by CodeRabbit

Release Notes

  • New Features

    • Added IPv6 address display for peer connections and details
    • Added "Disable IPv6" toggle to Advanced settings (iOS and tvOS)
    • Extended IPv6 support in network configuration
  • Bug Fixes

    • Corrected IPv6 property handling in tunnel provider
    • Improved IPv6 route selection logic
  • Chores

    • Updated subproject dependency

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 30, 2026

Review Change Stack

Warning

Rate limit exceeded

@lixmal has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 33 minutes and 42 seconds before requesting another review.

To continue reviewing without waiting, purchase usage credits in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f6267f4a-623c-4145-aa56-0f9ce2e64595

📥 Commits

Reviewing files that changed from the base of the PR and between c8a6d3a and 5fd1c06.

📒 Files selected for processing (4)
  • NetBird/Source/App/ViewModels/MainViewModel.swift
  • NetbirdKit/ConfigurationProvider.swift
  • NetbirdKit/NetworkChangeListener.swift
  • NetbirdNetworkExtension/PacketTunnelProviderSettingsManager.swift
📝 Walkthrough

Walkthrough

This PR adds IPv6 support across the NetBird iOS client: new optional ipv6 fields in data structures, a persisted disableIPv6 configuration setting, tunnel interface IPv6 address configuration with blackhole routing fallback, corrected IPv6 property mappings in packet tunnel status reporting, and UI toggles for disabling IPv6 along with peer IPv6 display in cards and detail sheets.

Changes

IPv6 Support Across Configuration, Network Extension, and UI

Layer / File(s) Summary
Data Schema & Type Contracts
NetbirdKit/StatusDetails.swift
StatusDetails and PeerInfo add optional ipv6: String? field with updated Equatable comparisons and PeerInfo initializer/update methods.
Configuration Contract & Persistence
NetbirdKit/ConfigurationProvider.swift
Protocol adds disableIPv6: Bool { get set }; iOS implementation uses NetBirdSDKPreferences getters/setters; tvOS uses JSON field DisableIPv6.
Network Tunnel IPv6 Configuration
NetbirdNetworkExtension/PacketTunnelProviderSettingsManager.swift
New interfaceIPv6 property and setInterfaceIPv6(interfaceIPv6:) method; IPv6 settings are built from parsed address/prefix or fallback to fe80::1 blackhole with ::/0 included route for default routes.
Packet Tunnel Status & Route Reporting
NetbirdNetworkExtension/PacketTunnelProvider.swift,
NetBirdTVNetworkExtension/PacketTunnelProvider.swift
IPv6 field mappings corrected to use peer.iPv6 and statusDetailsMessage.getIPv6(); route-selection compactMap closure uses explicit -> RoutesSelectionInfo? return type.
View Model State & Configuration
NetBird/Source/App/ViewModels/MainViewModel.swift
New @Published var disableIPv6 flag with setDisableIPv6(disabled:) persistence method and loadIPv6Settings() synchronization from configProvider.
iOS & tvOS Settings UI
NetBird/Source/App/Views/AdvancedView.swift,
NetBird/Source/App/Views/TV/TVSettingsView.swift
AdvancedView adds "Disable IPv6" toggle in Network & Security; TVSettingsView adds Network section with IPv6 and Force Relay toggles; both call loadIPv6Settings() on view appearance.
Peer Display Components
NetBird/Source/App/Views/Components/PeerCard.swift,
NetBird/Source/App/Views/Components/PeerDetailSheet.swift
PeerCard conditionally renders peer.ipv6 below IPv4 address; PeerDetailSheet adds IPv6 row in main details section when available.
Supporting & Dependencies
NetbirdKit/NetworkChangeListener.swift,
netbird-core
Whitespace cleanup between setInterfaceIP and setInterfaceIPv6 methods; netbird-core submodule commit advanced.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • netbirdio/ios-client#99: Both PRs modify PacketTunnelProviderSettingsManager to add/adjust IPv6 blackhole/default-route handling (creating a link-local dummy IPv6 address and ::/0 included route).
  • netbirdio/ios-client#36: Related tvOS-support PR that modifies the same configuration and networking layers (ConfigurationProvider, PacketTunnelProvider/PacketTunnelProviderSettingsManager, and StatusDetails/PeerInfo).
  • netbirdio/ios-client#117: Both PRs modify PacketTunnelProvider.getSelectRoutes, adjusting how route-selection entries are constructed with explicit return types.

Suggested reviewers

  • pappz
  • mlsmaycon

🐰 Hops along with IPv6 delight,
Tunnels configured, DNS routes set right,
Peer addresses bloom in cards so bright,
tvOS and iOS, both shining light,
NetBird hops on, with protocols tight! 🌐✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main objective of the changeset—adding IPv6 dual-stack UI support to the iOS and tvOS client.
Description check ✅ Passed The description provides clear details of changes, references related work, and includes a checklist; however, it is minimal compared to the template structure which expects more structured documentation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/ipv6-support

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 and usage tips.

@github-actions
Copy link
Copy Markdown

TestFlight build failed for ac703e2

View workflow run

@github-actions
Copy link
Copy Markdown

TestFlight build failed for 8fbeabc

View workflow run

@lixmal
Copy link
Copy Markdown
Contributor Author

lixmal commented Mar 30, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 30, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions
Copy link
Copy Markdown

TestFlight build failed for 8fbeabc

View workflow run

@lixmal lixmal force-pushed the feature/ipv6-support branch 3 times, most recently from 0fc19cc to 192ea6d Compare April 18, 2026 10:15
@github-actions
Copy link
Copy Markdown

Build failed (iOS, tvOS) for 192ea6d

View workflow run

@lixmal lixmal force-pushed the feature/ipv6-support branch from 192ea6d to 3421c8b Compare April 18, 2026 10:29
@lixmal lixmal marked this pull request as ready for review April 18, 2026 10:37
@github-actions
Copy link
Copy Markdown

Build failed (iOS, tvOS) for 3421c8b

View workflow run

@lixmal lixmal force-pushed the feature/ipv6-support branch from 3421c8b to 7e15c12 Compare April 18, 2026 10:50
@github-actions
Copy link
Copy Markdown

Build failed (tvOS) for 7e15c12

View workflow run

lixmal added 2 commits April 21, 2026 10:43
# Conflicts:
#	NetbirdNetworkExtension/PacketTunnelProviderSettingsManager.swift
#	netbird-core
@lixmal
Copy link
Copy Markdown
Contributor Author

lixmal commented May 5, 2026

/testflight

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

Build failed (iOS) 0.1.6 (198) for b919a18

View workflow run

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

TestFlight builds uploaded 0.2.1 (4) for b919a18 — iOS + tvOS

View workflow run

@lixmal
Copy link
Copy Markdown
Contributor Author

lixmal commented May 7, 2026

/testflight

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

Build failed (iOS, tvOS) 0.2.1 (10) for f62edc9

View workflow run

@lixmal
Copy link
Copy Markdown
Contributor Author

lixmal commented May 7, 2026

/testflight

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

Build failed (iOS, tvOS) 0.2.1 (10) for d07c439

View workflow run

@lixmal
Copy link
Copy Markdown
Contributor Author

lixmal commented May 7, 2026

/testflight

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
NetbirdKit/NetworkChangeListener.swift (1)

111-128: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pre-existing IPv6 regex only matches fully-expanded 8-group addresses — all compressed forms will be silently dropped

The regex at line 113:

^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}(\/\d{1,3})?$

does not match any compressed IPv6 notation (e.g. ::1, fe80::1, ::/0, fd00::/8, 2001:db8::1/64). Routes sent by the Go SDK in compressed form will be classified as .invalid and silently discarded, leaving ipv6Routes permanently empty. This was a dormant bug before this PR, but now it directly breaks the IPv6 routing functionality being added.

A minimal fix is to replace the string-based regex with a presence heuristic or use inet_pton:

🐛 Proposed fix — replace the broken IPv6 regex
 func detectIPAddressType(_ address: String) -> IPAddressType {
     let ipv4Pattern = "^(\\d{1,3}\\.){3}\\d{1,3}(\\/\\d{1,2})?$"
-    let ipv6Pattern = "^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}(\\/\\d{1,3})?$"
 
     let ipv4Regex = try! NSRegularExpression(pattern: ipv4Pattern, options: [])
-    let ipv6Regex = try! NSRegularExpression(pattern: ipv6Pattern, options: [])
 
     let ipv4Matches = ipv4Regex.numberOfMatches(in: address, options: [], range: NSRange(location: 0, length: address.utf16.count))
-    let ipv6Matches = ipv6Regex.numberOfMatches(in: address, options: [], range: NSRange(location: 0, length: address.utf16.count))
 
     if ipv4Matches > 0 {
         return .ipv4
-    } else if ipv6Matches > 0 {
+    } else if address.contains(":") {
+        // IPv6 addresses always contain ":" — handles full, compressed, and CIDR forms
         return .ipv6
     } else {
         return .invalid
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@NetbirdKit/NetworkChangeListener.swift` around lines 111 - 128,
detectIPAddressType currently uses a strict IPv6 regex that only matches
fully-expanded 8-group addresses and thus misclassifies valid compressed IPv6
forms as .invalid; replace the regex approach in detectIPAddressType with a
robust validation that strips any CIDR suffix (split on "/") and uses inet_pton
(or equivalent system call) to test the bare address for AF_INET and AF_INET6,
returning .ipv4, .ipv6 or .invalid accordingly; update references in the
function detectIPAddressType and ensure IPAddressType enum values are used
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@NetBird/Source/App/ViewModels/MainViewModel.swift`:
- Around line 695-701: The UI state (self.disableIPv6) is being set before
persistence, so if configProvider.commit() fails the UI remains wrong; change
setDisableIPv6 to only update the UI and configProvider.disableIPv6 after a
successful commit (or, if you must set the UI first, revert self.disableIPv6
back to the prior value when configProvider.commit() returns false) by calling
configProvider.commit() with the intended value and on failure restore the
previous self.disableIPv6 and ensure configProvider.disableIPv6 reflects the
persisted value; key symbols: setDisableIPv6(disabled:), self.disableIPv6,
configProvider.disableIPv6, configProvider.commit().

In `@NetbirdKit/ConfigurationProvider.swift`:
- Around line 177-180: The disableIPv6 setter currently calls updateJSONField
which aborts when the JSON key is missing, so toggling the UI will silently fail
for configs without "DisableIPv6"; change updateJSONField (used by the
disableIPv6 setter) to perform an upsert instead of update-only—remove the
existence guard that checks dict[field] != nil and always assign dict[field] =
value (and then persist the JSON), or alternatively special-case the
"DisableIPv6" key to insert when absent; keep extractJSONBool as-is for reads.

In `@NetbirdKit/NetworkChangeListener.swift`:
- Around line 47-52: The method setInterfaceIPv6 currently only forwards the
value to tunnelManager and never stores it locally nor does
parseRoutesToNESettings inject an IPv6 interface route, and the IPv6 regex only
matches full 8-group addresses; fix by adding a stored property (e.g.,
interfaceIPv6) in NetworkChangeListener and assign validIPv6 to it inside
setInterfaceIPv6(interfaceIPv6:), then update parseRoutesToNESettings to add an
interface route for IPv6 peers analogous to the IPv4 path that uses
self.interfaceIP; finally replace the strict regex at the IPv6 route-parsing
site with a more permissive IPv6/CIDR matcher (or use system parsing like
IPv6Address/inet_pton to validate CIDR/compressed forms) so compressed addresses
and prefixes (e.g., ::1, 2001:db8::/32, ::/0, fd00::/8) are accepted.

In `@NetbirdNetworkExtension/PacketTunnelProviderSettingsManager.swift`:
- Around line 137-144: The helper extractIPv6AddressAndPrefix currently parses
any integer after the slash but doesn't validate it; update
extractIPv6AddressAndPrefix(from:) to verify the parsed prefix is within 0...128
and return nil for out-of-range values (and still return nil for malformed
input). Locate the function extractIPv6AddressAndPrefix(from cidr: String) and
add a range check for the parsed prefix (Int) before returning
(String(parts[0]), prefix) so callers like NEIPv6Settings receive only valid
prefix lengths.
- Around line 103-122: The code currently always creates and assigns an
NEIPv6Settings even when v6Addresses is empty; change the logic in
PacketTunnelProviderSettingsManager so you only instantiate and assign
NEIPv6Settings (using NEIPv6Settings(addresses:networkPrefixLengths:)) when
v6Addresses.count > 0 (i.e. when interfaceIPv6 or containsDefaultRoute supplied
an address); if v6Addresses is empty leave tunnelNetworkSettings.ipv6Settings
nil (and do not set includedRoutes) to avoid applying an empty IPv6 settings
object that breaks setTunnelNetworkSettings; look for symbols interfaceIPv6,
containsDefaultRoute, v6Addresses, NEIPv6Settings and tunnelNetworkSettings in
the diff to implement this guard.

---

Outside diff comments:
In `@NetbirdKit/NetworkChangeListener.swift`:
- Around line 111-128: detectIPAddressType currently uses a strict IPv6 regex
that only matches fully-expanded 8-group addresses and thus misclassifies valid
compressed IPv6 forms as .invalid; replace the regex approach in
detectIPAddressType with a robust validation that strips any CIDR suffix (split
on "/") and uses inet_pton (or equivalent system call) to test the bare address
for AF_INET and AF_INET6, returning .ipv4, .ipv6 or .invalid accordingly; update
references in the function detectIPAddressType and ensure IPAddressType enum
values are used unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1613742c-4e58-4f5b-a8ec-3e0b375a0bd9

📥 Commits

Reviewing files that changed from the base of the PR and between 3f67c8b and c8a6d3a.

📒 Files selected for processing (12)
  • NetBird/Source/App/ViewModels/MainViewModel.swift
  • NetBird/Source/App/Views/AdvancedView.swift
  • NetBird/Source/App/Views/Components/PeerCard.swift
  • NetBird/Source/App/Views/Components/PeerDetailSheet.swift
  • NetBird/Source/App/Views/TV/TVSettingsView.swift
  • NetBirdTVNetworkExtension/PacketTunnelProvider.swift
  • NetbirdKit/ConfigurationProvider.swift
  • NetbirdKit/NetworkChangeListener.swift
  • NetbirdKit/StatusDetails.swift
  • NetbirdNetworkExtension/PacketTunnelProvider.swift
  • NetbirdNetworkExtension/PacketTunnelProviderSettingsManager.swift
  • netbird-core

Comment thread NetBird/Source/App/ViewModels/MainViewModel.swift
Comment thread NetbirdKit/ConfigurationProvider.swift
Comment thread NetbirdKit/NetworkChangeListener.swift
Comment thread NetbirdNetworkExtension/PacketTunnelProviderSettingsManager.swift Outdated
Comment thread NetbirdNetworkExtension/PacketTunnelProviderSettingsManager.swift
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

TestFlight builds uploaded 0.2.1 (11) for c8a6d3a — iOS + tvOS

View workflow run

@lixmal
Copy link
Copy Markdown
Contributor Author

lixmal commented May 7, 2026

/testflight

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

TestFlight builds uploaded 0.2.1 (12) for 5fd1c06 — iOS + tvOS

View workflow run

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.

1 participant