From 339871f4b06935e8061eac4f109b5b32d0b0b3b7 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 11 Jul 2020 06:29:20 +0800 Subject: [PATCH] Use /system/bin/ip and ifindex for lookup --- README.md | 2 +- .../be/mygod/vpnhotspot/net/DhcpWorkaround.kt | 3 +- .../java/be/mygod/vpnhotspot/net/Routing.kt | 44 +++++++++++-------- .../mygod/vpnhotspot/net/monitor/IpMonitor.kt | 9 ++-- .../be/mygod/vpnhotspot/root/MiscCommands.kt | 11 ++--- .../java/be/mygod/vpnhotspot/util/Utils.kt | 6 +++ 6 files changed, 45 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 88773e12..eac1c2b1 100644 --- a/README.md +++ b/README.md @@ -304,7 +304,7 @@ Undocumented system binaries are all bundled and executable: * (since API 24) `iptables-save`, `ip6tables-save`; * `echo`; -* `ip` (`link monitor neigh rule`); +* `/system/bin/ip` (`link monitor neigh rule unreachable`); * `ndc` (`ipfwd` since API 23, `nat` since API 28); * `iptables`, `ip6tables` (with correct version corresponding to API level, `-nvx -L `); * `sh`; diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/DhcpWorkaround.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/DhcpWorkaround.kt index b32158e3..50a62c8b 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/DhcpWorkaround.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/DhcpWorkaround.kt @@ -2,6 +2,7 @@ package be.mygod.vpnhotspot.net import android.content.SharedPreferences import be.mygod.vpnhotspot.App.Companion.app +import be.mygod.vpnhotspot.net.Routing.Companion.IP import be.mygod.vpnhotspot.root.RoutingCommands import be.mygod.vpnhotspot.util.RootSession import be.mygod.vpnhotspot.widget.SmartSnackbar @@ -32,7 +33,7 @@ object DhcpWorkaround : SharedPreferences.OnSharedPreferenceChangeListener { try { RootSession.use { try { - it.exec("ip rule $action iif lo uidrange 0-0 lookup local_network priority 11000") + it.exec("$IP rule $action iif lo uidrange 0-0 lookup local_network priority 11000") } catch (e: RoutingCommands.UnexpectedOutputException) { if (Routing.shouldSuppressIpError(e, enabled)) return@use Timber.w(IOException("Failed to tweak dhcp workaround rule", 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 7c224b43..1bbfec1e 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt @@ -14,6 +14,8 @@ import be.mygod.vpnhotspot.room.AppDatabase import be.mygod.vpnhotspot.root.RootManager import be.mygod.vpnhotspot.root.RoutingCommands import be.mygod.vpnhotspot.util.RootSession +import be.mygod.vpnhotspot.util.if_nametoindex +import be.mygod.vpnhotspot.util.parseNumericAddress import be.mygod.vpnhotspot.widget.SmartSnackbar import timber.log.Timber import java.io.BufferedWriter @@ -43,6 +45,8 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh private const val RULE_PRIORITY_UPSTREAM_FALLBACK = 17900 private const val RULE_PRIORITY_UPSTREAM_DISABLE_SYSTEM = 17980 + private const val ROOT_DIR = "/system/bin/" + const val IP = "${ROOT_DIR}ip" const val IPTABLES = "iptables -w" const val IP6TABLES = "ip6tables -w" @@ -61,10 +65,10 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh commands.appendln("while $IP6TABLES -D OUTPUT -j vpnhotspot_filter; do done") commands.appendln("$IP6TABLES -F vpnhotspot_filter") commands.appendln("$IP6TABLES -X vpnhotspot_filter") - commands.appendln("while ip rule del priority $RULE_PRIORITY_DNS; do done") - commands.appendln("while ip rule del priority $RULE_PRIORITY_UPSTREAM; do done") - commands.appendln("while ip rule del priority $RULE_PRIORITY_UPSTREAM_FALLBACK; do done") - commands.appendln("while ip rule del priority $RULE_PRIORITY_UPSTREAM_DISABLE_SYSTEM; do done") + commands.appendln("while $IP rule del priority $RULE_PRIORITY_DNS; do done") + commands.appendln("while $IP rule del priority $RULE_PRIORITY_UPSTREAM; do done") + commands.appendln("while $IP rule del priority $RULE_PRIORITY_UPSTREAM_FALLBACK; do done") + commands.appendln("while $IP rule del priority $RULE_PRIORITY_UPSTREAM_DISABLE_SYSTEM; do done") } suspend fun clean() { @@ -99,17 +103,17 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh } == e.result.err.trim() } - private fun RootSession.Transaction.ipRule(add: String, priority: Int, rule: String = "", del: String = add) { + private fun RootSession.Transaction.ipRule(action: String, priority: Int, rule: String = "") { try { - exec("ip rule add $rule iif $downstream $add priority $priority", - "ip rule del $rule iif $downstream $del priority $priority") + exec("$IP rule add $rule iif $downstream $action priority $priority", + "$IP rule del $rule iif $downstream $action priority $priority") } catch (e: RoutingCommands.UnexpectedOutputException) { if (!shouldSuppressIpError(e)) throw e } } - private fun RootSession.Transaction.ipRuleLookup(upstream: String, priority: Int, rule: String = "") = - // by the time stopScript is called, table entry for upstream may already get removed - ipRule("lookup $upstream", priority, rule, "") + private fun RootSession.Transaction.ipRuleLookup(ifindex: Int, priority: Int, rule: String = "") = + // https://android.googlesource.com/platform/system/netd/+/android-5.0.0_r1/server/RouteController.h#37 + ipRule("lookup ${1000 + ifindex}", priority, rule) enum class MasqueradeMode { None, @@ -148,8 +152,9 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh * The only case when upstream is null is on API 23- and we are using system default rules. */ inner class Subrouting(priority: Int, val upstream: String? = null) { + val ifindex = if (upstream == null) 0 else if_nametoindex(upstream).also { check(it != 0) } val transaction = RootSession.beginTransaction().safeguard { - if (upstream != null) ipRuleLookup(upstream, priority) + if (upstream != null) ipRuleLookup(ifindex, priority) @TargetApi(28) when (masqueradeMode) { MasqueradeMode.None -> { } // nothing to be done here MasqueradeMode.Simple -> { @@ -216,6 +221,7 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh Timber.w(e) null } + dns = listOf(parseNumericAddress("8.8.8.8")) updateDnsRoute() } } @@ -313,10 +319,10 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh } } - private inner class DnsRoute(val upstream: String, val dns: String) { + private inner class DnsRoute(val ifindex: Int, val dns: String) { val transaction = RootSession.beginTransaction().safeguard { val hostAddress = hostAddress.address.hostAddress - ipRuleLookup(upstream, RULE_PRIORITY_DNS, "to $dns") + ipRuleLookup(ifindex, RULE_PRIORITY_DNS, "to $dns") iptablesAdd("PREROUTING -i $downstream -p tcp -d $hostAddress --dport 53 -j DNAT --to-destination $dns", "nat") iptablesAdd("PREROUTING -i $downstream -p udp -d $hostAddress --dport 53 -j DNAT --to-destination $dns", "nat") } @@ -324,16 +330,16 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh private var currentDns: DnsRoute? = null private fun updateDnsRoute() { val selected = sequenceOf(upstream, fallbackUpstream).flatMap { upstream -> - val ifname = upstream.subrouting?.upstream - if (ifname == null) emptySequence() else upstream.dns.asSequence().map { ifname to it } + val ifindex = upstream.subrouting?.ifindex ?: 0 + if (ifindex == 0) emptySequence() else upstream.dns.asSequence().map { ifindex to it } }.firstOrNull { it.second is Inet4Address } - val upstream = selected?.first + val ifindex = selected?.first ?: 0 var dns = selected?.second?.hostAddress if (dns.isNullOrBlank()) dns = null - if (upstream != currentDns?.upstream || dns != currentDns?.dns) { + if (ifindex != currentDns?.ifindex || dns != currentDns?.dns) { currentDns?.transaction?.revert() - currentDns = if (upstream == null || dns == null) null else try { - DnsRoute(upstream, dns) + currentDns = if (ifindex == 0 || dns == null) null else try { + DnsRoute(ifindex, dns) } catch (e: RuntimeException) { Timber.w(e) SmartSnackbar.make(e).show() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/IpMonitor.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/IpMonitor.kt index e4d5b187..a57cce97 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/IpMonitor.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/IpMonitor.kt @@ -8,6 +8,7 @@ import be.mygod.librootkotlinx.RootServer import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.BuildConfig import be.mygod.vpnhotspot.R +import be.mygod.vpnhotspot.net.Routing import be.mygod.vpnhotspot.root.ProcessData import be.mygod.vpnhotspot.root.ProcessListener import be.mygod.vpnhotspot.root.RootManager @@ -104,14 +105,14 @@ abstract class IpMonitor { if (mode.isMonitor) { if (mode != Mode.MonitorRoot) { // monitor may get rejected by SELinux enforcing - handleProcess(ProcessBuilder("ip", "monitor", monitoredObject)) + handleProcess(ProcessBuilder(Routing.IP, "monitor", monitoredObject)) if (destroyed) return@thread } try { runBlocking(EmptyCoroutineContext + worker) { RootManager.use { server -> // while we only need to use this server once, we need to also keep the server alive - handleChannel(server.create(ProcessListener(errorMatcher, "ip", "monitor", monitoredObject), + handleChannel(server.create(ProcessListener(errorMatcher, Routing.IP, "monitor", monitoredObject), this)) } } @@ -152,7 +153,7 @@ abstract class IpMonitor { } var newServer = server try { - val command = listOf("ip", monitoredObject) + val command = listOf(Routing.IP, monitoredObject) val result = (server ?: RootManager.acquire().also { newServer = it }) .execute(RoutingCommands.Process(command)) result.check(command, false) @@ -167,7 +168,7 @@ abstract class IpMonitor { } private fun poll() { - val process = ProcessBuilder("ip", monitoredObject) + val process = ProcessBuilder(Routing.IP, monitoredObject) .redirectErrorStream(true) .start() process.waitFor() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/root/MiscCommands.kt b/mobile/src/main/java/be/mygod/vpnhotspot/root/MiscCommands.kt index c3339e2c..e2906cf6 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/root/MiscCommands.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/root/MiscCommands.kt @@ -8,7 +8,8 @@ import android.provider.Settings import androidx.annotation.RequiresApi import be.mygod.librootkotlinx.* import be.mygod.vpnhotspot.App.Companion.app -import be.mygod.vpnhotspot.net.Routing +import be.mygod.vpnhotspot.net.Routing.Companion.IP +import be.mygod.vpnhotspot.net.Routing.Companion.IPTABLES import be.mygod.vpnhotspot.net.TetheringManager import be.mygod.vpnhotspot.util.Services import kotlinx.android.parcel.Parcelize @@ -50,16 +51,16 @@ class Dump(val path: String, val cacheDir: File = app.deviceStorage.codeCacheDir |$ip6tablesSave |echo |echo ip rule - |ip rule + |$IP rule |echo |echo ip neigh - |ip neigh + |$IP neigh |echo |echo iptables -nvx -L vpnhotspot_fwd - |${Routing.IPTABLES} -nvx -L vpnhotspot_fwd + |$IPTABLES -nvx -L vpnhotspot_fwd |echo |echo iptables -nvx -L vpnhotspot_acl - |${Routing.IPTABLES} -nvx -L vpnhotspot_acl + |$IPTABLES -nvx -L vpnhotspot_acl |echo |echo logcat-su |logcat -d diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt index b31bc4b8..1291ca86 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt @@ -7,6 +7,7 @@ import android.net.InetAddresses import android.os.Build import android.os.Handler import android.os.RemoteException +import android.system.Os import android.text.Spannable import android.text.SpannableString import android.text.SpannableStringBuilder @@ -24,6 +25,7 @@ import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.net.MacAddressCompat import be.mygod.vpnhotspot.widget.SmartSnackbar import timber.log.Timber +import java.io.File import java.lang.invoke.MethodHandles import java.lang.reflect.InvocationHandler import java.lang.reflect.InvocationTargetException @@ -150,3 +152,7 @@ fun InvocationHandler.callSuper(interfaceClass: Class<*>, proxy: Any, method: Me null } } + +fun if_nametoindex(ifname: String) = if (Build.VERSION.SDK_INT >= 26) { + Os.if_nametoindex(ifname) +} else File("/sys/class/net/$ifname/ifindex").inputStream().bufferedReader().readText().toInt()