diff --git a/README.md b/README.md index 2409c0ec..0e4dc89e 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,6 @@ Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded * (since API 26) [`Landroid/net/wifi/WifiManager;->cancelLocalOnlyHotspotRequest()V,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#154947s) * (prior to API 26) `Landroid/net/wifi/WifiManager;->setWifiApEnabled(Landroid/net/wifi/WifiConfiguration;Z)Z` * [`Landroid/net/wifi/p2p/WifiP2pConfig$Builder;->MAC_ANY_ADDRESS:Landroid/net/MacAddress;,blacklist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#157883) -* (prior to API 30) [`Landroid/net/wifi/p2p/WifiP2pManager;->WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION:Ljava/lang/String;,greylist-max-o`](https://android.googlesource.com/platform/prebuilts/runtime/+/3d07e5c/appcompat/hiddenapi-flags.csv#134686) * [`Landroid/net/wifi/p2p/WifiP2pManager;->startWps(Landroid/net/wifi/p2p/WifiP2pManager$Channel;Landroid/net/wifi/WpsInfo;Landroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#158332) * (since API 28) [`Landroid/provider/Settings$Global;->SOFT_AP_TIMEOUT_ENABLED:Ljava/lang/String;,greylist-max-o`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#183735) * (prior to API 30) [`Lcom/android/internal/R$array;->config_tether_bluetooth_regexs:I,greylist-max-q`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#272546) @@ -251,7 +250,6 @@ Hidden whitelisted APIs: (same catch as above, however, things in this list are * (prior to API 30) [`Landroid/net/wifi/WifiManager;->setWifiApConfiguration(Landroid/net/wifi/WifiConfiguration;)Z,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#155067) * [`Landroid/net/wifi/p2p/WifiP2pGroupList;->getGroupList()Ljava/util/List;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#158079) * [`Landroid/net/wifi/p2p/WifiP2pManager$PersistentGroupInfoListener;->onPersistentGroupInfoAvailable(Landroid/net/wifi/p2p/WifiP2pGroupList;)V,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#158156) -* (since API 30) [`Landroid/net/wifi/p2p/WifiP2pManager;->ACTION_WIFI_P2P_PERSISTENT_GROUPS_CHANGED:Ljava/lang/String;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#158160) * [`Landroid/net/wifi/p2p/WifiP2pManager;->deletePersistentGroup(Landroid/net/wifi/p2p/WifiP2pManager$Channel;ILandroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#158296) * [`Landroid/net/wifi/p2p/WifiP2pManager;->requestPersistentGroupInfo(Landroid/net/wifi/p2p/WifiP2pManager$Channel;Landroid/net/wifi/p2p/WifiP2pManager$PersistentGroupInfoListener;)V,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#158320) * [`Landroid/net/wifi/p2p/WifiP2pManager;->setWifiP2pChannels(Landroid/net/wifi/p2p/WifiP2pManager$Channel;IILandroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#158330) @@ -270,7 +268,6 @@ Nonexported system resources: Other: * (since API 29) `android.net.wifi.p2p.WifiP2pConfig` needs to be parcelized in a very specific order, except for possible extra fields at the end. (used only for safe mode) -* Broadcast `android.net.wifi.p2p.PERSISTENT_GROUPS_CHANGED` is assumed to be sticky. * Activity `com.android.settings/.Settings$TetherSettingsActivity` is assumed to be exported. For `ip rule` priorities, `RULE_PRIORITY_SECURE_VPN` and `RULE_PRIORITY_TETHERING` is assumed to be 12000 and 18000 respectively; diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt index 6e674779..9eed9741 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt @@ -32,7 +32,6 @@ import be.mygod.vpnhotspot.widget.SmartSnackbar import kotlinx.coroutines.* import timber.log.Timber import java.lang.reflect.InvocationTargetException -import java.net.NetworkInterface import java.util.concurrent.atomic.AtomicBoolean /** @@ -115,6 +114,57 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene } val groupChanged = StickyEvent1 { group } + @SuppressLint("NewApi") // networkId is available since Android 4.2 + suspend fun fetchPersistentGroup() { + val ownerAddress = lastMac?.let(MacAddressCompat.Companion::fromString) ?: try { + P2pSupplicantConfiguration().apply { init() }.bssid + } catch (e: Exception) { + Timber.d(e) + null + } ?: return + val channel = channel ?: return + fun Collection.filterUselessGroups(): List { + if (isNotEmpty()) persistentSupported = true + val ownedGroups = filter { + if (!it.isGroupOwner) return@filter false + val address = MacAddressCompat.fromString(it.owner.deviceAddress) + // WifiP2pServiceImpl only removes self address + Build.VERSION.SDK_INT >= 29 && address == MacAddressCompat.ANY_ADDRESS || address == ownerAddress + } + val main = ownedGroups.minBy { it.networkId } + // do not replace current group if it's better + if (binder.group?.passphrase == null) binder.group = main + return if (main != null) ownedGroups.filter { it.networkId != main.networkId } else emptyList() + } + fun Int?.print(group: WifiP2pGroup) { + if (this == null) Timber.i("Removed redundant owned group: $group") + else SmartSnackbar.make(formatReason(R.string.repeater_clean_pog_failure, this)).show() + } + // we only get empty list on permission denial. Is there a better permission check? + if (Build.VERSION.SDK_INT < 30 || checkSelfPermission("android.permission.READ_WIFI_CREDENTIAL") == + PackageManager.PERMISSION_GRANTED) try { + for (group in p2pManager.requestPersistentGroupInfo(channel).filterUselessGroups()) { + p2pManager.deletePersistentGroup(channel, group.networkId).print(group) + } + return + } catch (e: ReflectiveOperationException) { + Timber.w(e) + } + try { + RootManager.use { server -> + if (deinitPending.getAndSet(false)) server.execute(RepeaterCommands.Deinit()) + @Suppress("UNCHECKED_CAST") + val groups = server.execute(RepeaterCommands.RequestPersistentGroupInfo()).value as List + for (group in groups.filterUselessGroups()) { + server.execute(RepeaterCommands.DeletePersistentGroup(group.networkId))?.value.print(group) + } + } + } catch (e: Exception) { + Timber.w(e) + SmartSnackbar.make(e).show() + } + } + fun startWps(pin: String? = null) { val channel = channel if (channel == null) SmartSnackbar.make(R.string.repeater_failure_disconnected).show() @@ -154,14 +204,9 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene } } private val deviceListener = broadcastReceiver { _, intent -> - when (intent.action) { - WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> { - val addr = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE)?.deviceAddress - if (!addr.isNullOrEmpty() && (Build.VERSION.SDK_INT < 29 || - MacAddressCompat.fromString(addr) != MacAddressCompat.ANY_ADDRESS)) lastMac = addr - } - WifiP2pManagerHelper.ACTION_WIFI_P2P_PERSISTENT_GROUPS_CHANGED -> if (!safeMode) onPersistentGroupsChanged() - } + val addr = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE)?.deviceAddress + if (!addr.isNullOrEmpty() && (Build.VERSION.SDK_INT < 29 || + MacAddressCompat.fromString(addr) != MacAddressCompat.ANY_ADDRESS)) lastMac = addr } /** * Writes and critical reads to routingManager should be protected with this context. @@ -193,8 +238,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene override fun onCreate() { super.onCreate() onChannelDisconnected() - registerReceiver(deviceListener, intentFilter(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION, - WifiP2pManagerHelper.ACTION_WIFI_P2P_PERSISTENT_GROUPS_CHANGED)) + registerReceiver(deviceListener, intentFilter(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)) app.pref.registerOnSharedPreferenceChangeListener(this) } @@ -251,62 +295,10 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene KEY_SAFE_MODE -> { deinitPending.set(true) setOperatingChannel() - onPersistentGroupsChanged() } } } - @SuppressLint("NewApi") // networkId is available since Android 4.2 - private fun onPersistentGroupsChanged() = launch { - val ownerAddress = lastMac?.let(MacAddressCompat.Companion::fromString) ?: try { - P2pSupplicantConfiguration().apply { init() }.bssid - } catch (e: Exception) { - Timber.d(e) - null - } ?: return@launch - val channel = channel ?: return@launch - fun Collection.filterUselessGroups(): List { - if (isNotEmpty()) persistentSupported = true - val ownedGroups = filter { - if (!it.isGroupOwner) return@filter false - val address = MacAddressCompat.fromString(it.owner.deviceAddress) - // WifiP2pServiceImpl only removes self address - Build.VERSION.SDK_INT >= 29 && address == MacAddressCompat.ANY_ADDRESS || address == ownerAddress - } - val main = ownedGroups.minBy { it.networkId } - // do not replace current group if it's better - if (binder.group?.passphrase == null) binder.group = main - return if (main != null) ownedGroups.filter { it.networkId != main.networkId } else emptyList() - } - fun Int?.print(group: WifiP2pGroup) { - if (this == null) Timber.i("Removed redundant owned group: $group") - else SmartSnackbar.make(formatReason(R.string.repeater_clean_pog_failure, this)).show() - } - // we only get empty list on permission denial. Is there a better permission check? - if (Build.VERSION.SDK_INT < 30 || checkSelfPermission("android.permission.READ_WIFI_CREDENTIAL") == - PackageManager.PERMISSION_GRANTED) try { - for (group in p2pManager.requestPersistentGroupInfo(channel).filterUselessGroups()) { - p2pManager.deletePersistentGroup(channel, group.networkId).print(group) - } - return@launch - } catch (e: ReflectiveOperationException) { - Timber.w(e) - } - try { - RootManager.use { server -> - if (deinitPending.getAndSet(false)) server.execute(RepeaterCommands.Deinit()) - @Suppress("UNCHECKED_CAST") - val groups = server.execute(RepeaterCommands.RequestPersistentGroupInfo()).value as List - for (group in groups.filterUselessGroups()) { - server.execute(RepeaterCommands.DeletePersistentGroup(group.networkId))?.value.print(group) - } - } - } catch (e: Exception) { - Timber.w(e) - SmartSnackbar.make(e).show() - } - } - /** * startService Step 1 */ @@ -328,7 +320,9 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene else -> { Timber.i("Removing old group ($it)") p2pManager.removeGroup(channel, object : WifiP2pManager.ActionListener { - override fun onSuccess() = doStart() + override fun onSuccess() { + doStart() + } override fun onFailure(reason: Int) = startFailure(formatReason(R.string.repeater_remove_old_group_failure, reason)) }) @@ -345,7 +339,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene /** * startService Step 2 (if a group isn't already available) */ - private fun doStart() { + private fun doStart() = launch { val listener = object : WifiP2pManager.ActionListener { override fun onFailure(reason: Int) { startFailure(formatReason(R.string.repeater_create_group_failure, reason), @@ -353,7 +347,8 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene } override fun onSuccess() { } // wait for WIFI_P2P_CONNECTION_CHANGED_ACTION to fire to go to step 3 } - val channel = channel ?: return listener.onFailure(WifiP2pManager.BUSY) + val channel = channel ?: return@launch listener.onFailure(WifiP2pManager.BUSY) + if (!safeMode) binder.fetchPersistentGroup() val networkName = networkName val passphrase = passphrase try { @@ -431,11 +426,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene persistNextGroup = false } check(routingManager == null) - routingManager = object : RoutingManager.LocalOnly(this@RepeaterService, group.`interface`!!) { - override fun ifaceHandler(iface: NetworkInterface) { - iface.hardwareAddress?.let { lastMac = MacAddressCompat.bytesToString(it) } - } - }.apply { start() } + routingManager = RoutingManager.LocalOnly(this@RepeaterService, group.`interface`!!).apply { start() } status = Status.ACTIVE showNotification(group) } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RoutingManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RoutingManager.kt index 6fb97843..768a7bd2 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/RoutingManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/RoutingManager.kt @@ -48,7 +48,7 @@ abstract class RoutingManager(private val caller: Any, val downstream: String, p /** * Both repeater and local-only hotspot are Wi-Fi based. */ - open class LocalOnly(caller: Any, downstream: String) : RoutingManager(caller, downstream, true) { + class LocalOnly(caller: Any, downstream: String) : RoutingManager(caller, downstream, true) { override fun Routing.configure() { ipForward() // local only interfaces need to enable ip_forward forward() @@ -84,10 +84,8 @@ abstract class RoutingManager(private val caller: Any, val downstream: String, p } } - open fun ifaceHandler(iface: NetworkInterface) { } - private fun initRoutingLocked() = try { - routing = Routing(caller, downstream, this::ifaceHandler).apply { + routing = Routing(caller, downstream).apply { try { configure() } catch (e: Exception) { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt index 65a32fee..4c5268e4 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt @@ -202,8 +202,8 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic bssid = RepeaterService.deviceAddress } to false } - } else { - val group = binder?.group + } else binder?.let { binder -> + val group = binder.group ?: binder.fetchPersistentGroup().let { binder.group } if (group != null) return SoftApConfigurationCompat.empty().run { ssid = group.networkName securityType = SoftApConfiguration.SECURITY_TYPE_WPA2_PSK // is not actually used diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt index a58c82b8..2ba7bba7 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt @@ -28,8 +28,7 @@ import java.net.SocketException * * Once revert is called, this object no longer serves any purpose. */ -class Routing(private val caller: Any, private val downstream: String, - ifaceHandler: (NetworkInterface) -> Unit) : IpNeighbourMonitor.Callback { +class Routing(private val caller: Any, private val downstream: String) : IpNeighbourMonitor.Callback { companion object { /** * Since Android 5.0, RULE_PRIORITY_TETHERING = 18000. @@ -130,7 +129,6 @@ class Routing(private val caller: Any, private val downstream: String, private val hostAddress = try { val iface = NetworkInterface.getByName(downstream) ?: error("iface not found") - ifaceHandler(iface) val addresses = iface.interfaceAddresses!!.filter { it.address is Inet4Address } if (addresses.size > 1) error("More than one addresses was found: $addresses") addresses.first() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt index ce248d6a..6a617325 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt @@ -4,7 +4,6 @@ import android.annotation.SuppressLint import android.net.wifi.WpsInfo import android.net.wifi.p2p.WifiP2pGroup import android.net.wifi.p2p.WifiP2pManager -import android.os.Build import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.util.callSuper import kotlinx.coroutines.CompletableDeferred @@ -27,9 +26,6 @@ object WifiP2pManagerHelper { } const val UNSUPPORTED = -2 - val ACTION_WIFI_P2P_PERSISTENT_GROUPS_CHANGED = if (Build.VERSION.SDK_INT >= 30) { - "android.net.wifi.p2p.action.WIFI_P2P_PERSISTENT_GROUPS_CHANGED" - } else "android.net.wifi.p2p.PERSISTENT_GROUPS_CHANGED" /** * Available since Android 4.4.