[PR #48] Support ExcludeIPs in wg-quick configs (excluded routes) #48

Open
opened 2026-10-13 21:08:54 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/amnezia-vpn/amneziawg-apple/pull/48
Author: @SlavaLobozov
Created: 9/24/2026
Status: 🔄 Open

Base: master ← Head: feat/exclude-ips-config-key


📝 Commits (1)

  • d36756e Support ExcludeIPs in wg-quick configs (excluded routes)

📊 Changes

11 files changed (+165 additions, -7 deletions)

View changed files

📝 README.md (+23 -0)
📝 Sources/Shared/Model/TunnelConfiguration+WgQuickConfig.swift (+17 -2)
📝 Sources/WireGuardApp/Localizable.xcstrings (+48 -0)
📝 Sources/WireGuardApp/UI/TunnelViewModel.swift (+17 -0)
📝 Sources/WireGuardApp/UI/iOS/ViewController/TunnelDetailTableViewController.swift (+1 -1)
📝 Sources/WireGuardApp/UI/iOS/ViewController/TunnelEditTableViewController.swift (+2 -2)
📝 Sources/WireGuardApp/UI/macOS/ParseError+WireGuardAppError.swift (+2 -0)
📝 Sources/WireGuardApp/UI/macOS/View/highlighter.c (+5 -1)
📝 Sources/WireGuardApp/UI/macOS/ViewController/TunnelDetailTableViewController.swift (+1 -1)
📝 Sources/WireGuardKit/PeerConfiguration.swift (+4 -0)
➕ Tests/WireGuardKitTests/PeerConfigurationExcludeIPsTests.swift (+45 -0)

📄 Description

Summary

Adds first-class support for the ExcludeIPs key in the [Peer] section of wg-quick configs.

PeerConfiguration.excludeIPs already exists and PacketTunnelSettingsGenerator already installs it as excluded routes on the tunnel interface, but nothing populated it from a config file: the parser rejected ExcludeIPs as an unrecognized peer key, so the only way to use excluded routes was programmatic (as the AmneziaVPN client does). This PR wires the key through the whole app so a plain .conf can express "full tunnel, except these networks".

[Peer]
PublicKey = …
AllowedIPs = 0.0.0.0/0
ExcludeIPs = 213.59.252.0/22, 194.190.12.0/24
Endpoint = vpn.example.com:51820

Motivation

On iOS, a split tunnel expressed as a long complementary AllowedIPs list (everything except a few networks, no 0.0.0.0/0) leaves the default route on the physical interface. NWPathMonitor in the network extension then reports the path as unsatisfied on some networks, and the adapter pauses the backend while the UI still shows "Connected" — see amnezia-vpn/amnezia-client#3207. A full-tunnel config never hits this, because the tunnel interface owns the default route.

With AllowedIPs = 0.0.0.0/0 plus ExcludeIPs, the tunnel keeps the default route and the listed destinations still bypass it via NEIPv4Settings.excludedRoutes / NEIPv6Settings.excludedRoutes. In our deployment this replaces a 517-prefix AllowedIPs list with one route and 45 excluded routes, and the config fits in a QR code again.

Changes

  • Parser (TunnelConfiguration+WgQuickConfig.swift): accept ExcludeIPs in [Peer]; comma-separated, multiple lines allowed (like AllowedIPs); same validation via IPAddressRange; new ParseError.peerHasInvalidExcludeIP.
  • Exporter: emit ExcludeIPs = … when non-empty, so edit / export / zip round-trips keep it.
  • PeerConfiguration: include excludeIPs in == and hash(into:), so a changed exclude list is detected and applied like any other peer change.
  • UI: "Exclude IPs" field on the iOS/macOS detail screens and the iOS edit screen (hidden when empty, like other optional fields); macOS parse-error mapping; English and Russian strings.
  • macOS editor: highlighter.c treats ExcludeIPs values like AllowedIPs (IP / CIDR highlighting, multi-value).
  • Tests (PeerConfigurationExcludeIPsTests): equality/hash sensitivity; generated network settings contain the excluded routes (IPv4 and IPv6) alongside the endpoint exclusion, and included routes stay interface address + AllowedIPs.
  • README: documents the key.

The key is local to the app: it never reaches the UAPI / the peer, and the on-wire cryptokey routing (AllowedIPs) is unchanged.

Notes for review

  • Naming follows the existing excludeIPs property. ExcludeIPs is also the spelling used by the AmneziaVPN client and by community forks, so configs are interchangeable.
  • No excludePrivateIPs interaction: that checkbox still derives from AllowedIPs only.
  • I could not run the Xcode build on this machine, so the change was made to mirror the AllowedIPs code paths line by line; the tests target WireGuardKit (SwiftPM) only. Happy to adjust anything.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/amnezia-vpn/amneziawg-apple/pull/48 **Author:** [@SlavaLobozov](https://github.com/SlavaLobozov) **Created:** 9/24/2026 **Status:** 🔄 Open **Base:** `master` ← **Head:** `feat/exclude-ips-config-key` --- ### 📝 Commits (1) - [`d36756e`](https://github.com/amnezia-vpn/amneziawg-apple/commit/d36756ed5e6b58ccc09889fa993c0228103f1b8e) Support ExcludeIPs in wg-quick configs (excluded routes) ### 📊 Changes **11 files changed** (+165 additions, -7 deletions) <details> <summary>View changed files</summary> 📝 `README.md` (+23 -0) 📝 `Sources/Shared/Model/TunnelConfiguration+WgQuickConfig.swift` (+17 -2) 📝 `Sources/WireGuardApp/Localizable.xcstrings` (+48 -0) 📝 `Sources/WireGuardApp/UI/TunnelViewModel.swift` (+17 -0) 📝 `Sources/WireGuardApp/UI/iOS/ViewController/TunnelDetailTableViewController.swift` (+1 -1) 📝 `Sources/WireGuardApp/UI/iOS/ViewController/TunnelEditTableViewController.swift` (+2 -2) 📝 `Sources/WireGuardApp/UI/macOS/ParseError+WireGuardAppError.swift` (+2 -0) 📝 `Sources/WireGuardApp/UI/macOS/View/highlighter.c` (+5 -1) 📝 `Sources/WireGuardApp/UI/macOS/ViewController/TunnelDetailTableViewController.swift` (+1 -1) 📝 `Sources/WireGuardKit/PeerConfiguration.swift` (+4 -0) ➕ `Tests/WireGuardKitTests/PeerConfigurationExcludeIPsTests.swift` (+45 -0) </details> ### 📄 Description ## Summary Adds first-class support for the `ExcludeIPs` key in the `[Peer]` section of wg-quick configs. `PeerConfiguration.excludeIPs` already exists and `PacketTunnelSettingsGenerator` already installs it as excluded routes on the tunnel interface, but nothing populated it from a config file: the parser rejected `ExcludeIPs` as an unrecognized peer key, so the only way to use excluded routes was programmatic (as the AmneziaVPN client does). This PR wires the key through the whole app so a plain `.conf` can express "full tunnel, except these networks". ``` [Peer] PublicKey = … AllowedIPs = 0.0.0.0/0 ExcludeIPs = 213.59.252.0/22, 194.190.12.0/24 Endpoint = vpn.example.com:51820 ``` ## Motivation On iOS, a split tunnel expressed as a long complementary `AllowedIPs` list (everything except a few networks, no `0.0.0.0/0`) leaves the default route on the physical interface. `NWPathMonitor` in the network extension then reports the path as `unsatisfied` on some networks, and the adapter pauses the backend while the UI still shows "Connected" — see amnezia-vpn/amnezia-client#3207. A full-tunnel config never hits this, because the tunnel interface owns the default route. With `AllowedIPs = 0.0.0.0/0` plus `ExcludeIPs`, the tunnel keeps the default route and the listed destinations still bypass it via `NEIPv4Settings.excludedRoutes` / `NEIPv6Settings.excludedRoutes`. In our deployment this replaces a 517-prefix `AllowedIPs` list with one route and 45 excluded routes, and the config fits in a QR code again. ## Changes - **Parser** (`TunnelConfiguration+WgQuickConfig.swift`): accept `ExcludeIPs` in `[Peer]`; comma-separated, multiple lines allowed (like `AllowedIPs`); same validation via `IPAddressRange`; new `ParseError.peerHasInvalidExcludeIP`. - **Exporter**: emit `ExcludeIPs = …` when non-empty, so edit / export / zip round-trips keep it. - **`PeerConfiguration`**: include `excludeIPs` in `==` and `hash(into:)`, so a changed exclude list is detected and applied like any other peer change. - **UI**: "Exclude IPs" field on the iOS/macOS detail screens and the iOS edit screen (hidden when empty, like other optional fields); macOS parse-error mapping; English and Russian strings. - **macOS editor**: `highlighter.c` treats `ExcludeIPs` values like `AllowedIPs` (IP / CIDR highlighting, multi-value). - **Tests** (`PeerConfigurationExcludeIPsTests`): equality/hash sensitivity; generated network settings contain the excluded routes (IPv4 and IPv6) alongside the endpoint exclusion, and included routes stay `interface address + AllowedIPs`. - **README**: documents the key. The key is local to the app: it never reaches the UAPI / the peer, and the on-wire cryptokey routing (`AllowedIPs`) is unchanged. ## Notes for review - Naming follows the existing `excludeIPs` property. `ExcludeIPs` is also the spelling used by the AmneziaVPN client and by community forks, so configs are interchangeable. - No `excludePrivateIPs` interaction: that checkbox still derives from `AllowedIPs` only. - I could not run the Xcode build on this machine, so the change was made to mirror the `AllowedIPs` code paths line by line; the tests target `WireGuardKit` (SwiftPM) only. Happy to adjust anything. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
amnezia-vpn/amneziawg-apple#48
No description provided.