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)
}