Revert "Remove fallback upstream monitor"

This commit reverts 99e721bf7e.

Fixes #119.
This commit is contained in:
Mygod
2019-07-15 23:28:48 +08:00
parent 654c1fc48f
commit 5583e742ca
9 changed files with 107 additions and 34 deletions

View File

@@ -48,7 +48,7 @@ abstract class RoutingManager(private val caller: Any, val downstream: String, p
ipForward() // local only interfaces need to enable ip_forward
forward()
masquerade(masqueradeMode)
commit(true)
commit()
}
}

View File

@@ -11,6 +11,7 @@ import androidx.preference.SwitchPreference
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.Routing.Companion.IPTABLES
import be.mygod.vpnhotspot.net.TetherOffloadManager
import be.mygod.vpnhotspot.net.monitor.FallbackUpstreamMonitor
import be.mygod.vpnhotspot.net.monitor.IpMonitor
import be.mygod.vpnhotspot.net.monitor.UpstreamMonitor
import be.mygod.vpnhotspot.net.wifi.WifiDoubleLock
@@ -115,6 +116,9 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
|echo iptables -nvx -L vpnhotspot_fwd
|$IPTABLES -nvx -L vpnhotspot_fwd
|echo
|echo iptables -nvx -L vpnhotspot_acl
|$IPTABLES -nvx -L vpnhotspot_acl
|echo
|echo logcat-su
|logcat -d
""".trimMargin())
@@ -146,23 +150,24 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
override fun onDisplayPreferenceDialog(preference: Preference) {
when (preference.key) {
UpstreamMonitor.KEY -> AlwaysAutoCompleteEditTextPreferenceDialogFragmentCompat().apply {
setArguments(preference.key, try {
NetworkInterface.getNetworkInterfaces().asSequence()
.filter {
try {
it.isUp && !it.isLoopback && it.interfaceAddresses.isNotEmpty()
} catch (_: SocketException) {
false
UpstreamMonitor.KEY, FallbackUpstreamMonitor.KEY ->
AlwaysAutoCompleteEditTextPreferenceDialogFragmentCompat().apply {
setArguments(preference.key, try {
NetworkInterface.getNetworkInterfaces().asSequence()
.filter {
try {
it.isUp && !it.isLoopback && it.interfaceAddresses.isNotEmpty()
} catch (_: SocketException) {
false
}
}
}
.map { it.name }.sorted().toList().toTypedArray()
} catch (e: SocketException) {
Timber.d(e)
emptyArray<String>()
})
setTargetFragment(this@SettingsPreferenceFragment, 0)
}.show(fragmentManager ?: return, preference.key)
.map { it.name }.sorted().toList().toTypedArray()
} catch (e: SocketException) {
Timber.d(e)
emptyArray<String>()
})
setTargetFragment(this@SettingsPreferenceFragment, 0)
}.show(fragmentManager ?: return, preference.key)
else -> super.onDisplayPreferenceDialog(preference)
}
}

View File

@@ -5,7 +5,7 @@ import android.os.Build
import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.net.monitor.DefaultNetworkMonitor
import be.mygod.vpnhotspot.net.monitor.FallbackUpstreamMonitor
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
import be.mygod.vpnhotspot.net.monitor.TrafficRecorder
import be.mygod.vpnhotspot.net.monitor.UpstreamMonitor
@@ -52,6 +52,8 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh
it.execQuiet("while $IPTABLES -D FORWARD -j vpnhotspot_fwd; do done")
it.execQuiet("$IPTABLES -F vpnhotspot_fwd")
it.execQuiet("$IPTABLES -X vpnhotspot_fwd")
it.execQuiet("$IPTABLES -F vpnhotspot_acl")
it.execQuiet("$IPTABLES -X vpnhotspot_acl")
it.execQuiet("while $IPTABLES -t nat -D POSTROUTING -j vpnhotspot_masquerade; do done")
it.execQuiet("$IPTABLES -t nat -F vpnhotspot_masquerade")
it.execQuiet("$IPTABLES -t nat -X vpnhotspot_masquerade")
@@ -120,17 +122,23 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh
*/
inner class Subrouting(priority: Int, val upstream: String? = null) {
val transaction = RootSession.beginTransaction().safeguard {
if (upstream != null) ipRule("from all", downstream, upstream, priority)
fun simpleMasquerade() {
// note: specifying -i wouldn't work for POSTROUTING
iptablesAdd(if (upstream == null) "vpnhotspot_masquerade -s $hostSubnet -j MASQUERADE" else
"vpnhotspot_masquerade -s $hostSubnet -o $upstream -j MASQUERADE", "nat")
if (upstream != null) {
ipRule("from all", downstream, upstream, priority)
iptablesInsert("vpnhotspot_fwd -i $downstream -o $upstream -j vpnhotspot_acl")
iptablesInsert("vpnhotspot_fwd -i $upstream -o $downstream -m state --state ESTABLISHED,RELATED -j vpnhotspot_acl")
} else {
iptablesInsert("vpnhotspot_fwd -i $downstream -j vpnhotspot_acl")
iptablesInsert("vpnhotspot_fwd -o $downstream -m state --state ESTABLISHED,RELATED -j vpnhotspot_acl")
}
@TargetApi(28) when (masqueradeMode) {
MasqueradeMode.None -> { } // nothing to be done here
MasqueradeMode.Simple -> simpleMasquerade()
// fallback is only needed for repeater on API 23
MasqueradeMode.Netd -> if (upstream == null) simpleMasquerade() else {
MasqueradeMode.Simple -> {
// note: specifying -i wouldn't work for POSTROUTING
iptablesAdd(if (upstream == null) "vpnhotspot_masquerade -s $hostSubnet -j MASQUERADE" else
"vpnhotspot_masquerade -s $hostSubnet -o $upstream -j MASQUERADE", "nat")
}
MasqueradeMode.Netd -> {
check(upstream != null) // fallback is only needed for repeater on API 23 < 28
/**
* 0 means that there are no interface addresses coming after, which is unused anyway.
*
@@ -166,7 +174,7 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh
override fun onLost() = synchronized(this@Routing) {
val subrouting = subrouting ?: return
// we could be removing fallback subrouting which no collision could ever happen, check before removing
if (subrouting.upstream != null) check(upstreams.remove(subrouting.upstream))
subrouting.upstream?.let { check(upstreams.remove(it)) }
subrouting.transaction.revert()
this.subrouting = null
dns = emptyList()
@@ -191,8 +199,8 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh
private inner class Client(private val ip: Inet4Address, mac: Long) : AutoCloseable {
private val transaction = RootSession.beginTransaction().safeguard {
val address = ip.hostAddress
iptablesInsert("vpnhotspot_fwd -i $downstream -s $address -j ACCEPT")
iptablesInsert("vpnhotspot_fwd -o $downstream -d $address -m state --state ESTABLISHED,RELATED -j ACCEPT")
iptablesInsert("vpnhotspot_acl -i $downstream -s $address -j ACCEPT")
iptablesInsert("vpnhotspot_acl -o $downstream -d $address -j ACCEPT")
}
init {
@@ -261,6 +269,7 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh
fun forward() {
transaction.execQuiet("$IPTABLES -N vpnhotspot_fwd")
transaction.execQuiet("$IPTABLES -N vpnhotspot_acl")
transaction.iptablesInsert("FORWARD -j vpnhotspot_fwd")
transaction.iptablesAdd("vpnhotspot_fwd -i $downstream ! -o $downstream -j DROP") // ensure blocking works
// the real forwarding filters will be added in Subrouting when clients are connected
@@ -306,15 +315,14 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh
fun stop() {
IpNeighbourMonitor.unregisterCallback(this)
DefaultNetworkMonitor.unregisterCallback(fallbackUpstream)
FallbackUpstreamMonitor.unregisterCallback(fallbackUpstream)
UpstreamMonitor.unregisterCallback(upstream)
}
fun commit(localOnly: Boolean = false) {
fun commit() {
transaction.commit()
Timber.i("Started routing for $downstream by $caller")
@TargetApi(28)
if (localOnly || masqueradeMode != MasqueradeMode.Netd) DefaultNetworkMonitor.registerCallback(fallbackUpstream)
FallbackUpstreamMonitor.registerCallback(fallbackUpstream)
UpstreamMonitor.registerCallback(upstream)
IpNeighbourMonitor.registerCallback(this)
}

View File

@@ -0,0 +1,41 @@
package be.mygod.vpnhotspot.net.monitor
import android.content.SharedPreferences
import be.mygod.vpnhotspot.App.Companion.app
abstract class FallbackUpstreamMonitor private constructor() : UpstreamMonitor() {
companion object : SharedPreferences.OnSharedPreferenceChangeListener {
const val KEY = "service.upstream.fallback"
init {
app.pref.registerOnSharedPreferenceChangeListener(this)
}
private fun generateMonitor(): UpstreamMonitor {
val upstream = app.pref.getString(KEY, null)
return if (upstream.isNullOrEmpty()) DefaultNetworkMonitor else InterfaceMonitor(upstream)
}
private var monitor = generateMonitor()
fun registerCallback(callback: Callback) = synchronized(this) { monitor.registerCallback(callback) }
fun unregisterCallback(callback: Callback) = synchronized(this) { monitor.unregisterCallback(callback) }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == KEY) synchronized(this) {
val old = monitor
val callbacks = synchronized(old) {
val callbacks = old.callbacks.toList()
old.callbacks.clear()
old.destroyLocked()
callbacks
}
val new = generateMonitor()
monitor = new
for (callback in callbacks) {
callback.onLost()
new.registerCallback(callback)
}
}
}
}
}

View File

@@ -62,7 +62,7 @@ object TrafficRecorder {
private fun doUpdate(timestamp: Long) {
val oldRecords = LongSparseArray<TrafficRecord>()
loop@ for (line in RootSession.use {
val command = "$IPTABLES -nvx -L vpnhotspot_fwd"
val command = "$IPTABLES -nvx -L vpnhotspot_acl"
val result = it.execQuiet(command)
val message = RootSession.checkOutput(command, result, false, false)
if (result.err.isNotEmpty()) Timber.i(message)

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="#FF000000"
android:pathData="M5,2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v4L1,6v6h6L7,6L5,6L5,2zM9,16c0,1.3 0.84,2.4 2,2.82L11,23h2v-4.18c1.16,-0.41 2,-1.51 2,-2.82v-2L9,14v2zM1,16c0,1.3 0.84,2.4 2,2.82L3,23h2v-4.18C6.16,18.4 7,17.3 7,16v-2L1,14v2zM21,6L21,2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v4h-2v6h6L23,6h-2zM13,2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v4L9,6v6h6L15,6h-2L13,2zM17,16c0,1.3 0.84,2.4 2,2.82L19,23h2v-4.18c1.16,-0.41 2,-1.51 2,-2.82v-2h-6v2z"/>
</vector>

View File

@@ -99,6 +99,8 @@
<string name="settings_service_ip_monitor_poll_root">轮询 (root)</string>
<string name="settings_service_upstream">上游网络接口</string>
<string name="settings_service_upstream_auto">自动检测系统 VPN</string>
<string name="settings_upstream_fallback">备用上游接口</string>
<string name="settings_upstream_fallback_auto">自动检测系统默认网络</string>
<string name="settings_service_clean">清理/重新应用路由规则</string>
<string name="settings_service_clean_summary">将修改的设置应用到当前启用的服务上。也可用于修复偶尔会发生的竞态条件。</string>
<string name="settings_service_dhcp_workaround">尝试修复 DHCP</string>

View File

@@ -104,6 +104,8 @@
<string name="settings_service_ip_monitor_poll_root">Poll with root</string>
<string name="settings_service_upstream">Upstream network interface</string>
<string name="settings_service_upstream_auto">Auto detect system VPN</string>
<string name="settings_upstream_fallback">Fallback upstream interface</string>
<string name="settings_upstream_fallback_auto">Auto detect system default network</string>
<string name="settings_service_dhcp_workaround">Enable DHCP workaround</string>
<string name="settings_service_dhcp_workaround_summary">Use this if clients cannot obtain IP addresses.</string>
<string name="settings_service_clean">Clean/reapply routing rules</string>

View File

@@ -12,6 +12,11 @@
app:icon="@drawable/ic_action_settings_ethernet"
app:title="@string/settings_service_upstream"
app:summary="@string/settings_service_upstream_auto"/>
<be.mygod.vpnhotspot.preference.AlwaysAutoCompleteEditTextPreference
app:key="service.upstream.fallback"
app:icon="@drawable/ic_action_settings_input_component"
app:title="@string/settings_upstream_fallback"
app:summary="@string/settings_upstream_fallback_auto"/>
<com.takisoft.preferencex.SimpleMenuPreference
app:key="service.masqueradeMode"
app:icon="@drawable/ic_social_people"