From d70acfa29e0f476f981c32f9f0a027b9e1f8a7e7 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 11 Feb 2018 01:08:59 -0800 Subject: [PATCH] Add IPv6 addresses to connected devices --- .../be/mygod/vpnhotspot/RepeaterFragment.kt | 46 +++++++++++-------- .../be/mygod/vpnhotspot/TetheringService.kt | 5 +- .../be/mygod/vpnhotspot/net/IpNeighbour.kt | 25 +++++----- .../vpnhotspot/net/IpNeighbourMonitor.kt | 10 ++-- mobile/src/main/res/values-zh-rCN/strings.xml | 6 +-- mobile/src/main/res/values/strings.xml | 6 +-- 6 files changed, 57 insertions(+), 41 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterFragment.kt index c26bbecb..88c19d57 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterFragment.kt @@ -29,6 +29,7 @@ import be.mygod.vpnhotspot.net.IpNeighbourMonitor import be.mygod.vpnhotspot.net.ConnectivityManagerHelper import be.mygod.vpnhotspot.net.TetherType import java.net.NetworkInterface +import java.util.* class RepeaterFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickListener, IpNeighbourMonitor.Callback { inner class Data : BaseObservable() { @@ -79,19 +80,21 @@ class RepeaterFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickL val statusListener = broadcastReceiver { _, _ -> onStatusChanged() } } - inner class Client(p2p: WifiP2pDevice? = null, private val neighbour: IpNeighbour? = null) { - private val iface = neighbour?.dev ?: p2pInterface!! - val mac = neighbour?.lladdr ?: p2p!!.deviceAddress!! - val ip = neighbour?.ip + inner class Client(p2p: WifiP2pDevice? = null, neighbour: IpNeighbour? = null) { + val iface = neighbour?.dev ?: p2pInterface!! + val mac = p2p?.deviceAddress ?: neighbour?.lladdr!! + val ip = TreeMap() val icon get() = TetherType.ofInterface(iface, p2pInterface).icon - val title get() = listOf(ip, mac).filter { !it.isNullOrEmpty() }.joinToString("\t\t") - val description get() = getString(when (neighbour?.state) { - IpNeighbour.State.INCOMPLETE, null -> R.string.connected_state_incomplete - IpNeighbour.State.VALID -> R.string.connected_state_valid - IpNeighbour.State.FAILED -> R.string.connected_state_failed - else -> throw IllegalStateException("Invalid IpNeighbour.State") - }, iface) + val title get() = "$mac%$iface" + val description get() = ip.entries.joinToString("\n") { (ip, state) -> + getString(when (state) { + IpNeighbour.State.INCOMPLETE -> R.string.connected_state_incomplete + IpNeighbour.State.VALID -> R.string.connected_state_valid + IpNeighbour.State.FAILED -> R.string.connected_state_failed + else -> throw IllegalStateException("Invalid IpNeighbour.State: $state") + }, ip) + } } private class ClientViewHolder(val binding: ListitemClientBinding) : RecyclerView.ViewHolder(binding.root) private inner class ClientAdapter : RecyclerView.Adapter() { @@ -100,15 +103,20 @@ class RepeaterFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickL var neighbours = emptyList() fun recreate() { + val p2p = HashMap(p2p.associateBy({ Pair(p2pInterface, it.deviceAddress) }, { Client(it) })) + for (neighbour in neighbours) { + val key = Pair(neighbour.dev, neighbour.lladdr) + var client = p2p[key] + if (client == null) { + if (!tetheredInterfaces.contains(neighbour.dev)) continue + client = Client(neighbour = neighbour) + p2p[key] = client + } + client.ip += Pair(neighbour.ip, neighbour.state) + } clients.clear() - val p2p = HashMap(p2p.associateBy { it.deviceAddress }) - for (neighbour in neighbours) - if (neighbour.dev == p2pInterface) { - val client = p2p.remove(neighbour.lladdr) - if (client != null) clients.add(Client(client, neighbour)) - } else if (tetheredInterfaces.contains(neighbour.dev)) clients.add(Client(neighbour = neighbour)) - clients.addAll(p2p.map { Client(it.value) }) - clients.sortWith(compareBy { it.ip }.thenBy { it.mac }) + clients.addAll(p2p.values) + clients.sortWith(compareBy { it.iface }.thenBy { it.mac }) notifyDataSetChanged() // recreate everything binding.swipeRefresher.isRefreshing = false } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt index bfa26c22..4af51381 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt @@ -101,7 +101,10 @@ class TetheringService : Service(), VpnMonitor.Callback, IpNeighbourMonitor.Call } override fun postIpNeighbourAvailable() { val sizeLookup = neighbours.groupBy { it.dev }.mapValues { (_, neighbours) -> - neighbours.count { it.state != IpNeighbour.State.FAILED } + neighbours + .filter { it.state != IpNeighbour.State.FAILED } + .distinctBy { it.lladdr } + .size } ServiceNotification.startForeground(this, routings.keys.associate { Pair(it, sizeLookup[it] ?: 0) }) } 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 d3b41b46..fba2b985 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/IpNeighbour.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/IpNeighbour.kt @@ -16,10 +16,10 @@ data class IpNeighbour(val ip: String, val dev: String, val lladdr: String, val * Parser based on: * https://android.googlesource.com/platform/external/iproute2/+/ad0a6a2/ip/ipneigh.c#194 * https://people.cs.clemson.edu/~westall/853/notes/arpstate.pdf - * Assumptions: IP addr (key) always present, IPv4 only, RTM_GETNEIGH is never used and show_stats = 0 + * Assumptions: IP addr (key) always present, RTM_GETNEIGH is never used and show_stats = 0 */ - private val parser = - "^(Deleted )?(.+?) (dev (.+?) )?(lladdr (.+?))?( proxy)?( ([INCOMPLET,RAHBSDYF]+))?\$".toRegex() + private val parser = ("^(Deleted )?(.+?) (dev (.+?) )?(lladdr (.+?))?( router)?( proxy)?" + + "( ([INCOMPLET,RAHBSDYF]+))?\$").toRegex() private fun checkLladdrNotLoopback(lladdr: String) = if (lladdr == "00:00:00:00:00:00") "" else lladdr fun parse(line: String): IpNeighbour? { @@ -36,16 +36,17 @@ data class IpNeighbour(val ip: String, val dev: String, val lladdr: String, val .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 - "REACHABLE", "DELAY", "STALE", "PROBE", "PERMANENT" -> State.VALID - "FAILED" -> State.FAILED - "NOARP" -> return null // skip - else -> { - Log.w(TAG, "Unknown state encountered: ${match.groupValues[9]}") - return null + val state = if (match.groupValues[1].isNotEmpty() || lladdr.isEmpty()) State.DELETING else + when (match.groupValues[10]) { + "", "INCOMPLETE" -> State.INCOMPLETE + "REACHABLE", "DELAY", "STALE", "PROBE", "PERMANENT" -> State.VALID + "FAILED" -> State.FAILED + "NOARP" -> return null // skip + else -> { + Log.w(TAG, "Unknown state encountered: ${match.groupValues[10]}") + return null + } } - } return IpNeighbour(ip, dev, lladdr, state) } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/IpNeighbourMonitor.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/IpNeighbourMonitor.kt index 37d84c22..65061fc3 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/IpNeighbourMonitor.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/IpNeighbourMonitor.kt @@ -58,7 +58,7 @@ class IpNeighbourMonitor private constructor() { init { thread(name = TAG + "-input") { // monitor may get rejected by SELinux - val monitor = ProcessBuilder("sh", "-c", "ip -4 monitor neigh || su -c ip -4 monitor neigh") + val monitor = ProcessBuilder("sh", "-c", "ip monitor neigh || su -c ip monitor neigh") .redirectErrorStream(true) .start() this.monitor = monitor @@ -85,7 +85,7 @@ class IpNeighbourMonitor private constructor() { } fun flush() = thread(name = TAG + "-flush") { - val process = ProcessBuilder("ip", "-4", "neigh") + val process = ProcessBuilder("ip", "neigh") .redirectErrorStream(true) .start() process.waitFor() @@ -94,7 +94,11 @@ class IpNeighbourMonitor private constructor() { process.inputStream.bufferedReader().useLines { synchronized(neighbours) { neighbours.clear() - neighbours.putAll(it.map(IpNeighbour.Companion::parse).filterNotNull().associateBy { it.ip }) + neighbours.putAll(it + .map(IpNeighbour.Companion::parse) + .filterNotNull() + .filter { it.state != IpNeighbour.State.DELETING } // skip entries without lladdr + .associateBy { it.ip }) postUpdateLocked() } } diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml index 76332d99..27d34814 100644 --- a/mobile/src/main/res/values-zh-rCN/strings.xml +++ b/mobile/src/main/res/values-zh-rCN/strings.xml @@ -36,9 +36,9 @@ 管理… 已连接设备 - 正在通过 %s 连接 - 已连接到 %s - 已从 %s 断开 + %s (正在连接) + %s (已连上) + %s (已断开) 服务 下游 DNS 服务器[:端口] diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index af7aa2aa..b350a374 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -38,9 +38,9 @@ Manage… Connected devices - Connecting to %s - Connected to %s - Lost from %s + %s (connecting) + %s (reachable) + %s (lost) Service Downstream DNS server[:port]