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"/>
|
||||||
<uses-feature android:name="android.hardware.wifi.direct" android:required="false"/>
|
<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.ACCESS_WIFI_STATE"/>
|
||||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import android.app.PendingIntent
|
|||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.NetworkInfo
|
import android.net.*
|
||||||
import android.net.wifi.WifiConfiguration
|
import android.net.wifi.WifiConfiguration
|
||||||
import android.net.wifi.WifiManager
|
import android.net.wifi.WifiManager
|
||||||
import android.net.wifi.p2p.WifiP2pGroup
|
import android.net.wifi.p2p.WifiP2pGroup
|
||||||
@@ -51,6 +51,18 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
|||||||
private val isWifiApEnabledMethod = WifiManager::class.java.getDeclaredMethod("isWifiApEnabled")
|
private val isWifiApEnabledMethod = WifiManager::class.java.getDeclaredMethod("isWifiApEnabled")
|
||||||
val WifiManager.isWifiApEnabled get() = isWifiApEnabledMethod.invoke(this) as Boolean
|
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 {
|
init {
|
||||||
isWifiApEnabledMethod.isAccessible = true
|
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 wifiManager by lazy { getSystemService(Context.WIFI_SERVICE) as WifiManager }
|
||||||
private val p2pManager by lazy { getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager }
|
private val p2pManager by lazy { getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager }
|
||||||
private var _channel: WifiP2pManager.Channel? = null
|
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()
|
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) {
|
val ssid get() = when (status) {
|
||||||
HotspotService.Status.ACTIVE_P2P -> group.networkName
|
HotspotService.Status.ACTIVE_P2P -> group.networkName
|
||||||
@@ -183,6 +228,8 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
|||||||
receiverRegistered = true
|
receiverRegistered = true
|
||||||
try {
|
try {
|
||||||
if (initApRouting()) {
|
if (initApRouting()) {
|
||||||
|
connectivityManager.registerNetworkCallback(request, netListener)
|
||||||
|
netListenerRegistered = true
|
||||||
apConfiguration = NetUtils.loadApConfiguration()
|
apConfiguration = NetUtils.loadApConfiguration()
|
||||||
status = Status.ACTIVE_AP
|
status = Status.ACTIVE_AP
|
||||||
showNotification()
|
showNotification()
|
||||||
@@ -250,8 +297,11 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
|||||||
if (!info.groupFormed || !info.isGroupOwner || downstream == null || owner == null) return
|
if (!info.groupFormed || !info.isGroupOwner || downstream == null || owner == null) return
|
||||||
receiverRegistered = true
|
receiverRegistered = true
|
||||||
try {
|
try {
|
||||||
if (initP2pRouting(downstream, owner)) doStart(group)
|
if (initP2pRouting(downstream, owner)) {
|
||||||
else startFailure("Something went wrong, please check logcat.", group)
|
connectivityManager.registerNetworkCallback(request, netListener)
|
||||||
|
netListenerRegistered = true
|
||||||
|
doStart(group)
|
||||||
|
} else startFailure("Something went wrong, please check logcat.", group)
|
||||||
} catch (e: Routing.InterfaceNotFoundException) {
|
} catch (e: Routing.InterfaceNotFoundException) {
|
||||||
startFailure(e.message, group)
|
startFailure(e.message, group)
|
||||||
return
|
return
|
||||||
@@ -285,6 +335,10 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
private fun unregisterReceiver() {
|
private fun unregisterReceiver() {
|
||||||
|
if (netListenerRegistered) {
|
||||||
|
connectivityManager.unregisterNetworkCallback(netListener)
|
||||||
|
netListenerRegistered = false
|
||||||
|
}
|
||||||
if (receiverRegistered) {
|
if (receiverRegistered) {
|
||||||
unregisterReceiver(receiver)
|
unregisterReceiver(receiver)
|
||||||
receiverRegistered = false
|
receiverRegistered = false
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import java.net.InetAddress
|
|||||||
import java.net.NetworkInterface
|
import java.net.NetworkInterface
|
||||||
import java.util.*
|
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 {
|
companion object {
|
||||||
fun clean() = noisySu(
|
fun clean() = noisySu(
|
||||||
"iptables -t nat -F PREROUTING",
|
"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()
|
?.singleOrNull { it is Inet4Address } ?: throw InterfaceNotFoundException()
|
||||||
private val startScript = LinkedList<String>()
|
private val startScript = LinkedList<String>()
|
||||||
private val stopScript = LinkedList<String>()
|
private val stopScript = LinkedList<String>()
|
||||||
|
private var started = false
|
||||||
|
|
||||||
fun ipForward(): Routing {
|
fun ipForward(): Routing {
|
||||||
startScript.add("echo 1 >/proc/sys/net/ipv4/ip_forward")
|
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
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start() = noisySu(startScript)
|
fun start(): Boolean {
|
||||||
fun stop() = noisySu(stopScript)
|
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 android.util.Log
|
||||||
import java.io.InputStream
|
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() {
|
fun broadcastReceiver(receiver: (Context, Intent) -> Unit) = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) = receiver(context, intent)
|
override fun onReceive(context: Context, intent: Intent) = receiver(context, intent)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user