From b031ad5ba577e0c15d518e45b513654701c8af6e Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 5 Jan 2018 10:56:24 +0800 Subject: [PATCH] Reapply routing rules automatically when interface up/down changes --- mobile/src/main/AndroidManifest.xml | 1 + .../be/mygod/vpnhotspot/HotspotService.kt | 60 ++++++++++++++++++- .../main/java/be/mygod/vpnhotspot/Routing.kt | 15 ++++- .../main/java/be/mygod/vpnhotspot/Utils.kt | 4 ++ 4 files changed, 74 insertions(+), 6 deletions(-) diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index 699a8570..08e0b449 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ + diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/HotspotService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/HotspotService.kt index 71e17914..3a193503 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/HotspotService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/HotspotService.kt @@ -4,7 +4,7 @@ import android.app.PendingIntent import android.app.Service import android.content.Context import android.content.Intent -import android.net.NetworkInfo +import android.net.* import android.net.wifi.WifiConfiguration import android.net.wifi.WifiManager import android.net.wifi.p2p.WifiP2pGroup @@ -51,6 +51,18 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener { private val isWifiApEnabledMethod = WifiManager::class.java.getDeclaredMethod("isWifiApEnabled") val WifiManager.isWifiApEnabled get() = isWifiApEnabledMethod.invoke(this) as Boolean + private val request by lazy { + /* We don't know how to specify the interface we're interested in, so we will listen for everything. + * However, we need to remove all default capabilities defined in NetworkCapabilities constructor. + * Also this unfortunately doesn't work for P2P/AP connectivity changes. + */ + NetworkRequest.Builder() + .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) + .build() + } + init { isWifiApEnabledMethod.isAccessible = true } @@ -86,6 +98,7 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener { } } + private val connectivityManager by lazy { getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager } private val wifiManager by lazy { getSystemService(Context.WIFI_SERVICE) as WifiManager } private val p2pManager by lazy { getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager } private var _channel: WifiP2pManager.Channel? = null @@ -112,6 +125,38 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener { if (intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 0) != WIFI_AP_STATE_ENABLED) clean() } } + private var netListenerRegistered = false + private val netListener = object : ConnectivityManager.NetworkCallback() { + /** + * Obtaining ifname in onLost doesn't work so we need to cache it in onAvailable. + */ + private val ifnameCache = HashMap() + private val Network.ifname: String? get() { + var result = ifnameCache[this] + if (result == null) { + result = connectivityManager.getLinkProperties(this)?.interfaceName + if (result != null) ifnameCache.put(this, result) + } + return result + } + + override fun onAvailable(network: Network?) { + val routing = routing ?: return + val ifname = network?.ifname + debugLog(TAG, "onAvailable: $ifname") + if (ifname == routing.upstream) routing.start() + } + + override fun onLost(network: Network?) { + val routing = routing ?: return + val ifname = network?.ifname + debugLog(TAG, "onLost: $ifname") + when (ifname) { + routing.downstream -> clean() + routing.upstream -> routing.stop() + } + } + } val ssid get() = when (status) { HotspotService.Status.ACTIVE_P2P -> group.networkName @@ -183,6 +228,8 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener { receiverRegistered = true try { if (initApRouting()) { + connectivityManager.registerNetworkCallback(request, netListener) + netListenerRegistered = true apConfiguration = NetUtils.loadApConfiguration() status = Status.ACTIVE_AP showNotification() @@ -250,8 +297,11 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener { if (!info.groupFormed || !info.isGroupOwner || downstream == null || owner == null) return receiverRegistered = true try { - if (initP2pRouting(downstream, owner)) doStart(group) - else startFailure("Something went wrong, please check logcat.", group) + if (initP2pRouting(downstream, owner)) { + connectivityManager.registerNetworkCallback(request, netListener) + netListenerRegistered = true + doStart(group) + } else startFailure("Something went wrong, please check logcat.", group) } catch (e: Routing.InterfaceNotFoundException) { startFailure(e.message, group) return @@ -285,6 +335,10 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener { }) } private fun unregisterReceiver() { + if (netListenerRegistered) { + connectivityManager.unregisterNetworkCallback(netListener) + netListenerRegistered = false + } if (receiverRegistered) { unregisterReceiver(receiver) receiverRegistered = false diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/Routing.kt b/mobile/src/main/java/be/mygod/vpnhotspot/Routing.kt index d32e3178..46381ffc 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/Routing.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/Routing.kt @@ -7,7 +7,7 @@ import java.net.InetAddress import java.net.NetworkInterface import java.util.* -class Routing(private val upstream: String, val downstream: String, ownerAddress: InetAddress? = null) { +class Routing(val upstream: String, val downstream: String, ownerAddress: InetAddress? = null) { companion object { fun clean() = noisySu( "iptables -t nat -F PREROUTING", @@ -25,6 +25,7 @@ class Routing(private val upstream: String, val downstream: String, ownerAddress ?.singleOrNull { it is Inet4Address } ?: throw InterfaceNotFoundException() private val startScript = LinkedList() private val stopScript = LinkedList() + private var started = false fun ipForward(): Routing { startScript.add("echo 1 >/proc/sys/net/ipv4/ip_forward") @@ -63,6 +64,14 @@ class Routing(private val upstream: String, val downstream: String, ownerAddress return this } - fun start() = noisySu(startScript) - fun stop() = noisySu(stopScript) + fun start(): Boolean { + if (started) return true + started = true + return noisySu(startScript) + } + fun stop(): Boolean { + if (!started) return true + started = false + return noisySu(stopScript) + } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/Utils.kt b/mobile/src/main/java/be/mygod/vpnhotspot/Utils.kt index ea7b7ae3..76156380 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/Utils.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/Utils.kt @@ -8,6 +8,10 @@ import android.os.Bundle import android.util.Log import java.io.InputStream +fun debugLog(tag: String?, message: String?) { + if (BuildConfig.DEBUG) Log.d(tag, message) +} + fun broadcastReceiver(receiver: (Context, Intent) -> Unit) = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) = receiver(context, intent) }