Group net package
This commit is contained in:
38
mobile/src/main/java/be/mygod/vpnhotspot/net/NetUtils.kt
Normal file
38
mobile/src/main/java/be/mygod/vpnhotspot/net/NetUtils.kt
Normal file
@@ -0,0 +1,38 @@
|
||||
package be.mygod.vpnhotspot.net
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.support.annotation.RequiresApi
|
||||
import java.io.File
|
||||
|
||||
object NetUtils {
|
||||
// hidden constants from ConnectivityManager
|
||||
/**
|
||||
* This is a sticky broadcast since almost forever.
|
||||
*
|
||||
* https://android.googlesource.com/platform/frameworks/base.git/+/2a091d7aa0c174986387e5d56bf97a87fe075bdb%5E%21/services/java/com/android/server/connectivity/Tethering.java
|
||||
*/
|
||||
const val ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED"
|
||||
@Deprecated("No longer used on Android 8+ (API 26+)")
|
||||
private const val EXTRA_ACTIVE_TETHER_LEGACY = "activeArray"
|
||||
@RequiresApi(26)
|
||||
private const val EXTRA_ACTIVE_LOCAL_ONLY = "localOnlyArray"
|
||||
@RequiresApi(26)
|
||||
private const val EXTRA_ACTIVE_TETHER = "tetherArray"
|
||||
|
||||
private val spaces = " +".toPattern()
|
||||
private val mac = "^([0-9a-f]{2}:){5}[0-9a-f]{2}$".toPattern()
|
||||
|
||||
fun getTetheredIfaces(extras: Bundle) = if (Build.VERSION.SDK_INT >= 26)
|
||||
extras.getStringArrayList(EXTRA_ACTIVE_TETHER).toSet() + extras.getStringArrayList(EXTRA_ACTIVE_LOCAL_ONLY)
|
||||
else extras.getStringArrayList(EXTRA_ACTIVE_TETHER_LEGACY).toSet()
|
||||
|
||||
fun arp(iface: String? = null) = File("/proc/net/arp").bufferedReader().useLines {
|
||||
// IP address HW type Flags HW address Mask Device
|
||||
it.map { it.split(spaces) }
|
||||
.drop(1)
|
||||
.filter { it.size >= 4 && (iface == null || it.getOrNull(5) == iface) &&
|
||||
mac.matcher(it[3]).matches() }
|
||||
.associateBy({ it[3] }, { it[0] })
|
||||
}
|
||||
}
|
||||
89
mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt
Normal file
89
mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt
Normal file
@@ -0,0 +1,89 @@
|
||||
package be.mygod.vpnhotspot.net
|
||||
|
||||
import android.os.Build
|
||||
import be.mygod.vpnhotspot.App.Companion.app
|
||||
import be.mygod.vpnhotspot.R
|
||||
import be.mygod.vpnhotspot.noisySu
|
||||
import java.io.IOException
|
||||
import java.net.Inet4Address
|
||||
import java.net.InetAddress
|
||||
import java.net.NetworkInterface
|
||||
import java.util.*
|
||||
|
||||
class Routing(private val upstream: String, val downstream: String, ownerAddress: InetAddress? = null) {
|
||||
companion object {
|
||||
/**
|
||||
* -w <seconds> is not supported on 7.1-.
|
||||
* Fortunately there also isn't a time limit for starting a foreground service back in 7.1-.
|
||||
*
|
||||
* Source: https://android.googlesource.com/platform/external/iptables/+/android-5.0.0_r1/iptables/iptables.c#1574
|
||||
*/
|
||||
private val IPTABLES = if (Build.VERSION.SDK_INT >= 26) "iptables -w 1" else "iptables -w"
|
||||
|
||||
fun clean() = noisySu(
|
||||
"$IPTABLES -t nat -F PREROUTING",
|
||||
"quiet while $IPTABLES -D FORWARD -j vpnhotspot_fwd; do done",
|
||||
"$IPTABLES -F vpnhotspot_fwd",
|
||||
"$IPTABLES -X vpnhotspot_fwd",
|
||||
"quiet while ip rule del priority 17900; do done")
|
||||
}
|
||||
|
||||
class InterfaceNotFoundException : IOException() {
|
||||
override val message: String get() = app.getString(R.string.exception_interface_not_found)
|
||||
}
|
||||
|
||||
val hostAddress = ownerAddress ?: NetworkInterface.getByName(downstream)?.inetAddresses?.asSequence()
|
||||
?.singleOrNull { it is Inet4Address } ?: throw InterfaceNotFoundException()
|
||||
private val startScript = LinkedList<String>()
|
||||
private val stopScript = LinkedList<String>()
|
||||
var started = false
|
||||
|
||||
fun ipForward(): Routing {
|
||||
startScript.add("echo 1 >/proc/sys/net/ipv4/ip_forward")
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Since Android 5.0, RULE_PRIORITY_TETHERING = 18000.
|
||||
* This also works for Wi-Fi direct where there's no rule at 18000.
|
||||
*
|
||||
* Source: https://android.googlesource.com/platform/system/netd/+/b9baf26/server/RouteController.cpp#65
|
||||
*/
|
||||
fun rule(): Routing {
|
||||
startScript.add("ip rule add from all iif $downstream lookup $upstream priority 17900")
|
||||
// by the time stopScript is called, table entry for upstream may already get removed
|
||||
stopScript.addFirst("ip rule del from all iif $downstream priority 17900")
|
||||
return this
|
||||
}
|
||||
|
||||
fun forward(): Routing {
|
||||
startScript.add("quiet $IPTABLES -N vpnhotspot_fwd 2>/dev/null")
|
||||
startScript.add("$IPTABLES -A vpnhotspot_fwd -i $upstream -o $downstream -m state --state ESTABLISHED,RELATED -j ACCEPT")
|
||||
startScript.add("$IPTABLES -A vpnhotspot_fwd -i $downstream -o $upstream -j ACCEPT")
|
||||
startScript.add("$IPTABLES -I FORWARD -j vpnhotspot_fwd")
|
||||
stopScript.addFirst("$IPTABLES -D vpnhotspot_fwd -i $upstream -o $downstream -m state --state ESTABLISHED,RELATED -j ACCEPT")
|
||||
stopScript.addFirst("$IPTABLES -D vpnhotspot_fwd -i $downstream -o $upstream -j ACCEPT")
|
||||
stopScript.addFirst("$IPTABLES -D FORWARD -j vpnhotspot_fwd")
|
||||
return this
|
||||
}
|
||||
|
||||
fun dnsRedirect(dns: String): Routing {
|
||||
val hostAddress = hostAddress.hostAddress
|
||||
startScript.add("$IPTABLES -t nat -A PREROUTING -i $downstream -p tcp -d $hostAddress --dport 53 -j DNAT --to-destination $dns")
|
||||
startScript.add("$IPTABLES -t nat -A PREROUTING -i $downstream -p udp -d $hostAddress --dport 53 -j DNAT --to-destination $dns")
|
||||
stopScript.addFirst("$IPTABLES -t nat -D PREROUTING -i $downstream -p tcp -d $hostAddress --dport 53 -j DNAT --to-destination $dns")
|
||||
stopScript.addFirst("$IPTABLES -t nat -D PREROUTING -i $downstream -p udp -d $hostAddress --dport 53 -j DNAT --to-destination $dns")
|
||||
return this
|
||||
}
|
||||
|
||||
fun start(): Boolean {
|
||||
if (started) return true
|
||||
started = true
|
||||
return noisySu(startScript)
|
||||
}
|
||||
fun stop(): Boolean {
|
||||
if (!started) return true
|
||||
started = false
|
||||
return noisySu(stopScript)
|
||||
}
|
||||
}
|
||||
59
mobile/src/main/java/be/mygod/vpnhotspot/net/VpnMonitor.kt
Normal file
59
mobile/src/main/java/be/mygod/vpnhotspot/net/VpnMonitor.kt
Normal file
@@ -0,0 +1,59 @@
|
||||
package be.mygod.vpnhotspot.net
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkRequest
|
||||
import be.mygod.vpnhotspot.App.Companion.app
|
||||
import be.mygod.vpnhotspot.debugLog
|
||||
|
||||
object VpnMonitor : ConnectivityManager.NetworkCallback() {
|
||||
interface Callback {
|
||||
fun onAvailable(ifname: String)
|
||||
fun onLost(ifname: String)
|
||||
}
|
||||
|
||||
private const val TAG = "VpnMonitor"
|
||||
|
||||
val connectivityManager = app.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
private val request by lazy {
|
||||
NetworkRequest.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
|
||||
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
||||
.build()
|
||||
}
|
||||
private val callbacks = HashSet<Callback>()
|
||||
private var registered = false
|
||||
|
||||
/**
|
||||
* Obtaining ifname in onLost doesn't work so we need to cache it in onAvailable.
|
||||
*/
|
||||
private val available = HashMap<Network, String>()
|
||||
override fun onAvailable(network: Network) {
|
||||
val ifname = connectivityManager.getLinkProperties(network)?.interfaceName ?: return
|
||||
if (available.put(network, ifname) != null) return
|
||||
debugLog(TAG, "onAvailable: $ifname")
|
||||
callbacks.forEach { it.onAvailable(ifname) }
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
val ifname = available.remove(network) ?: return
|
||||
debugLog(TAG, "onLost: $ifname")
|
||||
callbacks.forEach { it.onLost(ifname) }
|
||||
}
|
||||
|
||||
fun registerCallback(callback: Callback) {
|
||||
if (!callbacks.add(callback)) return
|
||||
if (registered) available.forEach { callback.onAvailable(it.value) } else {
|
||||
connectivityManager.registerNetworkCallback(request, this)
|
||||
registered = true
|
||||
}
|
||||
}
|
||||
fun unregisterCallback(callback: Callback) {
|
||||
if (!callbacks.remove(callback) || callbacks.isNotEmpty() || !registered) return
|
||||
connectivityManager.unregisterNetworkCallback(this)
|
||||
registered = false
|
||||
available.clear()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user