diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/App.kt b/mobile/src/main/java/be/mygod/vpnhotspot/App.kt index 2556197d..077ff57d 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/App.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/App.kt @@ -18,7 +18,6 @@ import io.fabric.sdk.android.Fabric class App : Application() { companion object { const val KEY_OPERATING_CHANNEL = "service.repeater.oc" - private const val KEY_MASQUERADE = "service.masquerade" @SuppressLint("StaticFieldLeak") lateinit var app: App @@ -50,7 +49,8 @@ class App : Application() { val result = pref.getString(KEY_OPERATING_CHANNEL, null)?.toIntOrNull() ?: 0 return if (result in 1..165) result else 0 } - val masquerade: Boolean get() = pref.getBoolean(KEY_MASQUERADE, true) + val masquerade: Boolean get() = pref.getBoolean("service.masquerade", true) + val dhcpWorkaround: Boolean get() = pref.getBoolean("service.dhcpWorkaround", false) val cleanRoutings = Event0() } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyInterfaceManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyInterfaceManager.kt index 52e716ba..bcde73d0 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyInterfaceManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyInterfaceManager.kt @@ -40,15 +40,18 @@ class LocalOnlyInterfaceManager(val downstream: String) : UpstreamMonitor.Callba private fun initRouting(upstream: String? = null, owner: InterfaceAddress? = null, dns: List = this.dns) { try { - val routing = Routing(upstream, downstream, owner) - this.routing = routing this.dns = dns - val strict = app.pref.getBoolean("service.repeater.strict", false) - if (strict && upstream == null) return // in this case, nothing to be done - routing.ipForward() // local only interfaces need not enable ip_forward - .rule().forward(strict) - if (app.masquerade) routing.masquerade(strict) - if (!routing.dnsRedirect(dns).start()) SmartSnackbar.make(R.string.noisy_su_failure).show() + this.routing = Routing(upstream, downstream, owner).apply { + val strict = app.pref.getBoolean("service.repeater.strict", false) + if (strict && upstream == null) return@apply // in this case, nothing to be done + if (app.dhcpWorkaround) dhcpWorkaround() + ipForward() // local only interfaces need to enable ip_forward + rule() + forward(strict) + if (app.masquerade) masquerade(strict) + dnsRedirect(dns) + if (!start()) SmartSnackbar.make(R.string.noisy_su_failure).show() + } } catch (e: SocketException) { SmartSnackbar.make(e.localizedMessage).show() Crashlytics.logException(e) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt index 51584b3d..378d8b7c 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt @@ -47,14 +47,17 @@ class TetheringService : IpNeighbourMonitoringService(), UpstreamMonitor.Callbac var failed = false for ((downstream, value) in routings) if (value == null || value.upstream != upstream) try { - // system tethering already has working forwarding rules - // so it doesn't make sense to add additional forwarding rules - val routing = Routing(upstream, downstream).rule().forward() - if (app.masquerade) routing.masquerade() - routing.dnsRedirect(dns) - if (app.pref.getBoolean("service.disableIpv6", false)) routing.disableIpv6() - routings[downstream] = routing - if (!routing.start()) failed = true + routings[downstream] = Routing(upstream, downstream).apply { + if (app.dhcpWorkaround) dhcpWorkaround() + // system tethering already has working forwarding rules + // so it doesn't make sense to add additional forwarding rules + rule() + forward() + if (app.masquerade) masquerade() + dnsRedirect(dns) + if (app.pref.getBoolean("service.disableIpv6", false)) disableIpv6() + if (!start()) failed = true + } } catch (e: SocketException) { e.printStackTrace() Crashlytics.logException(e) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt index 061909af..d40891b3 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt @@ -27,6 +27,7 @@ class Routing(val upstream: String?, private val downstream: String, ownerAddres "$IPTABLES -t nat -F vpnhotspot_masquerade", "$IPTABLES -t nat -X vpnhotspot_masquerade", "quiet while ip rule del priority 17900; do done", + "quiet while ip rule del iif lo uidrange 0-0 lookup local_network priority 11000; do done", report = false) } @@ -40,15 +41,13 @@ class Routing(val upstream: String?, private val downstream: String, ownerAddres private val stopScript = LinkedList() var started = false - fun ipForward(): Routing { + fun ipForward() { startScript.add("echo 1 >/proc/sys/net/ipv4/ip_forward") - return this } - fun disableIpv6(): Routing { + fun disableIpv6() { startScript.add("echo 1 >/proc/sys/net/ipv6/conf/$downstream/disable_ipv6") stopScript.add("echo 0 >/proc/sys/net/ipv6/conf/$downstream/disable_ipv6") - return this } /** @@ -57,16 +56,15 @@ class Routing(val upstream: String?, private val downstream: String, ownerAddres * * Source: https://android.googlesource.com/platform/system/netd/+/b9baf26/server/RouteController.cpp#65 */ - fun rule(): Routing { + fun rule() { if (upstream != null) { startScript.add("ip rule add from all iif $downstream lookup $upstream priority 17900") // by the time stopScript is called, table entry for upstream may already get removed stopScript.addFirst("ip rule del from all iif $downstream priority 17900") } - return this } - fun forward(strict: Boolean = true): Routing { + fun forward(strict: Boolean = true) { startScript.add("quiet $IPTABLES -N vpnhotspot_fwd 2>/dev/null") if (strict) { check(upstream != null) @@ -84,10 +82,9 @@ class Routing(val upstream: String?, private val downstream: String, ownerAddres } startScript.add("$IPTABLES -I FORWARD -j vpnhotspot_fwd") stopScript.addFirst("$IPTABLES -D FORWARD -j vpnhotspot_fwd") - return this } - fun masquerade(strict: Boolean = true): Routing { + fun masquerade(strict: Boolean = true) { val hostSubnet = "${hostAddress.address.hostAddress}/${hostAddress.networkPrefixLength}" startScript.add("quiet $IPTABLES -t nat -N vpnhotspot_masquerade 2>/dev/null") // note: specifying -i wouldn't work for POSTROUTING @@ -101,10 +98,9 @@ class Routing(val upstream: String?, private val downstream: String, ownerAddres } startScript.add("$IPTABLES -t nat -I POSTROUTING -j vpnhotspot_masquerade") stopScript.addFirst("$IPTABLES -t nat -D POSTROUTING -j vpnhotspot_masquerade") - return this } - fun dnsRedirect(dnses: List): Routing { + fun dnsRedirect(dnses: List) { val hostAddress = hostAddress.address.hostAddress val dns = dnses.firstOrNull { it is Inet4Address }?.hostAddress ?: app.pref.getString("service.dns", "8.8.8.8") debugLog("Routing", "Using $dns from ($dnses)") @@ -112,7 +108,19 @@ class Routing(val upstream: String?, private val downstream: String, ownerAddres startScript.add("$IPTABLES -t nat -A PREROUTING -i $downstream -p udp -d $hostAddress --dport 53 -j DNAT --to-destination $dns") stopScript.addFirst("$IPTABLES -t nat -D PREROUTING -i $downstream -p tcp -d $hostAddress --dport 53 -j DNAT --to-destination $dns") stopScript.addFirst("$IPTABLES -t nat -D PREROUTING -i $downstream -p udp -d $hostAddress --dport 53 -j DNAT --to-destination $dns") - return this + } + + /** + * Similarly, assuming RULE_PRIORITY_VPN_OUTPUT_TO_LOCAL = 11000. + * Normally this is used to forward packets from remote to local, but it works anyways. It just needs to be before + * RULE_PRIORITY_SECURE_VPN = 12000. It would be great if we can gain better understanding into why this is only + * needed on some of the devices but not others. + * + * Source: https://android.googlesource.com/platform/system/netd/+/b9baf26/server/RouteController.cpp#57 + */ + fun dhcpWorkaround() { + startScript.add("ip rule add iif lo uidrange 0-0 lookup local_network priority 11000") + stopScript.addFirst("ip rule del iif lo uidrange 0-0 lookup local_network priority 11000") } fun start(): Boolean { diff --git a/mobile/src/main/res/drawable/ic_action_build.xml b/mobile/src/main/res/drawable/ic_action_build.xml new file mode 100644 index 00000000..fa5477d9 --- /dev/null +++ b/mobile/src/main/res/drawable/ic_action_build.xml @@ -0,0 +1,6 @@ + + + diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml index ac06b49b..30c9daf0 100644 --- a/mobile/src/main/res/values-zh-rCN/strings.xml +++ b/mobile/src/main/res/values-zh-rCN/strings.xml @@ -71,6 +71,8 @@ 自动检测系统 VPN 清理/重新应用路由规则 将修改的设置应用到当前启用的服务上。也可用于修复偶尔会发生的竞态条件。 + 尝试修复 DHCP + 如果设备无法获取 IP 地址,尝试打开这个选项。 杂项 导出调试信息 这种非常有用哇 diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 472f5aa2..0f114396 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -74,6 +74,8 @@ Fallback DNS server[:port] Upstream network interface Auto detect system VPN + Enable DHCP workaround + Use this if clients cannot obtain IP addresses. Clean/reapply routing rules Update changed settings to current active services. Can also fix rare race conditions. diff --git a/mobile/src/main/res/xml/pref_settings.xml b/mobile/src/main/res/xml/pref_settings.xml index 3506dc2e..3158c0c7 100644 --- a/mobile/src/main/res/xml/pref_settings.xml +++ b/mobile/src/main/res/xml/pref_settings.xml @@ -40,6 +40,11 @@ android:summary="@string/settings_service_upstream_auto" android:hint="@string/settings_service_upstream_auto" android:singleLine="true"/> +