Move MAC utils to MacAddressCompat
This commit is contained in:
@@ -16,7 +16,10 @@ android {
|
|||||||
targetCompatibility = javaVersion
|
targetCompatibility = javaVersion
|
||||||
}
|
}
|
||||||
compileSdkVersion("android-R")
|
compileSdkVersion("android-R")
|
||||||
kotlinOptions.jvmTarget = javaVersion.toString()
|
kotlinOptions {
|
||||||
|
freeCompilerArgs = listOf("-XXLanguage:+InlineClasses")
|
||||||
|
jvmTarget = javaVersion.toString()
|
||||||
|
}
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "be.mygod.vpnhotspot"
|
applicationId = "be.mygod.vpnhotspot"
|
||||||
minSdkVersion(21)
|
minSdkVersion(21)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import androidx.annotation.StringRes
|
|||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
|
import be.mygod.vpnhotspot.net.MacAddressCompat
|
||||||
import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor
|
import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor
|
||||||
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper
|
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper
|
||||||
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.deletePersistentGroup
|
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.deletePersistentGroup
|
||||||
@@ -23,7 +24,6 @@ import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.requestPersistentGroupI
|
|||||||
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setWifiP2pChannels
|
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setWifiP2pChannels
|
||||||
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.startWps
|
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.startWps
|
||||||
import be.mygod.vpnhotspot.net.wifi.configuration.channelToFrequency
|
import be.mygod.vpnhotspot.net.wifi.configuration.channelToFrequency
|
||||||
import be.mygod.vpnhotspot.room.macToString
|
|
||||||
import be.mygod.vpnhotspot.util.*
|
import be.mygod.vpnhotspot.util.*
|
||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
@@ -381,7 +381,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
|
|||||||
check(routingManager == null)
|
check(routingManager == null)
|
||||||
routingManager = object : RoutingManager.LocalOnly(this, group.`interface`!!) {
|
routingManager = object : RoutingManager.LocalOnly(this, group.`interface`!!) {
|
||||||
override fun ifaceHandler(iface: NetworkInterface) {
|
override fun ifaceHandler(iface: NetworkInterface) {
|
||||||
iface.hardwareAddress?.asIterable()?.macToString()?.let { lastMac = it }
|
iface.hardwareAddress?.let { lastMac = MacAddressCompat.bytesToString(it) }
|
||||||
}
|
}
|
||||||
}.apply { start() }
|
}.apply { start() }
|
||||||
status = Status.ACTIVE
|
status = Status.ACTIVE
|
||||||
|
|||||||
@@ -9,16 +9,16 @@ import be.mygod.vpnhotspot.App.Companion.app
|
|||||||
import be.mygod.vpnhotspot.R
|
import be.mygod.vpnhotspot.R
|
||||||
import be.mygod.vpnhotspot.net.InetAddressComparator
|
import be.mygod.vpnhotspot.net.InetAddressComparator
|
||||||
import be.mygod.vpnhotspot.net.IpNeighbour
|
import be.mygod.vpnhotspot.net.IpNeighbour
|
||||||
|
import be.mygod.vpnhotspot.net.MacAddressCompat
|
||||||
import be.mygod.vpnhotspot.net.TetherType
|
import be.mygod.vpnhotspot.net.TetherType
|
||||||
import be.mygod.vpnhotspot.room.AppDatabase
|
import be.mygod.vpnhotspot.room.AppDatabase
|
||||||
import be.mygod.vpnhotspot.room.ClientRecord
|
import be.mygod.vpnhotspot.room.ClientRecord
|
||||||
import be.mygod.vpnhotspot.room.macToString
|
|
||||||
import be.mygod.vpnhotspot.util.makeIpSpan
|
import be.mygod.vpnhotspot.util.makeIpSpan
|
||||||
import be.mygod.vpnhotspot.util.makeMacSpan
|
import be.mygod.vpnhotspot.util.makeMacSpan
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
open class Client(val mac: Long, val iface: String) {
|
open class Client(val mac: MacAddressCompat, val iface: String) {
|
||||||
companion object DiffCallback : DiffUtil.ItemCallback<Client>() {
|
companion object DiffCallback : DiffUtil.ItemCallback<Client>() {
|
||||||
override fun areItemsTheSame(oldItem: Client, newItem: Client) =
|
override fun areItemsTheSame(oldItem: Client, newItem: Client) =
|
||||||
oldItem.iface == newItem.iface && oldItem.mac == newItem.mac
|
oldItem.iface == newItem.iface && oldItem.mac == newItem.mac
|
||||||
@@ -26,7 +26,7 @@ open class Client(val mac: Long, val iface: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val ip = TreeMap<InetAddress, IpNeighbour.State>(InetAddressComparator)
|
val ip = TreeMap<InetAddress, IpNeighbour.State>(InetAddressComparator)
|
||||||
val macString by lazy { mac.macToString() }
|
val macString by lazy { mac.toString() }
|
||||||
private val record = AppDatabase.instance.clientRecordDao.lookupOrDefaultSync(mac)
|
private val record = AppDatabase.instance.clientRecordDao.lookupOrDefaultSync(mac)
|
||||||
private val macIface get() = SpannableStringBuilder(makeMacSpan(macString)).apply {
|
private val macIface get() = SpannableStringBuilder(makeMacSpan(macString)).apply {
|
||||||
append('%')
|
append('%')
|
||||||
@@ -65,7 +65,7 @@ open class Client(val mac: Long, val iface: String) {
|
|||||||
}.trimEnd()
|
}.trimEnd()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun obtainRecord() = record.value ?: ClientRecord(mac)
|
fun obtainRecord() = record.value ?: ClientRecord(mac.addr)
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ import androidx.lifecycle.ViewModel
|
|||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import be.mygod.vpnhotspot.RepeaterService
|
import be.mygod.vpnhotspot.RepeaterService
|
||||||
import be.mygod.vpnhotspot.net.IpNeighbour
|
import be.mygod.vpnhotspot.net.IpNeighbour
|
||||||
|
import be.mygod.vpnhotspot.net.MacAddressCompat
|
||||||
import be.mygod.vpnhotspot.net.TetheringManager
|
import be.mygod.vpnhotspot.net.TetheringManager
|
||||||
import be.mygod.vpnhotspot.net.TetheringManager.localOnlyTetheredIfaces
|
import be.mygod.vpnhotspot.net.TetheringManager.localOnlyTetheredIfaces
|
||||||
import be.mygod.vpnhotspot.net.TetheringManager.tetheredIfaces
|
import be.mygod.vpnhotspot.net.TetheringManager.tetheredIfaces
|
||||||
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
|
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
|
||||||
import be.mygod.vpnhotspot.room.macToLong
|
|
||||||
import be.mygod.vpnhotspot.util.broadcastReceiver
|
import be.mygod.vpnhotspot.util.broadcastReceiver
|
||||||
|
|
||||||
class ClientViewModel : ViewModel(), ServiceConnection, IpNeighbourMonitor.Callback {
|
class ClientViewModel : ViewModel(), ServiceConnection, IpNeighbourMonitor.Callback {
|
||||||
@@ -31,15 +31,15 @@ class ClientViewModel : ViewModel(), ServiceConnection, IpNeighbourMonitor.Callb
|
|||||||
val clients = MutableLiveData<List<Client>>()
|
val clients = MutableLiveData<List<Client>>()
|
||||||
|
|
||||||
private fun populateClients() {
|
private fun populateClients() {
|
||||||
val clients = HashMap<Pair<String, Long>, Client>()
|
val clients = HashMap<Pair<String, MacAddressCompat>, Client>()
|
||||||
val group = repeater?.group
|
val group = repeater?.group
|
||||||
val p2pInterface = group?.`interface`
|
val p2pInterface = group?.`interface`
|
||||||
if (p2pInterface != null) {
|
if (p2pInterface != null) {
|
||||||
for (client in p2p) clients[Pair(p2pInterface, client.deviceAddress.macToLong())] =
|
for (client in p2p) clients[p2pInterface to MacAddressCompat.fromString(client.deviceAddress)] =
|
||||||
WifiP2pClient(p2pInterface, client)
|
WifiP2pClient(p2pInterface, client)
|
||||||
}
|
}
|
||||||
for (neighbour in neighbours) {
|
for (neighbour in neighbours) {
|
||||||
val key = Pair(neighbour.dev, neighbour.lladdr)
|
val key = neighbour.dev to neighbour.lladdr
|
||||||
var client = clients[key]
|
var client = clients[key]
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
if (!tetheredInterfaces.contains(neighbour.dev)) continue
|
if (!tetheredInterfaces.contains(neighbour.dev)) continue
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ import be.mygod.vpnhotspot.Empty
|
|||||||
import be.mygod.vpnhotspot.R
|
import be.mygod.vpnhotspot.R
|
||||||
import be.mygod.vpnhotspot.databinding.FragmentClientsBinding
|
import be.mygod.vpnhotspot.databinding.FragmentClientsBinding
|
||||||
import be.mygod.vpnhotspot.databinding.ListitemClientBinding
|
import be.mygod.vpnhotspot.databinding.ListitemClientBinding
|
||||||
|
import be.mygod.vpnhotspot.net.MacAddressCompat
|
||||||
import be.mygod.vpnhotspot.net.TetherType
|
import be.mygod.vpnhotspot.net.TetherType
|
||||||
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
|
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
|
||||||
import be.mygod.vpnhotspot.net.monitor.TrafficRecorder
|
import be.mygod.vpnhotspot.net.monitor.TrafficRecorder
|
||||||
import be.mygod.vpnhotspot.room.AppDatabase
|
import be.mygod.vpnhotspot.room.AppDatabase
|
||||||
import be.mygod.vpnhotspot.room.ClientStats
|
import be.mygod.vpnhotspot.room.ClientStats
|
||||||
import be.mygod.vpnhotspot.room.TrafficRecord
|
import be.mygod.vpnhotspot.room.TrafficRecord
|
||||||
import be.mygod.vpnhotspot.room.macToString
|
|
||||||
import be.mygod.vpnhotspot.util.SpanFormatter
|
import be.mygod.vpnhotspot.util.SpanFormatter
|
||||||
import be.mygod.vpnhotspot.util.toPluralInt
|
import be.mygod.vpnhotspot.util.toPluralInt
|
||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
@@ -46,11 +46,11 @@ import java.text.NumberFormat
|
|||||||
|
|
||||||
class ClientsFragment : Fragment() {
|
class ClientsFragment : Fragment() {
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class NicknameArg(val mac: Long, val nickname: CharSequence) : Parcelable
|
data class NicknameArg(val mac: MacAddressCompat, val nickname: CharSequence) : Parcelable
|
||||||
class NicknameDialogFragment : AlertDialogFragment<NicknameArg, Empty>() {
|
class NicknameDialogFragment : AlertDialogFragment<NicknameArg, Empty>() {
|
||||||
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
|
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
|
||||||
setView(R.layout.dialog_nickname)
|
setView(R.layout.dialog_nickname)
|
||||||
setTitle(getString(R.string.clients_nickname_title, arg.mac.macToString()))
|
setTitle(getString(R.string.clients_nickname_title, arg.mac.toString()))
|
||||||
setPositiveButton(android.R.string.ok, listener)
|
setPositiveButton(android.R.string.ok, listener)
|
||||||
setNegativeButton(android.R.string.cancel, null)
|
setNegativeButton(android.R.string.cancel, null)
|
||||||
setNeutralButton(emojize(getText(R.string.clients_nickname_set_to_vendor)), listener)
|
setNeutralButton(emojize(getText(R.string.clients_nickname_set_to_vendor)), listener)
|
||||||
@@ -153,7 +153,7 @@ class ClientsFragment : Fragment() {
|
|||||||
withContext(Dispatchers.Unconfined) {
|
withContext(Dispatchers.Unconfined) {
|
||||||
StatsDialogFragment().withArg(StatsArg(
|
StatsDialogFragment().withArg(StatsArg(
|
||||||
client.title.value ?: return@withContext,
|
client.title.value ?: return@withContext,
|
||||||
AppDatabase.instance.trafficRecordDao.queryStats(client.mac)
|
AppDatabase.instance.trafficRecordDao.queryStats(client.mac.addr)
|
||||||
)).show(this@ClientsFragment)
|
)).show(this@ClientsFragment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,7 +181,7 @@ class ClientsFragment : Fragment() {
|
|||||||
override fun onBindViewHolder(holder: ClientViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ClientViewHolder, position: Int) {
|
||||||
val client = getItem(position)
|
val client = getItem(position)
|
||||||
holder.binding.client = client
|
holder.binding.client = client
|
||||||
holder.binding.rate = rates.computeIfAbsent(Pair(client.iface, client.mac)) { TrafficRate() }
|
holder.binding.rate = rates.computeIfAbsent(client.iface to client.mac) { TrafficRate() }
|
||||||
holder.binding.executePendingBindings()
|
holder.binding.executePendingBindings()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +196,9 @@ class ClientsFragment : Fragment() {
|
|||||||
check(newRecord.receivedPackets == oldRecord.receivedPackets)
|
check(newRecord.receivedPackets == oldRecord.receivedPackets)
|
||||||
check(newRecord.receivedBytes == oldRecord.receivedBytes)
|
check(newRecord.receivedBytes == oldRecord.receivedBytes)
|
||||||
} else {
|
} else {
|
||||||
val rate = rates.computeIfAbsent(Pair(newRecord.downstream, newRecord.mac)) { TrafficRate() }
|
val rate = rates.computeIfAbsent(newRecord.downstream to MacAddressCompat(newRecord.mac)) {
|
||||||
|
TrafficRate()
|
||||||
|
}
|
||||||
if (rate.send < 0 || rate.receive < 0) {
|
if (rate.send < 0 || rate.receive < 0) {
|
||||||
rate.send = 0
|
rate.send = 0
|
||||||
rate.receive = 0
|
rate.receive = 0
|
||||||
@@ -211,7 +213,7 @@ class ClientsFragment : Fragment() {
|
|||||||
|
|
||||||
private lateinit var binding: FragmentClientsBinding
|
private lateinit var binding: FragmentClientsBinding
|
||||||
private val adapter = ClientAdapter()
|
private val adapter = ClientAdapter()
|
||||||
private var rates = mutableMapOf<Pair<String, Long>, TrafficRate>()
|
private var rates = mutableMapOf<Pair<String, MacAddressCompat>, TrafficRate>()
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
binding = FragmentClientsBinding.inflate(inflater, container, false)
|
binding = FragmentClientsBinding.inflate(inflater, container, false)
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import android.os.Build
|
|||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import be.mygod.vpnhotspot.R
|
import be.mygod.vpnhotspot.R
|
||||||
|
import be.mygod.vpnhotspot.net.MacAddressCompat
|
||||||
import be.mygod.vpnhotspot.room.AppDatabase
|
import be.mygod.vpnhotspot.room.AppDatabase
|
||||||
import be.mygod.vpnhotspot.room.macToString
|
|
||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
@@ -22,26 +22,26 @@ import java.net.URL
|
|||||||
* This class generates a default nickname for new clients.
|
* This class generates a default nickname for new clients.
|
||||||
*/
|
*/
|
||||||
object MacLookup {
|
object MacLookup {
|
||||||
class UnexpectedError(val mac: Long, val error: String) : JSONException("") {
|
class UnexpectedError(val mac: MacAddressCompat, val error: String) : JSONException("") {
|
||||||
private fun formatMessage(context: Context) =
|
private fun formatMessage(context: Context) =
|
||||||
context.getString(R.string.clients_mac_lookup_unexpected_error, mac.macToString(), error)
|
context.getString(R.string.clients_mac_lookup_unexpected_error, mac.toString(), error)
|
||||||
override val message get() = formatMessage(app.english)
|
override val message get() = formatMessage(app.english)
|
||||||
override fun getLocalizedMessage() = formatMessage(app)
|
override fun getLocalizedMessage() = formatMessage(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val macLookupBusy = mutableMapOf<Long, Pair<HttpURLConnection, Job>>()
|
private val macLookupBusy = mutableMapOf<MacAddressCompat, Pair<HttpURLConnection, Job>>()
|
||||||
private val countryCodeRegex = "([A-Z]{2})\\s*\$".toRegex() // http://en.wikipedia.org/wiki/ISO_3166-1
|
private val countryCodeRegex = "([A-Z]{2})\\s*\$".toRegex() // http://en.wikipedia.org/wiki/ISO_3166-1
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
fun abort(mac: Long) = macLookupBusy.remove(mac)?.let { (conn, job) ->
|
fun abort(mac: MacAddressCompat) = macLookupBusy.remove(mac)?.let { (conn, job) ->
|
||||||
job.cancel()
|
job.cancel()
|
||||||
if (Build.VERSION.SDK_INT >= 26) conn.disconnect() else GlobalScope.launch(Dispatchers.IO) { conn.disconnect() }
|
if (Build.VERSION.SDK_INT >= 26) conn.disconnect() else GlobalScope.launch(Dispatchers.IO) { conn.disconnect() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
fun perform(mac: Long, explicit: Boolean = false) {
|
fun perform(mac: MacAddressCompat, explicit: Boolean = false) {
|
||||||
abort(mac)
|
abort(mac)
|
||||||
val conn = URL("https://macvendors.co/api/" + mac.macToString()).openConnection() as HttpURLConnection
|
val conn = URL("https://macvendors.co/api/$mac").openConnection() as HttpURLConnection
|
||||||
macLookupBusy[mac] = conn to GlobalScope.launch(Dispatchers.IO) {
|
macLookupBusy[mac] = conn to GlobalScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val response = conn.inputStream.bufferedReader().readText()
|
val response = conn.inputStream.bufferedReader().readText()
|
||||||
@@ -69,7 +69,7 @@ object MacLookup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun extractCountry(mac: Long, response: String, obj: JSONObject): MatchResult? {
|
private fun extractCountry(mac: MacAddressCompat, response: String, obj: JSONObject): MatchResult? {
|
||||||
countryCodeRegex.matchEntire(obj.optString("country"))?.also { return it }
|
countryCodeRegex.matchEntire(obj.optString("country"))?.also { return it }
|
||||||
val address = obj.optString("address")
|
val address = obj.optString("address")
|
||||||
if (address.isBlank()) return null
|
if (address.isBlank()) return null
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package be.mygod.vpnhotspot.client
|
package be.mygod.vpnhotspot.client
|
||||||
|
|
||||||
import android.net.wifi.p2p.WifiP2pDevice
|
import android.net.wifi.p2p.WifiP2pDevice
|
||||||
|
import be.mygod.vpnhotspot.net.MacAddressCompat
|
||||||
import be.mygod.vpnhotspot.net.TetherType
|
import be.mygod.vpnhotspot.net.TetherType
|
||||||
import be.mygod.vpnhotspot.room.macToLong
|
|
||||||
|
|
||||||
class WifiP2pClient(p2pInterface: String, p2p: WifiP2pDevice) : Client(p2p.deviceAddress!!.macToLong(), p2pInterface) {
|
class WifiP2pClient(p2pInterface: String, p2p: WifiP2pDevice) :
|
||||||
|
Client(MacAddressCompat.fromString(p2p.deviceAddress!!), p2pInterface) {
|
||||||
override val icon: Int get() = TetherType.WIFI_P2P.icon
|
override val icon: Int get() = TetherType.WIFI_P2P.icon
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package be.mygod.vpnhotspot.net
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.system.ErrnoException
|
import android.system.ErrnoException
|
||||||
import android.system.OsConstants
|
import android.system.OsConstants
|
||||||
import be.mygod.vpnhotspot.room.macToLong
|
|
||||||
import be.mygod.vpnhotspot.util.parseNumericAddress
|
import be.mygod.vpnhotspot.util.parseNumericAddress
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -13,7 +12,7 @@ import java.net.InetAddress
|
|||||||
import java.net.NetworkInterface
|
import java.net.NetworkInterface
|
||||||
import java.net.SocketException
|
import java.net.SocketException
|
||||||
|
|
||||||
data class IpNeighbour(val ip: InetAddress, val dev: String, val lladdr: Long, val state: State) {
|
data class IpNeighbour(val ip: InetAddress, val dev: String, val lladdr: MacAddressCompat, val state: State) {
|
||||||
enum class State {
|
enum class State {
|
||||||
INCOMPLETE, VALID, FAILED, DELETING
|
INCOMPLETE, VALID, FAILED, DELETING
|
||||||
}
|
}
|
||||||
@@ -56,12 +55,12 @@ data class IpNeighbour(val ip: InetAddress, val dev: String, val lladdr: Long, v
|
|||||||
else -> throw IllegalArgumentException("Unknown state encountered: ${match.groupValues[7]}")
|
else -> throw IllegalArgumentException("Unknown state encountered: ${match.groupValues[7]}")
|
||||||
}
|
}
|
||||||
val mac = try {
|
val mac = try {
|
||||||
lladdr.macToLong()
|
MacAddressCompat.fromString(lladdr)
|
||||||
} catch (e: NumberFormatException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
if (match.groups[4] == null) return emptyList()
|
if (match.groups[4] == null) return emptyList()
|
||||||
// for DELETING, we only care about IP address and do not care if MAC is not present
|
// for DELETING, we only care about IP address and do not care if MAC is not present
|
||||||
if (state != State.DELETING) Timber.w(IOException("Failed to find MAC address for $line"))
|
if (state != State.DELETING) Timber.w(IOException("Failed to find MAC address for $line"))
|
||||||
0L
|
MacAddressCompat.ALL_ZEROS_ADDRESS
|
||||||
}
|
}
|
||||||
val result = IpNeighbour(ip, dev, mac, state)
|
val result = IpNeighbour(ip, dev, mac, state)
|
||||||
val devParser = devFallback.matchEntire(dev)
|
val devParser = devFallback.matchEntire(dev)
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package be.mygod.vpnhotspot.net
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.ByteOrder
|
||||||
|
|
||||||
|
inline class MacAddressCompat(val addr: Long) {
|
||||||
|
companion object {
|
||||||
|
private const val ETHER_ADDR_LEN = 6
|
||||||
|
/**
|
||||||
|
* The MacAddress zero MAC address.
|
||||||
|
*
|
||||||
|
* Not publicly exposed or treated specially since the OUI 00:00:00 is registered.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
val ALL_ZEROS_ADDRESS = MacAddressCompat(0)
|
||||||
|
|
||||||
|
fun bytesToString(addr: ByteArray): String {
|
||||||
|
require(addr.size == ETHER_ADDR_LEN) { addr.contentToString() + " was not a valid MAC address" }
|
||||||
|
return addr.joinToString(":") { "%02x".format(it) }
|
||||||
|
}
|
||||||
|
fun bytesToString(addr: Collection<Byte>): String {
|
||||||
|
require(addr.size == ETHER_ADDR_LEN) { addr.joinToString() + " was not a valid MAC address" }
|
||||||
|
return addr.joinToString(":") { "%02x".format(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IllegalArgumentException::class)
|
||||||
|
fun fromString(addr: String) = MacAddressCompat(ByteBuffer.allocate(8).run {
|
||||||
|
order(ByteOrder.LITTLE_ENDIAN)
|
||||||
|
mark()
|
||||||
|
try {
|
||||||
|
put(addr.split(':').map { Integer.parseInt(it, 16).toByte() }.toByteArray())
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
throw IllegalArgumentException(e)
|
||||||
|
}
|
||||||
|
reset()
|
||||||
|
long
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString() = ByteBuffer.allocate(8).run {
|
||||||
|
order(ByteOrder.LITTLE_ENDIAN)
|
||||||
|
putLong(addr)
|
||||||
|
bytesToString(array().take(6))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -208,7 +208,7 @@ class Routing(private val caller: Any, private val downstream: String,
|
|||||||
private val upstream = Upstream(RULE_PRIORITY_UPSTREAM)
|
private val upstream = Upstream(RULE_PRIORITY_UPSTREAM)
|
||||||
private var disableSystem: RootSession.Transaction? = null
|
private var disableSystem: RootSession.Transaction? = null
|
||||||
|
|
||||||
private inner class Client(private val ip: Inet4Address, mac: Long) : AutoCloseable {
|
private inner class Client(private val ip: Inet4Address, mac: MacAddressCompat) : AutoCloseable {
|
||||||
private val transaction = RootSession.beginTransaction().safeguard {
|
private val transaction = RootSession.beginTransaction().safeguard {
|
||||||
val address = ip.hostAddress
|
val address = ip.hostAddress
|
||||||
iptablesInsert("vpnhotspot_acl -i $downstream -s $address -j ACCEPT")
|
iptablesInsert("vpnhotspot_acl -i $downstream -s $address -j ACCEPT")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package be.mygod.vpnhotspot.net.monitor
|
package be.mygod.vpnhotspot.net.monitor
|
||||||
|
|
||||||
import android.util.LongSparseArray
|
import android.util.LongSparseArray
|
||||||
|
import be.mygod.vpnhotspot.net.MacAddressCompat
|
||||||
import be.mygod.vpnhotspot.net.Routing.Companion.IPTABLES
|
import be.mygod.vpnhotspot.net.Routing.Companion.IPTABLES
|
||||||
import be.mygod.vpnhotspot.room.AppDatabase
|
import be.mygod.vpnhotspot.room.AppDatabase
|
||||||
import be.mygod.vpnhotspot.room.TrafficRecord
|
import be.mygod.vpnhotspot.room.TrafficRecord
|
||||||
@@ -20,8 +21,8 @@ object TrafficRecorder {
|
|||||||
private val records = mutableMapOf<Pair<InetAddress, String>, TrafficRecord>()
|
private val records = mutableMapOf<Pair<InetAddress, String>, TrafficRecord>()
|
||||||
val foregroundListeners = Event2<Collection<TrafficRecord>, LongSparseArray<TrafficRecord>>()
|
val foregroundListeners = Event2<Collection<TrafficRecord>, LongSparseArray<TrafficRecord>>()
|
||||||
|
|
||||||
fun register(ip: InetAddress, downstream: String, mac: Long) {
|
fun register(ip: InetAddress, downstream: String, mac: MacAddressCompat) {
|
||||||
val record = TrafficRecord(mac = mac, ip = ip, downstream = downstream)
|
val record = TrafficRecord(mac = mac.addr, ip = ip, downstream = downstream)
|
||||||
AppDatabase.instance.trafficRecordDao.insert(record)
|
AppDatabase.instance.trafficRecordDao.insert(record)
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
Timber.d("Registering $ip%$downstream")
|
Timber.d("Registering $ip%$downstream")
|
||||||
@@ -149,5 +150,5 @@ object TrafficRecorder {
|
|||||||
/**
|
/**
|
||||||
* Possibly inefficient. Don't call this too often.
|
* Possibly inefficient. Don't call this too often.
|
||||||
*/
|
*/
|
||||||
fun isWorking(mac: Long) = records.values.any { it.mac == mac }
|
fun isWorking(mac: MacAddressCompat) = records.values.any { it.mac == mac.addr }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package be.mygod.vpnhotspot.room
|
|||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.map
|
import androidx.lifecycle.map
|
||||||
import androidx.room.*
|
import androidx.room.*
|
||||||
|
import be.mygod.vpnhotspot.net.MacAddressCompat
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
data class ClientRecord(@PrimaryKey
|
data class ClientRecord(@PrimaryKey
|
||||||
@@ -14,7 +15,7 @@ data class ClientRecord(@PrimaryKey
|
|||||||
abstract class Dao {
|
abstract class Dao {
|
||||||
@Query("SELECT * FROM `ClientRecord` WHERE `mac` = :mac")
|
@Query("SELECT * FROM `ClientRecord` WHERE `mac` = :mac")
|
||||||
protected abstract fun lookupBlocking(mac: Long): ClientRecord?
|
protected abstract fun lookupBlocking(mac: Long): ClientRecord?
|
||||||
fun lookupOrDefaultBlocking(mac: Long) = lookupBlocking(mac) ?: ClientRecord(mac)
|
fun lookupOrDefaultBlocking(mac: MacAddressCompat) = lookupBlocking(mac.addr) ?: ClientRecord(mac.addr)
|
||||||
|
|
||||||
@Query("SELECT * FROM `ClientRecord` WHERE `mac` = :mac")
|
@Query("SELECT * FROM `ClientRecord` WHERE `mac` = :mac")
|
||||||
protected abstract suspend fun lookup(mac: Long): ClientRecord?
|
protected abstract suspend fun lookup(mac: Long): ClientRecord?
|
||||||
@@ -22,14 +23,15 @@ data class ClientRecord(@PrimaryKey
|
|||||||
|
|
||||||
@Query("SELECT * FROM `ClientRecord` WHERE `mac` = :mac")
|
@Query("SELECT * FROM `ClientRecord` WHERE `mac` = :mac")
|
||||||
protected abstract fun lookupSync(mac: Long): LiveData<ClientRecord?>
|
protected abstract fun lookupSync(mac: Long): LiveData<ClientRecord?>
|
||||||
fun lookupOrDefaultSync(mac: Long) = lookupSync(mac).map { it ?: ClientRecord(mac) }
|
fun lookupOrDefaultSync(mac: MacAddressCompat) = lookupSync(mac.addr).map { it ?: ClientRecord(mac.addr) }
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
protected abstract suspend fun updateInternal(value: ClientRecord): Long
|
protected abstract suspend fun updateInternal(value: ClientRecord): Long
|
||||||
suspend fun update(value: ClientRecord) = check(updateInternal(value) == value.mac)
|
suspend fun update(value: ClientRecord) = check(updateInternal(value) == value.mac)
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
open suspend fun upsert(mac: Long, operation: suspend ClientRecord.() -> Unit) = lookupOrDefault(mac).apply {
|
open suspend fun upsert(mac: MacAddressCompat, operation: suspend ClientRecord.() -> Unit) = lookupOrDefault(
|
||||||
|
mac.addr).apply {
|
||||||
operation()
|
operation()
|
||||||
update(this)
|
update(this)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import androidx.room.TypeConverter
|
|||||||
import be.mygod.vpnhotspot.util.useParcel
|
import be.mygod.vpnhotspot.util.useParcel
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.ByteOrder
|
|
||||||
|
|
||||||
object Converters {
|
object Converters {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@@ -37,18 +35,3 @@ object Converters {
|
|||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun unpersistInetAddress(data: ByteArray): InetAddress = InetAddress.getByAddress(data)
|
fun unpersistInetAddress(data: ByteArray): InetAddress = InetAddress.getByAddress(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.macToLong(): Long = ByteBuffer.allocate(8).run {
|
|
||||||
order(ByteOrder.LITTLE_ENDIAN)
|
|
||||||
mark()
|
|
||||||
put(split(':').map { Integer.parseInt(it, 16).toByte() }.toByteArray())
|
|
||||||
reset()
|
|
||||||
long
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Iterable<Byte>.macToString() = joinToString(":") { "%02x".format(it) }
|
|
||||||
fun Long.macToString(): String = ByteBuffer.allocate(8).run {
|
|
||||||
order(ByteOrder.LITTLE_ENDIAN)
|
|
||||||
putLong(this@macToString)
|
|
||||||
array().take(6).macToString()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import androidx.databinding.BindingAdapter
|
|||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import be.mygod.vpnhotspot.room.macToString
|
import be.mygod.vpnhotspot.net.MacAddressCompat
|
||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.net.NetworkInterface
|
import java.net.NetworkInterface
|
||||||
@@ -94,7 +94,7 @@ fun makeMacSpan(mac: String) = if (app.hasTouch) SpannableString(mac).apply {
|
|||||||
|
|
||||||
fun NetworkInterface.formatAddresses(macOnly: Boolean = false) = SpannableStringBuilder().apply {
|
fun NetworkInterface.formatAddresses(macOnly: Boolean = false) = SpannableStringBuilder().apply {
|
||||||
try {
|
try {
|
||||||
hardwareAddress?.apply { appendln(makeMacSpan(asIterable().macToString())) }
|
hardwareAddress?.let { appendln(makeMacSpan(MacAddressCompat.bytesToString(it))) }
|
||||||
} catch (_: SocketException) { }
|
} catch (_: SocketException) { }
|
||||||
if (!macOnly) for (address in interfaceAddresses) {
|
if (!macOnly) for (address in interfaceAddresses) {
|
||||||
append(makeIpSpan(address.address))
|
append(makeIpSpan(address.address))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package be.mygod.vpnhotspot.room
|
package be.mygod.vpnhotspot.net
|
||||||
|
|
||||||
import org.junit.Assert.*
|
import org.junit.Assert.*
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -7,7 +7,7 @@ class ConvertersTest {
|
|||||||
@Test
|
@Test
|
||||||
fun macSerialization() {
|
fun macSerialization() {
|
||||||
for (test in listOf("01:23:45:67:89:ab", "DE:AD:88:88:BE:EF")) {
|
for (test in listOf("01:23:45:67:89:ab", "DE:AD:88:88:BE:EF")) {
|
||||||
assertTrue(test.equals(test.macToLong().macToString(), true))
|
assertTrue(test.equals(MacAddressCompat.fromString(test).toString(), true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user