Reapply routing rules automatically when interface up/down changes
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
<uses-feature android:name="android.hardware.wifi"/>
|
||||
<uses-feature android:name="android.hardware.wifi.direct" android:required="false"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
@@ -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<Network, String>()
|
||||
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
|
||||
|
||||
@@ -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<String>()
|
||||
private val stopScript = LinkedList<String>()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user