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`;
|
* (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`;
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user