Use /system/bin/ip and ifindex for lookup

This commit is contained in:
Mygod
2020-07-11 06:29:20 +08:00
parent e227353134
commit 339871f4b0
6 changed files with 45 additions and 30 deletions

View File

@@ -304,7 +304,7 @@ Undocumented system binaries are all bundled and executable:
* (since API 24) `iptables-save`, `ip6tables-save`; * (since API 24) `iptables-save`, `ip6tables-save`;
* `echo`; * `echo`;
* `ip` (`link monitor neigh rule`); * `/system/bin/ip` (`link monitor neigh rule unreachable`);
* `ndc` (`ipfwd` since API 23, `nat` since API 28); * `ndc` (`ipfwd` since API 23, `nat` since API 28);
* `iptables`, `ip6tables` (with correct version corresponding to API level, `-nvx -L <chain>`); * `iptables`, `ip6tables` (with correct version corresponding to API level, `-nvx -L <chain>`);
* `sh`; * `sh`;

View File

@@ -2,6 +2,7 @@ package be.mygod.vpnhotspot.net
import android.content.SharedPreferences import android.content.SharedPreferences
import be.mygod.vpnhotspot.App.Companion.app 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.root.RoutingCommands
import be.mygod.vpnhotspot.util.RootSession import be.mygod.vpnhotspot.util.RootSession
import be.mygod.vpnhotspot.widget.SmartSnackbar import be.mygod.vpnhotspot.widget.SmartSnackbar
@@ -32,7 +33,7 @@ object DhcpWorkaround : SharedPreferences.OnSharedPreferenceChangeListener {
try { try {
RootSession.use { RootSession.use {
try { 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) { } catch (e: RoutingCommands.UnexpectedOutputException) {
if (Routing.shouldSuppressIpError(e, enabled)) return@use if (Routing.shouldSuppressIpError(e, enabled)) return@use
Timber.w(IOException("Failed to tweak dhcp workaround rule", e)) Timber.w(IOException("Failed to tweak dhcp workaround rule", e))

View File

@@ -14,6 +14,8 @@ import be.mygod.vpnhotspot.room.AppDatabase
import be.mygod.vpnhotspot.root.RootManager import be.mygod.vpnhotspot.root.RootManager
import be.mygod.vpnhotspot.root.RoutingCommands import be.mygod.vpnhotspot.root.RoutingCommands
import be.mygod.vpnhotspot.util.RootSession 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 be.mygod.vpnhotspot.widget.SmartSnackbar
import timber.log.Timber import timber.log.Timber
import java.io.BufferedWriter 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_FALLBACK = 17900
private const val RULE_PRIORITY_UPSTREAM_DISABLE_SYSTEM = 17980 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 IPTABLES = "iptables -w"
const val IP6TABLES = "ip6tables -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("while $IP6TABLES -D OUTPUT -j vpnhotspot_filter; do done")
commands.appendln("$IP6TABLES -F vpnhotspot_filter") commands.appendln("$IP6TABLES -F vpnhotspot_filter")
commands.appendln("$IP6TABLES -X 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_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; 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_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_UPSTREAM_DISABLE_SYSTEM; do done")
} }
suspend fun clean() { suspend fun clean() {
@@ -99,17 +103,17 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh
} == e.result.err.trim() } == 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 { try {
exec("ip rule add $rule iif $downstream $add priority $priority", exec("$IP rule add $rule iif $downstream $action priority $priority",
"ip rule del $rule iif $downstream $del priority $priority") "$IP rule del $rule iif $downstream $action priority $priority")
} catch (e: RoutingCommands.UnexpectedOutputException) { } catch (e: RoutingCommands.UnexpectedOutputException) {
if (!shouldSuppressIpError(e)) throw e if (!shouldSuppressIpError(e)) throw e
} }
} }
private fun RootSession.Transaction.ipRuleLookup(upstream: String, priority: Int, rule: String = "") = private fun RootSession.Transaction.ipRuleLookup(ifindex: Int, priority: Int, rule: String = "") =
// by the time stopScript is called, table entry for upstream may already get removed // https://android.googlesource.com/platform/system/netd/+/android-5.0.0_r1/server/RouteController.h#37
ipRule("lookup $upstream", priority, rule, "") ipRule("lookup ${1000 + ifindex}", priority, rule)
enum class MasqueradeMode { enum class MasqueradeMode {
None, 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. * 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) { 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 { val transaction = RootSession.beginTransaction().safeguard {
if (upstream != null) ipRuleLookup(upstream, priority) if (upstream != null) ipRuleLookup(ifindex, priority)
@TargetApi(28) when (masqueradeMode) { @TargetApi(28) when (masqueradeMode) {
MasqueradeMode.None -> { } // nothing to be done here MasqueradeMode.None -> { } // nothing to be done here
MasqueradeMode.Simple -> { MasqueradeMode.Simple -> {
@@ -216,6 +221,7 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh
Timber.w(e) Timber.w(e)
null null
} }
dns = listOf(parseNumericAddress("8.8.8.8"))
updateDnsRoute() 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 transaction = RootSession.beginTransaction().safeguard {
val hostAddress = hostAddress.address.hostAddress 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 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") 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 var currentDns: DnsRoute? = null
private fun updateDnsRoute() { private fun updateDnsRoute() {
val selected = sequenceOf(upstream, fallbackUpstream).flatMap { upstream -> val selected = sequenceOf(upstream, fallbackUpstream).flatMap { upstream ->
val ifname = upstream.subrouting?.upstream val ifindex = upstream.subrouting?.ifindex ?: 0
if (ifname == null) emptySequence() else upstream.dns.asSequence().map { ifname to it } if (ifindex == 0) emptySequence() else upstream.dns.asSequence().map { ifindex to it }
}.firstOrNull { it.second is Inet4Address } }.firstOrNull { it.second is Inet4Address }
val upstream = selected?.first val ifindex = selected?.first ?: 0
var dns = selected?.second?.hostAddress var dns = selected?.second?.hostAddress
if (dns.isNullOrBlank()) dns = null if (dns.isNullOrBlank()) dns = null
if (upstream != currentDns?.upstream || dns != currentDns?.dns) { if (ifindex != currentDns?.ifindex || dns != currentDns?.dns) {
currentDns?.transaction?.revert() currentDns?.transaction?.revert()
currentDns = if (upstream == null || dns == null) null else try { currentDns = if (ifindex == 0 || dns == null) null else try {
DnsRoute(upstream, dns) DnsRoute(ifindex, dns)
} catch (e: RuntimeException) { } catch (e: RuntimeException) {
Timber.w(e) Timber.w(e)
SmartSnackbar.make(e).show() SmartSnackbar.make(e).show()

View File

@@ -8,6 +8,7 @@ import be.mygod.librootkotlinx.RootServer
import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.BuildConfig import be.mygod.vpnhotspot.BuildConfig
import be.mygod.vpnhotspot.R import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.net.Routing
import be.mygod.vpnhotspot.root.ProcessData import be.mygod.vpnhotspot.root.ProcessData
import be.mygod.vpnhotspot.root.ProcessListener import be.mygod.vpnhotspot.root.ProcessListener
import be.mygod.vpnhotspot.root.RootManager import be.mygod.vpnhotspot.root.RootManager
@@ -104,14 +105,14 @@ abstract class IpMonitor {
if (mode.isMonitor) { if (mode.isMonitor) {
if (mode != Mode.MonitorRoot) { if (mode != Mode.MonitorRoot) {
// monitor may get rejected by SELinux enforcing // monitor may get rejected by SELinux enforcing
handleProcess(ProcessBuilder("ip", "monitor", monitoredObject)) handleProcess(ProcessBuilder(Routing.IP, "monitor", monitoredObject))
if (destroyed) return@thread if (destroyed) return@thread
} }
try { try {
runBlocking(EmptyCoroutineContext + worker) { runBlocking(EmptyCoroutineContext + worker) {
RootManager.use { server -> RootManager.use { server ->
// while we only need to use this server once, we need to also keep the server alive // 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)) this))
} }
} }
@@ -152,7 +153,7 @@ abstract class IpMonitor {
} }
var newServer = server var newServer = server
try { try {
val command = listOf("ip", monitoredObject) val command = listOf(Routing.IP, monitoredObject)
val result = (server ?: RootManager.acquire().also { newServer = it }) val result = (server ?: RootManager.acquire().also { newServer = it })
.execute(RoutingCommands.Process(command)) .execute(RoutingCommands.Process(command))
result.check(command, false) result.check(command, false)
@@ -167,7 +168,7 @@ abstract class IpMonitor {
} }
private fun poll() { private fun poll() {
val process = ProcessBuilder("ip", monitoredObject) val process = ProcessBuilder(Routing.IP, monitoredObject)
.redirectErrorStream(true) .redirectErrorStream(true)
.start() .start()
process.waitFor() process.waitFor()

View File

@@ -8,7 +8,8 @@ import android.provider.Settings
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import be.mygod.librootkotlinx.* import be.mygod.librootkotlinx.*
import be.mygod.vpnhotspot.App.Companion.app 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.net.TetheringManager
import be.mygod.vpnhotspot.util.Services import be.mygod.vpnhotspot.util.Services
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
@@ -50,16 +51,16 @@ class Dump(val path: String, val cacheDir: File = app.deviceStorage.codeCacheDir
|$ip6tablesSave |$ip6tablesSave
|echo |echo
|echo ip rule |echo ip rule
|ip rule |$IP rule
|echo |echo
|echo ip neigh |echo ip neigh
|ip neigh |$IP neigh
|echo |echo
|echo iptables -nvx -L vpnhotspot_fwd |echo iptables -nvx -L vpnhotspot_fwd
|${Routing.IPTABLES} -nvx -L vpnhotspot_fwd |$IPTABLES -nvx -L vpnhotspot_fwd
|echo |echo
|echo iptables -nvx -L vpnhotspot_acl |echo iptables -nvx -L vpnhotspot_acl
|${Routing.IPTABLES} -nvx -L vpnhotspot_acl |$IPTABLES -nvx -L vpnhotspot_acl
|echo |echo
|echo logcat-su |echo logcat-su
|logcat -d |logcat -d

View File

@@ -7,6 +7,7 @@ import android.net.InetAddresses
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.RemoteException import android.os.RemoteException
import android.system.Os
import android.text.Spannable import android.text.Spannable
import android.text.SpannableString import android.text.SpannableString
import android.text.SpannableStringBuilder 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.net.MacAddressCompat
import be.mygod.vpnhotspot.widget.SmartSnackbar import be.mygod.vpnhotspot.widget.SmartSnackbar
import timber.log.Timber import timber.log.Timber
import java.io.File
import java.lang.invoke.MethodHandles import java.lang.invoke.MethodHandles
import java.lang.reflect.InvocationHandler import java.lang.reflect.InvocationHandler
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
@@ -150,3 +152,7 @@ fun InvocationHandler.callSuper(interfaceClass: Class<*>, proxy: Any, method: Me
null 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()