Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions app/src/main/java/eu/faircode/netguard/ServiceSinkhole.java
Original file line number Diff line number Diff line change
Expand Up @@ -1623,9 +1623,21 @@ private Builder getBuilder(List<Rule> listAllowed, List<Rule> listRule) {

// WireGuard AllowedIPs can opt RFC 1918 ranges into the VPN routes;
// without WireGuard, private and reserved ranges remain excluded.
List<IPUtil.CIDR> routes = (wgEnabled && !wgAllowedIps.isEmpty())
? VpnRoutes.getRoutes(wgAllowedIps)
: VpnRoutes.getRoutes();
// Tethering compatibility mode instead installs a plain default route,
// which some devices need before they forward tethered traffic into the
// tun at all (#699). WireGuard keeps precedence: a full tunnel would
// hand LAN and reserved traffic to the peer, which drops it.
boolean tetheringCompat = prefs.getBoolean("tcp_mss_clamp", false);
List<IPUtil.CIDR> routes;
if (wgEnabled && !wgAllowedIps.isEmpty()) {
if (tetheringCompat)
Log.i(TAG, "Tethering compatibility routes skipped: WireGuard AllowedIPs apply");
routes = VpnRoutes.getRoutes(wgAllowedIps);
} else if (tetheringCompat) {
Log.i(TAG, "Using tethering compatibility routes (full tunnel)");
routes = VpnRoutes.getTetheringRoutes();
} else
routes = VpnRoutes.getRoutes();
for (IPUtil.CIDR route : routes)
try {
builder.addRoute(route.address, route.prefix);
Expand Down
21 changes: 21 additions & 0 deletions app/src/main/java/eu/faircode/netguard/VpnRoutes.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,27 @@ public static List<IPUtil.CIDR> getRoutes() {
return cachedRoutes;
}

/**
* Returns the full-tunnel route list (a single 0.0.0.0/0 default route)
* used by tethering compatibility mode.
*
* <p>Some devices only forward tethered traffic into the tun while the VPN
* carries a real default route; with the split-tunnel route set above they
* fall back to the device's own tethering path, which on such devices is
* broken (#699). This also puts the tethering downstream subnets
* (192.168.42/43/44/49) and the 255.255.255.255 DHCP broadcast back inside
* the tunnel, matching the behaviour TrackerControl had before the static
* route set was introduced.
*
* <p>The cost is that LAN traffic and reserved ranges no longer bypass the
* tunnel, which is why this is opt-in. Carrier ePDG addresses are still
* excluded on Android 13+, where {@code excludeRoute()} applies on top of
* this route.
*/
public static List<IPUtil.CIDR> getTetheringRoutes() {
return Collections.singletonList(new IPUtil.CIDR("0.0.0.0", 0));
}

/**
* Returns the route list for an active WireGuard remote-egress tunnel,
* making the profile's AllowedIPs authoritative over the RFC 1918
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
<string name="setting_call">Disable on call</string>
<string name="setting_reload_onconnectivity">Reload on every connectivity change</string>
<string name="setting_tcp_mss_clamp">Tethering compatibility mode</string>
<string name="summary_tcp_mss_clamp">Limit TCP segment size to avoid stalled connections on some tethered networks. May reduce performance.</string>
<string name="summary_tcp_mss_clamp">Route all traffic through TrackerControl and limit TCP segment size, to fix tethered connections that stall or get no internet access. Local network access bypasses TrackerControl no longer, and performance may be reduced.</string>

<string name="setting_advanced_options">Advanced options (for experts)</string>
<string name="setting_system">Show system apps</string>
Expand Down
32 changes: 32 additions & 0 deletions app/src/test/java/eu/faircode/netguard/VpnRoutesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package eu.faircode.netguard;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

Expand Down Expand Up @@ -87,6 +88,37 @@ public void emptyOrIpv6OnlyAllowedIpsFallsBackToDefault() throws Exception {
assertFalse(isRouted(viaV6, "192.168.1.10"));
}

// --- tethering compatibility mode ---------------------------------------

@Test
public void tetheringRoutesAreASingleDefaultRoute() throws Exception {
List<IPUtil.CIDR> routes = VpnRoutes.getTetheringRoutes();

assertEquals(1, routes.size());
assertEquals(0, routes.get(0).prefix);

// Everything is inside the tunnel, including the ranges the default
// route set excludes: the tethering downstream subnets and the DHCP
// broadcast the tethered client uses to get a lease (#699).
assertTrue(isRouted(routes, "8.8.8.8"));
assertTrue(isRouted(routes, "192.168.42.129")); // USB tethering
assertTrue(isRouted(routes, "192.168.43.1")); // Wi-Fi tethering
assertTrue(isRouted(routes, "192.168.44.1")); // Bluetooth tethering
assertTrue(isRouted(routes, "10.0.0.1"));
assertTrue(isRouted(routes, "100.64.0.1"));
assertTrue(isRouted(routes, "0.0.0.0"));
assertTrue(isRouted(routes, "255.255.255.255")); // DHCP broadcast
}

@Test
public void tetheringRoutesDoNotAffectTheDefaultRouteSet() throws Exception {
VpnRoutes.getTetheringRoutes();
List<IPUtil.CIDR> routes = VpnRoutes.getRoutes();

assertFalse(isRouted(routes, "192.168.42.129"));
assertTrue(isRouted(routes, "8.8.8.8"));
}

// --- WireGuard active: AllowedIPs authoritative --------------------------

@Test
Expand Down