Use /system/bin/ip and ifindex for lookup
This commit is contained in:
@@ -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`;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user