diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/App.kt b/mobile/src/main/java/be/mygod/vpnhotspot/App.kt index 75de33ce..8eb2aea8 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/App.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/App.kt @@ -14,6 +14,7 @@ import androidx.core.content.getSystemService import androidx.core.provider.FontRequest import androidx.emoji.text.EmojiCompat import androidx.emoji.text.FontRequestEmojiCompatConfig +import be.mygod.vpnhotspot.net.DhcpWorkaround import be.mygod.vpnhotspot.room.AppDatabase import be.mygod.vpnhotspot.util.DeviceStorageApp import be.mygod.vpnhotspot.util.Event0 @@ -48,6 +49,7 @@ class App : Application() { override fun onFailed(throwable: Throwable?) = Timber.d(throwable) }) }) + if (DhcpWorkaround.shouldEnable) DhcpWorkaround.enable(true) } override fun onConfigurationChanged(newConfig: Configuration?) { @@ -79,7 +81,6 @@ class App : Application() { } val masquerade get() = pref.getBoolean("service.masquerade", true) - val dhcpWorkaround get() = pref.getBoolean("service.dhcpWorkaround", false) val onPreCleanRoutings = Event0() val onRoutingsCleaned = Event0() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyInterfaceManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyInterfaceManager.kt index 96b36dcc..f89b32c5 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyInterfaceManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyInterfaceManager.kt @@ -23,7 +23,6 @@ class LocalOnlyInterfaceManager(val downstream: String) { routing = try { Routing(downstream, owner).apply { try { - if (app.dhcpWorkaround) dhcpWorkaround() ipForward() // local only interfaces need to enable ip_forward forward() if (app.masquerade) masquerade() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt index ceddb924..07a79517 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt @@ -69,7 +69,6 @@ class TetheringService : IpNeighbourMonitoringService() { try { routings[downstream] = Routing(downstream).apply { try { - if (app.dhcpWorkaround) dhcpWorkaround() // system tethering already has working forwarding rules // so it doesn't make sense to add additional forwarding rules forward() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/DhcpWorkaround.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/DhcpWorkaround.kt new file mode 100644 index 00000000..a56a401b --- /dev/null +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/DhcpWorkaround.kt @@ -0,0 +1,41 @@ +package be.mygod.vpnhotspot.net + +import android.content.SharedPreferences +import be.mygod.vpnhotspot.App.Companion.app +import be.mygod.vpnhotspot.util.RootSession +import be.mygod.vpnhotspot.widget.SmartSnackbar +import timber.log.Timber + +/** + * 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 + */ +object DhcpWorkaround : SharedPreferences.OnSharedPreferenceChangeListener { + private const val KEY_ENABLED = "service.dhcpWorkaround" + + init { + app.pref.registerOnSharedPreferenceChangeListener(this) + } + + val shouldEnable get() = app.pref.getBoolean(KEY_ENABLED, false) + fun enable(enabled: Boolean) { + val action = if (enabled) "add" else "del" + try { + RootSession.use { it.exec("ip rule $action iif lo uidrange 0-0 lookup local_network priority 11000") } + } catch (e: RootSession.UnexpectedOutputException) { + if (e.result.code == 2 && e.result.out.isEmpty() && + e.result.err.joinToString("\n") == "RTNETLINK answers: File exists") return + Timber.w(e) + SmartSnackbar.make(e).show() + } + } + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + if (key == KEY_ENABLED) enable(shouldEnable) + } +} 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 6c0e1efe..2e3968f2 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt @@ -14,7 +14,6 @@ import be.mygod.vpnhotspot.widget.SmartSnackbar import kotlinx.coroutines.runBlocking import timber.log.Timber import java.net.* -import java.util.concurrent.atomic.AtomicLong /** * A transaction wrapper that helps set up routing environment. @@ -32,8 +31,6 @@ class Routing(val downstream: String, ownerAddress: InterfaceAddress? = null) : private const val RULE_PRIORITY_UPSTREAM = 17800 private const val RULE_PRIORITY_UPSTREAM_FALLBACK = 17900 - private val dhcpWorkaroundCounter = AtomicLong() - /** * -w is not supported on 7.1-. * Fortunately there also isn't a time limit for starting a foreground service back in 7.1-. @@ -55,7 +52,6 @@ class Routing(val downstream: String, ownerAddress: InterfaceAddress? = null) : it.execQuiet("$IPTABLES -t nat -X vpnhotspot_masquerade") it.execQuiet("while ip rule del priority $RULE_PRIORITY_UPSTREAM; do done") it.execQuiet("while ip rule del priority $RULE_PRIORITY_UPSTREAM_FALLBACK; do done") - it.execQuiet("while ip rule del iif lo uidrange 0-0 lookup local_network priority 11000; do done") } } @@ -253,22 +249,6 @@ class Routing(val downstream: String, ownerAddress: InterfaceAddress? = null) : } } - /** - * 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() { - // workaround for adding multiple exact same rules - // if somebody decides to do this 1000 times to break this, god bless you - val priority = 11000 + dhcpWorkaroundCounter.getAndAdd(1) % 1000 - transaction.exec("ip rule add iif lo uidrange 0-0 lookup local_network priority $priority", - "ip rule del iif lo uidrange 0-0 lookup local_network priority $priority") - } - fun stop() { IpNeighbourMonitor.unregisterCallback(this) FallbackUpstreamMonitor.unregisterCallback(fallbackUpstream) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/RootSession.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/RootSession.kt index d4f39f51..c99111e3 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/RootSession.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/RootSession.kt @@ -60,12 +60,12 @@ class RootSession : AutoCloseable { val msg = StringBuilder("$command exited with ${result.code}") if (out) result.out.forEach { msg.append("\n$it") } if (err) result.err.forEach { msg.append("\nE $it") } - if (!result.isSuccess || out || err) throw UnexpectedOutputException(msg.toString()) + if (!result.isSuccess || out || err) throw UnexpectedOutputException(msg.toString(), result) return msg.toString() } } - class UnexpectedOutputException(msg: String) : RuntimeException(msg) + class UnexpectedOutputException(msg: String, val result: Shell.Result) : RuntimeException(msg) private val shell = Shell.newInstance("su") private val stdout = ArrayList()