Group net package

This commit is contained in:
Mygod
2018-01-20 18:37:45 -08:00
parent 610c6403ea
commit 2108c635ce
8 changed files with 23 additions and 11 deletions

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

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

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