Make DHCP workaround global
This commit is contained in:
@@ -14,6 +14,7 @@ import androidx.core.content.getSystemService
|
|||||||
import androidx.core.provider.FontRequest
|
import androidx.core.provider.FontRequest
|
||||||
import androidx.emoji.text.EmojiCompat
|
import androidx.emoji.text.EmojiCompat
|
||||||
import androidx.emoji.text.FontRequestEmojiCompatConfig
|
import androidx.emoji.text.FontRequestEmojiCompatConfig
|
||||||
|
import be.mygod.vpnhotspot.net.DhcpWorkaround
|
||||||
import be.mygod.vpnhotspot.room.AppDatabase
|
import be.mygod.vpnhotspot.room.AppDatabase
|
||||||
import be.mygod.vpnhotspot.util.DeviceStorageApp
|
import be.mygod.vpnhotspot.util.DeviceStorageApp
|
||||||
import be.mygod.vpnhotspot.util.Event0
|
import be.mygod.vpnhotspot.util.Event0
|
||||||
@@ -48,6 +49,7 @@ class App : Application() {
|
|||||||
override fun onFailed(throwable: Throwable?) = Timber.d(throwable)
|
override fun onFailed(throwable: Throwable?) = Timber.d(throwable)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
if (DhcpWorkaround.shouldEnable) DhcpWorkaround.enable(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onConfigurationChanged(newConfig: Configuration?) {
|
override fun onConfigurationChanged(newConfig: Configuration?) {
|
||||||
@@ -79,7 +81,6 @@ class App : Application() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val masquerade get() = pref.getBoolean("service.masquerade", true)
|
val masquerade get() = pref.getBoolean("service.masquerade", true)
|
||||||
val dhcpWorkaround get() = pref.getBoolean("service.dhcpWorkaround", false)
|
|
||||||
|
|
||||||
val onPreCleanRoutings = Event0()
|
val onPreCleanRoutings = Event0()
|
||||||
val onRoutingsCleaned = Event0()
|
val onRoutingsCleaned = Event0()
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ class LocalOnlyInterfaceManager(val downstream: String) {
|
|||||||
routing = try {
|
routing = try {
|
||||||
Routing(downstream, owner).apply {
|
Routing(downstream, owner).apply {
|
||||||
try {
|
try {
|
||||||
if (app.dhcpWorkaround) dhcpWorkaround()
|
|
||||||
ipForward() // local only interfaces need to enable ip_forward
|
ipForward() // local only interfaces need to enable ip_forward
|
||||||
forward()
|
forward()
|
||||||
if (app.masquerade) masquerade()
|
if (app.masquerade) masquerade()
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ class TetheringService : IpNeighbourMonitoringService() {
|
|||||||
try {
|
try {
|
||||||
routings[downstream] = Routing(downstream).apply {
|
routings[downstream] = Routing(downstream).apply {
|
||||||
try {
|
try {
|
||||||
if (app.dhcpWorkaround) dhcpWorkaround()
|
|
||||||
// system tethering already has working forwarding rules
|
// system tethering already has working forwarding rules
|
||||||
// so it doesn't make sense to add additional forwarding rules
|
// so it doesn't make sense to add additional forwarding rules
|
||||||
forward()
|
forward()
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package be.mygod.vpnhotspot.net
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
|
import be.mygod.vpnhotspot.util.RootSession
|
||||||
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assuming RULE_PRIORITY_VPN_OUTPUT_TO_LOCAL = 11000.
|
||||||
|
* Normally this is used to forward packets from remote to local, but it works anyways.
|
||||||
|
* It just needs to be before RULE_PRIORITY_SECURE_VPN = 12000.
|
||||||
|
* It would be great if we can gain better understanding into why this is only needed on some of the devices but not
|
||||||
|
* others.
|
||||||
|
*
|
||||||
|
* Source: https://android.googlesource.com/platform/system/netd/+/b9baf26/server/RouteController.cpp#57
|
||||||
|
*/
|
||||||
|
object DhcpWorkaround : SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
private const val KEY_ENABLED = "service.dhcpWorkaround"
|
||||||
|
|
||||||
|
init {
|
||||||
|
app.pref.registerOnSharedPreferenceChangeListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
val shouldEnable get() = app.pref.getBoolean(KEY_ENABLED, false)
|
||||||
|
fun enable(enabled: Boolean) {
|
||||||
|
val action = if (enabled) "add" else "del"
|
||||||
|
try {
|
||||||
|
RootSession.use { it.exec("ip rule $action iif lo uidrange 0-0 lookup local_network priority 11000") }
|
||||||
|
} catch (e: RootSession.UnexpectedOutputException) {
|
||||||
|
if (e.result.code == 2 && e.result.out.isEmpty() &&
|
||||||
|
e.result.err.joinToString("\n") == "RTNETLINK answers: File exists") return
|
||||||
|
Timber.w(e)
|
||||||
|
SmartSnackbar.make(e).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||||
|
if (key == KEY_ENABLED) enable(shouldEnable)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,6 @@ import be.mygod.vpnhotspot.widget.SmartSnackbar
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.net.*
|
import java.net.*
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A transaction wrapper that helps set up routing environment.
|
* A transaction wrapper that helps set up routing environment.
|
||||||
@@ -32,8 +31,6 @@ class Routing(val downstream: String, ownerAddress: InterfaceAddress? = null) :
|
|||||||
private const val RULE_PRIORITY_UPSTREAM = 17800
|
private const val RULE_PRIORITY_UPSTREAM = 17800
|
||||||
private const val RULE_PRIORITY_UPSTREAM_FALLBACK = 17900
|
private const val RULE_PRIORITY_UPSTREAM_FALLBACK = 17900
|
||||||
|
|
||||||
private val dhcpWorkaroundCounter = AtomicLong()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* -w <seconds> is not supported on 7.1-.
|
* -w <seconds> is not supported on 7.1-.
|
||||||
* Fortunately there also isn't a time limit for starting a foreground service back in 7.1-.
|
* Fortunately there also isn't a time limit for starting a foreground service back in 7.1-.
|
||||||
@@ -55,7 +52,6 @@ class Routing(val downstream: String, ownerAddress: InterfaceAddress? = null) :
|
|||||||
it.execQuiet("$IPTABLES -t nat -X vpnhotspot_masquerade")
|
it.execQuiet("$IPTABLES -t nat -X vpnhotspot_masquerade")
|
||||||
it.execQuiet("while ip rule del priority $RULE_PRIORITY_UPSTREAM; do done")
|
it.execQuiet("while ip rule del priority $RULE_PRIORITY_UPSTREAM; do done")
|
||||||
it.execQuiet("while ip rule del priority $RULE_PRIORITY_UPSTREAM_FALLBACK; do done")
|
it.execQuiet("while ip rule del priority $RULE_PRIORITY_UPSTREAM_FALLBACK; do done")
|
||||||
it.execQuiet("while ip rule del iif lo uidrange 0-0 lookup local_network priority 11000; do done")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,22 +249,6 @@ class Routing(val downstream: String, ownerAddress: InterfaceAddress? = null) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Similarly, assuming RULE_PRIORITY_VPN_OUTPUT_TO_LOCAL = 11000.
|
|
||||||
* Normally this is used to forward packets from remote to local, but it works anyways. It just needs to be before
|
|
||||||
* RULE_PRIORITY_SECURE_VPN = 12000. It would be great if we can gain better understanding into why this is only
|
|
||||||
* needed on some of the devices but not others.
|
|
||||||
*
|
|
||||||
* Source: https://android.googlesource.com/platform/system/netd/+/b9baf26/server/RouteController.cpp#57
|
|
||||||
*/
|
|
||||||
fun dhcpWorkaround() {
|
|
||||||
// workaround for adding multiple exact same rules
|
|
||||||
// if somebody decides to do this 1000 times to break this, god bless you
|
|
||||||
val priority = 11000 + dhcpWorkaroundCounter.getAndAdd(1) % 1000
|
|
||||||
transaction.exec("ip rule add iif lo uidrange 0-0 lookup local_network priority $priority",
|
|
||||||
"ip rule del iif lo uidrange 0-0 lookup local_network priority $priority")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
IpNeighbourMonitor.unregisterCallback(this)
|
IpNeighbourMonitor.unregisterCallback(this)
|
||||||
FallbackUpstreamMonitor.unregisterCallback(fallbackUpstream)
|
FallbackUpstreamMonitor.unregisterCallback(fallbackUpstream)
|
||||||
|
|||||||
@@ -60,12 +60,12 @@ class RootSession : AutoCloseable {
|
|||||||
val msg = StringBuilder("$command exited with ${result.code}")
|
val msg = StringBuilder("$command exited with ${result.code}")
|
||||||
if (out) result.out.forEach { msg.append("\n$it") }
|
if (out) result.out.forEach { msg.append("\n$it") }
|
||||||
if (err) result.err.forEach { msg.append("\nE $it") }
|
if (err) result.err.forEach { msg.append("\nE $it") }
|
||||||
if (!result.isSuccess || out || err) throw UnexpectedOutputException(msg.toString())
|
if (!result.isSuccess || out || err) throw UnexpectedOutputException(msg.toString(), result)
|
||||||
return msg.toString()
|
return msg.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UnexpectedOutputException(msg: String) : RuntimeException(msg)
|
class UnexpectedOutputException(msg: String, val result: Shell.Result) : RuntimeException(msg)
|
||||||
|
|
||||||
private val shell = Shell.newInstance("su")
|
private val shell = Shell.newInstance("su")
|
||||||
private val stdout = ArrayList<String>()
|
private val stdout = ArrayList<String>()
|
||||||
|
|||||||
Reference in New Issue
Block a user