diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterFragment.kt index 91645114..7e9e6f31 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterFragment.kt @@ -26,7 +26,7 @@ import be.mygod.vpnhotspot.databinding.FragmentRepeaterBinding import be.mygod.vpnhotspot.databinding.ListitemClientBinding import be.mygod.vpnhotspot.net.IpNeighbour import be.mygod.vpnhotspot.net.IpNeighbourMonitor -import be.mygod.vpnhotspot.net.NetUtils +import be.mygod.vpnhotspot.net.ConnectivityManagerHelper import be.mygod.vpnhotspot.net.TetherType class RepeaterFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickListener, IpNeighbourMonitor.Callback { @@ -127,7 +127,7 @@ class RepeaterFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickL private var p2pInterface: String? = null private var tetheredInterfaces = emptySet() private val receiver = broadcastReceiver { _, intent -> - tetheredInterfaces = NetUtils.getTetheredIfaces(intent.extras).toSet() + tetheredInterfaces = ConnectivityManagerHelper.getTetheredIfaces(intent.extras).toSet() adapter.recreate() } @@ -154,7 +154,7 @@ class RepeaterFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickL val context = context!! context.bindService(Intent(context, RepeaterService::class.java), this, Context.BIND_AUTO_CREATE) IpNeighbourMonitor.registerCallback(this) - context.registerReceiver(receiver, intentFilter(NetUtils.ACTION_TETHER_STATE_CHANGED)) + context.registerReceiver(receiver, intentFilter(ConnectivityManagerHelper.ACTION_TETHER_STATE_CHANGED)) } override fun onStop() { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringFragment.kt index 903e39c2..b56c2ab7 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringFragment.kt @@ -23,7 +23,7 @@ import android.view.View import android.view.ViewGroup import be.mygod.vpnhotspot.databinding.FragmentTetheringBinding import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding -import be.mygod.vpnhotspot.net.NetUtils +import be.mygod.vpnhotspot.net.ConnectivityManagerHelper import be.mygod.vpnhotspot.net.TetherType class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickListener { @@ -89,7 +89,7 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick private var binder: TetheringService.TetheringBinder? = null val adapter = InterfaceAdapter() private val receiver = broadcastReceiver { _, intent -> - adapter.update(NetUtils.getTetheredIfaces(intent.extras).toSet()) + adapter.update(ConnectivityManagerHelper.getTetheredIfaces(intent.extras).toSet()) } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -107,7 +107,7 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick override fun onStart() { super.onStart() val context = context!! - context.registerReceiver(receiver, intentFilter(NetUtils.ACTION_TETHER_STATE_CHANGED)) + context.registerReceiver(receiver, intentFilter(ConnectivityManagerHelper.ACTION_TETHER_STATE_CHANGED)) context.bindService(Intent(context, TetheringService::class.java), this, Context.BIND_AUTO_CREATE) } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt index a1c63656..3a943e80 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt @@ -26,8 +26,8 @@ class TetheringService : Service(), VpnMonitor.Callback, IpNeighbourMonitor.Call private var receiverRegistered = false private val receiver = broadcastReceiver { _, intent -> when (intent.action) { - NetUtils.ACTION_TETHER_STATE_CHANGED -> { - val remove = routings.keys - NetUtils.getTetheredIfaces(intent.extras) + ConnectivityManagerHelper.ACTION_TETHER_STATE_CHANGED -> { + val remove = routings.keys - ConnectivityManagerHelper.getTetheredIfaces(intent.extras) if (remove.isEmpty()) return@broadcastReceiver val failed = remove.any { routings.remove(it)?.stop() == false } if (failed) Toast.makeText(this, getText(R.string.noisy_su_failure), Toast.LENGTH_SHORT).show() @@ -56,7 +56,7 @@ class TetheringService : Service(), VpnMonitor.Callback, IpNeighbourMonitor.Call } if (failed) Toast.makeText(this, getText(R.string.noisy_su_failure), Toast.LENGTH_SHORT).show() } else if (!receiverRegistered) { - registerReceiver(receiver, intentFilter(NetUtils.ACTION_TETHER_STATE_CHANGED)) + registerReceiver(receiver, intentFilter(ConnectivityManagerHelper.ACTION_TETHER_STATE_CHANGED)) LocalBroadcastManager.getInstance(this) .registerReceiver(receiver, intentFilter(App.ACTION_CLEAN_ROUTINGS)) IpNeighbourMonitor.registerCallback(this) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/ConnectivityManagerHelper.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/ConnectivityManagerHelper.kt new file mode 100644 index 00000000..b243a89d --- /dev/null +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/ConnectivityManagerHelper.kt @@ -0,0 +1,27 @@ +package be.mygod.vpnhotspot.net + +import android.os.Build +import android.os.Bundle +import android.support.annotation.RequiresApi + +/** + * Hidden constants from ConnectivityManager and some helpers. + */ +object ConnectivityManagerHelper { + /** + * 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" + + 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() +} diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/IpNeighbour.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/IpNeighbour.kt index 7b8c5a62..b5eec7bc 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/IpNeighbour.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/IpNeighbour.kt @@ -1,6 +1,8 @@ package be.mygod.vpnhotspot.net import android.util.Log +import java.io.File +import java.io.IOException data class IpNeighbour(val ip: String, val dev: String, val lladdr: String, val state: State) { enum class State { @@ -18,6 +20,8 @@ data class IpNeighbour(val ip: String, val dev: String, val lladdr: String, val */ private val parser = "^(Deleted )?(.+?) (dev (.+?) )?(lladdr (.+?))?( proxy)?( ([INCOMPLET,RAHBSDYF]+))?\$".toRegex() + private fun checkLladdrNotLoopback(lladdr: String) = if (lladdr == "00:00:00:00:00:00") "" else lladdr + fun parse(line: String): IpNeighbour? { val match = parser.matchEntire(line) if (match == null) { @@ -26,11 +30,11 @@ data class IpNeighbour(val ip: String, val dev: String, val lladdr: String, val } val ip = match.groupValues[2] val dev = match.groupValues[4] - var lladdr = match.groupValues[6] + var lladdr = checkLladdrNotLoopback(match.groupValues[6]) // use ARP as fallback - if (dev.isNotBlank() && lladdr.isBlank()) lladdr = (NetUtils.arp() - .filter { it[NetUtils.ARP_IP_ADDRESS] == ip && it[NetUtils.ARP_DEVICE] == dev } - .map { it[NetUtils.ARP_HW_ADDRESS] } + if (dev.isNotBlank() && lladdr.isBlank()) lladdr = checkLladdrNotLoopback(arp() + .filter { it[ARP_IP_ADDRESS] == ip && it[ARP_DEVICE] == dev } + .map { it[ARP_HW_ADDRESS] } .singleOrNull() ?: "") val state = if (match.groupValues[1].isNotEmpty()) State.DELETING else when (match.groupValues[9]) { "", "INCOMPLETE" -> State.INCOMPLETE @@ -45,5 +49,29 @@ data class IpNeighbour(val ip: String, val dev: String, val lladdr: String, val } return IpNeighbour(ip, dev, lladdr, state) } + + private val spaces = " +".toPattern() + private val mac = "^([0-9a-f]{2}:){5}[0-9a-f]{2}$".toPattern() + + // IP address HW type Flags HW address Mask Device + private const val ARP_IP_ADDRESS = 0 + private const val ARP_HW_ADDRESS = 3 + private const val ARP_DEVICE = 5 + private const val ARP_CACHE_EXPIRE = 1L * 1000 * 1000 * 1000 + private var arpCache = emptyList>() + private var arpCacheTime = -ARP_CACHE_EXPIRE + fun arp(): List> { + if (System.nanoTime() - arpCacheTime >= ARP_CACHE_EXPIRE) try { + arpCache = File("/proc/net/arp").bufferedReader().useLines { + it.map { it.split(spaces) } + .drop(1) + .filter { it.size >= 6 && mac.matcher(it[ARP_HW_ADDRESS]).matches() } + .toList() + } + } catch (e: IOException) { + e.printStackTrace() + } + return arpCache + } } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/NetUtils.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/NetUtils.kt deleted file mode 100644 index 2e0ff423..00000000 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/NetUtils.kt +++ /dev/null @@ -1,51 +0,0 @@ -package be.mygod.vpnhotspot.net - -import android.os.Build -import android.os.Bundle -import android.support.annotation.RequiresApi -import java.io.File -import java.io.IOException - -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() - - // IP address HW type Flags HW address Mask Device - const val ARP_IP_ADDRESS = 0 - const val ARP_HW_ADDRESS = 3 - const val ARP_DEVICE = 5 - private const val ARP_CACHE_EXPIRE = 1L * 1000 * 1000 * 1000 - private var arpCache = emptyList>() - private var arpCacheTime = -ARP_CACHE_EXPIRE - fun arp(): List> { - if (System.nanoTime() - arpCacheTime >= ARP_CACHE_EXPIRE) try { - arpCache = File("/proc/net/arp").bufferedReader().useLines { - it.map { it.split(spaces) } - .drop(1) - .filter { it.size >= 6 && mac.matcher(it[ARP_HW_ADDRESS]).matches() } - .toList() - } - } catch (e: IOException) { - e.printStackTrace() - } - return arpCache - } -}