Skip to content

Make WireGuard AllowedIPs authoritative over RFC1918 route exclusions (#593)#645

Merged
kasnder merged 3 commits into
masterfrom
claude/wg-private-routes-593
Jul 12, 2026
Merged

Make WireGuard AllowedIPs authoritative over RFC1918 route exclusions (#593)#645
kasnder merged 3 commits into
masterfrom
claude/wg-private-routes-593

Conversation

@kasnder

@kasnder kasnder commented Jul 12, 2026

Copy link
Copy Markdown
Member

Closes #593 (draft — see note at bottom).

Problem

VpnRoutes.EXCLUDED_RANGES hardcoded the RFC 1918 private ranges (10/8, 172.16/12, 192.168/16) as always excluded from the tun. That is correct for the common case (LAN traffic should reach the local network directly, not the tunnel), but it breaks a legitimate WireGuard remote-egress setup:

A user with WG remote egress and split-DNS, where the VPN resolver returns RFC 1918 addresses for self-hosted services behind the WG endpoint, can't reach those hosts. DNS resolves, but traffic to the private IP bypasses the tunnel and dies. Setting AllowedIPs = 0.0.0.0/0 in the WG profile had no effect on Android's routes.

Design: AllowedIPs-authoritative, no new toggle

Philosophy here is simplicity over configurability, so this adds no user-facing setting. Instead, when WireGuard remote egress is active, the profile's AllowedIPs become authoritative over the RFC 1918 exclusions:

  • Any part of an RFC 1918 range that a peer's AllowedIPs covers (e.g. 0.0.0.0/0, or an explicit 192.168.1.0/24) is routed into the tunnel instead of excluded.
  • When WireGuard remote egress is off, routing is byte-for-byte identical to today.
  • Users who don't want their LAN pulled into the tunnel simply narrow AllowedIPs (the standard WireGuard knob).

This gives the reporter their fix with zero UI, and keeps the WG profile as the single source of truth for what egresses remotely.

Always excluded, regardless of AllowedIPs

Loopback (127/8), link-local (169.254/16), multicast/reserved (224/3), CGNAT (100.64/10), current-network (0/8), and the carrier Wi-Fi-calling ranges are split out into an ALWAYS_EXCLUDED set and are never routed into the tun, even if AllowedIPs = 0.0.0.0/0. WG's own socket is already handled by VpnService.protect(), but we still must never route loopback/link-local/multicast into the tunnel.

Implementation

  • VpnRoutes now splits its ranges into ALWAYS_EXCLUDED and RFC1918_RANGES.
  • New VpnRoutes.getRoutes(List<String> wgAllowedIps): parses the AllowedIPs (IPv4 only), and for each RFC 1918 range subtracts the AllowedIPs-covered portion before computing the route complement (interval math on 32-bit longs; overlapping/unsorted AllowedIPs handled). Result is cached by a normalized AllowedIPs signature so the per-rebuild cost stays low.
  • getRoutes() (no-arg) is unchanged behaviourally and still cached — used whenever WG egress is off or has no usable IPv4 AllowedIPs.
  • ServiceSinkhole.getBuilder() collects the union of every peer's AllowedIPs from the already-parsed WG config and passes it through when wg_enabled and the config parses.

Trade-off

When AllowedIPs covers RFC 1918 space, that LAN traffic enters the tunnel rather than reaching the local network directly. That is exactly what AllowedIPs means in WireGuard, and it is what the reporter wants, but it is a behaviour change for anyone who set 0.0.0.0/0 while also relying on direct LAN access. The mitigation is standard: narrow AllowedIPs to only the remote subnets you actually want tunneled.

IPv6

IPv6 AllowedIPs are ignored for now (the existing IPUtil.CIDR math is IPv4-only). RFC 1918 is IPv4-only anyway, and ULA handling would need parallel v6 interval math — noted as a follow-up rather than bundled here.

Tests

VpnRoutesTest (Robolectric) covers: default exclusions (WG off), 0.0.0.0/0 pulling all RFC 1918 in while reserved ranges stay out, an explicit LAN subnet routing only that subnet, unions of multiple AllowedIPs, empty/IPv6-only falling back to default, and a guard that reserved ranges are never re-exposed even if listed in AllowedIPs. ./gradlew :app:compileGithubDebugJavaWithJavac passes.

Note: running the unit tests currently requires working around a pre-existing, unrelated resource-linking break on mastervalues-uk-rUA/strings.xml references @string/режим_блокування_* strings that no longer exist in the base values/strings.xml (the known Crowdin string-recreation issue). That is not touched by this PR.

Needs on-device verification

  • WG egress on with AllowedIPs = 0.0.0.0/0: confirm a self-hosted RFC 1918 host behind the endpoint is reachable and general traffic still works.
  • WG egress on with an explicit private subnet in AllowedIPs: confirm only that subnet tunnels and other LAN hosts remain directly reachable.
  • WG egress off: confirm LAN access and routing are unchanged from before.
  • Confirm Wi-Fi calling / loopback / multicast are unaffected in all cases.

Note: a community contributor volunteered on the issue on 2026-07-12. This draft is offered as a starting point — the maintainer may prefer to hand it over to them.

🤖 Generated with Claude Code

kasnder and others added 2 commits July 12, 2026 10:26
…#593)

VpnRoutes previously hardcoded RFC1918 ranges (10/8, 172.16/12,
192.168/16) as always excluded from the tun, so LAN traffic bypasses
the tunnel and reaches the local network directly. With WireGuard
remote egress and a split-DNS setup (the VPN resolver returns private
addresses for self-hosted services behind the WG endpoint), those hosts
became unreachable: DNS resolved but packets to private IPs left the
tunnel and died.

When WG remote egress is active, the profile's AllowedIPs now decide:
any part of an RFC1918 range covered by a peer's AllowedIPs (0.0.0.0/0,
or an explicit subnet like 192.168.1.0/24) is routed into the tunnel
instead of excluded. WG off keeps today's behaviour exactly. No new
user-facing toggle — users who don't want LAN-through-tunnel narrow
their AllowedIPs.

Loopback, link-local, multicast, CGNAT, current-network, and carrier
Wi-Fi-calling ranges stay ALWAYS excluded regardless of AllowedIPs.
IPv6 AllowedIPs are ignored for now (follow-up).

Adds VpnRoutesTest covering the default exclusions, 0.0.0.0/0, explicit
subnets, unions, and the reserved-range guard.

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

kasnder commented Jul 12, 2026

Copy link
Copy Markdown
Member Author

Review summary: I inspected the full diff and surrounding VPN-builder/WireGuard configuration flow, including RFC1918 interval subtraction, overlapping/unsorted AllowedIPs, the route-cache key, malformed/IPv6 entry handling, reserved-range precedence, and the added Robolectric coverage. I did not find an actionable correctness or regression issue. is clean, and the refreshed GitHub job passes.

@kasnder kasnder marked this pull request as ready for review July 12, 2026 10:52
@kasnder kasnder merged commit 95f3f8b into master Jul 12, 2026
1 check 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.

Allow traffic towards private internal endpoints via WireGuard

1 participant