Implement client limiting and MAC address filtering

This commit is contained in:
Mygod
2020-07-04 05:43:24 +08:00
parent 46d0544df2
commit 278f3f143b
6 changed files with 133 additions and 13 deletions

View File

@@ -1,7 +1,9 @@
package be.mygod.vpnhotspot.manage
import android.annotation.TargetApi
import android.content.ClipData
import android.content.Intent
import android.net.MacAddress
import android.os.Build
import android.provider.Settings
import android.view.View
@@ -161,6 +163,14 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
capability = maxSupportedClients to supportedFeatures
data.notifyChange()
}
override fun onBlockedClientConnecting(client: MacAddress, blockedReason: Int) {
SmartSnackbar.make(parent.getString(R.string.tethering_manage_wifi_client_blocked, client,
WifiApManager.clientBlockLookup(blockedReason, true))).apply {
action(R.string.tethering_manage_wifi_copy_mac) {
app.clipboard.setPrimaryClip(ClipData.newPlainText(null, client.toString()))
}
}.show()
}
override val title get() = parent.getString(R.string.tethering_manage_wifi)
override val tetherType get() = TetherType.WIFI

View File

@@ -33,9 +33,9 @@ data class SoftApConfigurationCompat(
@TargetApi(30)
var isClientControlByUserEnabled: Boolean = false,
@RequiresApi(30)
var blockedClientList: List<MacAddress?> = emptyList(),
var blockedClientList: List<MacAddress> = emptyList(),
@RequiresApi(30)
var allowedClientList: List<MacAddress?> = emptyList(),
var allowedClientList: List<MacAddress> = emptyList(),
var underlying: Parcelable? = null) : Parcelable {
companion object {
const val BAND_2GHZ = 1
@@ -238,8 +238,8 @@ data class SoftApConfigurationCompat(
isAutoShutdownEnabled(this) as Boolean,
getShutdownTimeoutMillis(this) as Long,
isClientControlByUserEnabled(this) as Boolean,
getBlockedClientList(this) as List<MacAddress?>,
getAllowedClientList(this) as List<MacAddress?>,
getBlockedClientList(this) as List<MacAddress>,
getAllowedClientList(this) as List<MacAddress>,
this)
}

View File

@@ -1,8 +1,10 @@
package be.mygod.vpnhotspot.net.wifi
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.content.ClipData
import android.content.DialogInterface
import android.net.MacAddress
import android.net.wifi.SoftApConfiguration
import android.os.Build
import android.os.Parcelable
@@ -42,6 +44,7 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
Toolbar.OnMenuItemClickListener {
companion object {
private const val BASE64_FLAGS = Base64.NO_PADDING or Base64.NO_WRAP
private val nonMacChars = "[^0-9a-fA-F:]+".toRegex()
private val channels by lazy {
val list = ArrayList<BandOption.Channel>()
for (chan in 1..14) list.add(BandOption.Channel(SoftApConfigurationCompat.BAND_2GHZ, chan))
@@ -109,7 +112,7 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
securityType = dialogView.security.selectedItemPosition
isHiddenSsid = dialogView.hiddenSsid.isChecked
}
if (full) {
if (full) @TargetApi(28) {
isAutoShutdownEnabled = dialogView.autoShutdown.isChecked
shutdownTimeoutMillis = dialogView.timeout.text.let { text ->
if (text.isNullOrEmpty()) 0 else text.toString().toLong()
@@ -120,6 +123,14 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
bssid = if (dialogView.bssid.length() != 0) {
MacAddressCompat.fromString(dialogView.bssid.text.toString())
} else null
maxNumberOfClients = dialogView.maxClient.text.let { text ->
if (text.isNullOrEmpty()) 0 else text.toString().toInt()
}
isClientControlByUserEnabled = dialogView.clientUserControl.isChecked
allowedClientList = (dialogView.allowedList.text ?: "").split(nonMacChars)
.filter { it.isNotEmpty() }.map { MacAddress.fromString(it) }
blockedClientList = (dialogView.blockedList.text ?: "").split(nonMacChars)
.filter { it.isNotEmpty() }.map { MacAddress.fromString(it) }
}
}
@@ -150,7 +161,7 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
if (arg.p2pMode || Build.VERSION.SDK_INT >= 30) {
dialogView.timeoutWrapper.helperText = getString(R.string.wifi_hotspot_timeout_default,
TetherTimeoutMonitor.defaultTimeout)
if (!arg.readOnly) dialogView.timeout.addTextChangedListener(this@WifiApDialogFragment)
dialogView.timeout.addTextChangedListener(this@WifiApDialogFragment)
} else dialogView.timeoutWrapper.isGone = true
if (Build.VERSION.SDK_INT >= 23 || arg.p2pMode) dialogView.band.apply {
bandOptions = mutableListOf<BandOption>().apply {
@@ -175,6 +186,16 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
} else dialogView.bandWrapper.isGone = true
dialogView.bssid.addTextChangedListener(this@WifiApDialogFragment)
if (arg.p2pMode) dialogView.hiddenSsid.isGone = true
if (arg.p2pMode || Build.VERSION.SDK_INT < 30) {
dialogView.maxClientWrapper.isGone = true
dialogView.clientUserControl.isGone = true
dialogView.blockedListWrapper.isGone = true
dialogView.allowedListWrapper.isGone = true
} else {
dialogView.maxClient.addTextChangedListener(this@WifiApDialogFragment)
dialogView.blockedList.addTextChangedListener(this@WifiApDialogFragment)
dialogView.allowedList.addTextChangedListener(this@WifiApDialogFragment)
}
base = arg.configuration
populateFromConfiguration()
}
@@ -193,18 +214,19 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
}
dialogView.bssid.setText(base.bssid?.toString())
dialogView.hiddenSsid.isChecked = base.isHiddenSsid
// TODO support more fields from SACC
dialogView.maxClient.setText(base.maxNumberOfClients.let { if (it == 0) "" else it.toString() })
dialogView.clientUserControl.isChecked = base.isClientControlByUserEnabled
dialogView.blockedList.setText(base.blockedClientList.joinToString("\n"))
dialogView.allowedList.setText(base.allowedClientList.joinToString("\n"))
}
override fun onStart() {
super.onStart()
started = true
if (!arg.readOnly) validate()
validate()
}
/**
* This function is reached only if not arg.readOnly.
*/
@TargetApi(28)
private fun validate() {
if (!started) return
val ssidLength = dialogView.ssid.text.toString().toByteArray().size
@@ -239,9 +261,36 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
dialogView.bssidWrapper.error = e.readableMessage
false
}
val maxClientError = dialogView.maxClient.text.let { text ->
if (text.isNullOrEmpty()) null else try {
text.toString().toInt()
null
} catch (e: NumberFormatException) {
e.readableMessage
}
}
dialogView.maxClientWrapper.error = maxClientError
val blockedListError = try {
(dialogView.blockedList.text ?: "").split(nonMacChars)
.filter { it.isNotEmpty() }.forEach { MacAddress.fromString(it) }
null
} catch (e: IllegalArgumentException) {
e.readableMessage
}
dialogView.blockedListWrapper.error = blockedListError
val allowedListError = try {
(dialogView.allowedList.text ?: "").split(nonMacChars)
.filter { it.isNotEmpty() }.forEach { MacAddress.fromString(it) }
null
} catch (e: IllegalArgumentException) {
e.readableMessage
}
dialogView.allowedListWrapper.error = allowedListError
val canCopy = timeoutError == null && bssidValid && maxClientError == null && blockedListError == null &&
allowedListError == null
(dialog as? AlertDialog)?.getButton(DialogInterface.BUTTON_POSITIVE)?.isEnabled =
ssidLength in 1..32 && passwordValid && timeoutError == null && bssidValid
dialogView.toolbar.menu.findItem(android.R.id.copy).isEnabled = timeoutError == null && bssidValid
ssidLength in 1..32 && passwordValid && canCopy
dialogView.toolbar.menu.findItem(android.R.id.copy).isEnabled = canCopy
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { }

View File

@@ -97,6 +97,8 @@ object WifiApManager {
@RequiresApi(28)
val failureReasonLookup = ConstantLookup<WifiManager>("SAP_START_FAILURE_",
"SAP_START_FAILURE_GENERAL", "SAP_START_FAILURE_NO_CHANNEL")
@get:RequiresApi(30)
val clientBlockLookup by lazy { ConstantLookup<WifiManager>("SAP_CLIENT_") }
private val interfaceSoftApCallback by lazy { Class.forName("android.net.wifi.WifiManager\$SoftApCallback") }
private val registerSoftApCallback by lazy {

View File

@@ -143,6 +143,7 @@
android:layout_marginTop="8dip"
app:counterEnabled="true"
app:counterMaxLength="17"
app:errorEnabled="true"
android:hint="@string/wifi_advanced_mac_address_title">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/bssid"
@@ -159,6 +160,58 @@
android:layout_marginTop="8dip"
style="@style/wifi_item_label"
android:text="@string/wifi_hidden_network"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/max_client_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dip"
app:counterEnabled="true"
app:counterMaxLength="10"
app:errorEnabled="true"
android:hint="@string/wifi_max_clients">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/max_client"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/wifi_item_edit_content"
android:inputType="number"
android:maxLength="10" />
</com.google.android.material.textfield.TextInputLayout>
<Switch
android:id="@+id/client_user_control"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dip"
style="@style/wifi_item_label"
android:text="@string/wifi_client_user_control"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/blocked_list_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dip"
android:hint="@string/wifi_blocked_list"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/blocked_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/wifi_item_edit_content"
android:inputType="textMultiLine|textNoSuggestions" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/allowed_list_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dip"
android:hint="@string/wifi_allowed_list"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/allowed_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/wifi_item_edit_content"
android:inputType="textMultiLine|textNoSuggestions" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@@ -70,6 +70,8 @@
<string name="tethering_manage_wifi_info">%1$d MHz, channel %2$d, width %3$s</string>
<string name="tethering_manage_wifi_capabilities">%1$s/%2$d clients connected\nSupported features: %3$s</string>
<string name="tethering_manage_wifi_no_features">None</string>
<string name="tethering_manage_wifi_client_blocked">Blocked %1$s: %2$s</string>
<string name="tethering_manage_wifi_copy_mac">Copy MAC</string>
<string name="connected_state_incomplete">" (connecting)"</string>
<string name="connected_state_valid">" (reachable)"</string>
@@ -184,6 +186,10 @@
<string name="wifi_ap_choose_6G">6.0 GHz Band</string>
<string name="wifi_advanced_mac_address_title">MAC address</string>
<string name="wifi_hidden_network">Hidden network</string>
<string name="wifi_max_clients">Maximum number of clients</string>
<string name="wifi_client_user_control">Control which client can use hotspot</string>
<string name="wifi_blocked_list">Blocked list of clients</string>
<string name="wifi_allowed_list">Allowed list of clients</string>
<string name="wifi_save">Save</string>
<!-- Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/res/values/donations__strings.xml -->