Reapply routing rules automatically when interface up/down changes

This commit is contained in:
Mygod
2018-01-05 10:56:24 +08:00
parent c37901a2de
commit b031ad5ba5
4 changed files with 74 additions and 6 deletions

View File

@@ -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"/>

View File

@@ -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

View File

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

View File

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