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`;
* `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 <chain>`);
* `sh`;

View File

@@ -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))

View File

@@ -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()

View File

@@ -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()

View File

@@ -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

View File

@@ -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()