summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoshan Pius <rpius@google.com>2020-03-30 22:25:40 -0700
committerRoshan Pius <rpius@google.com>2020-03-31 14:30:28 -0700
commit7832f03e1a66dc144d8f33ed48a3405034d75762 (patch)
tree23daa4772708d12b5a2c078fd01ef995b3a66f83
parentbd16d74680d652c9f48fd3dafb9149e6cedda9af (diff)
WifiShellCommand: Add cmds for approving NetworkRequest from app
This is useful for bypassing the UI for local testing. Needs rooted shell. Also, added a utility method for parsing boolean params. Bug: 152299953 Test: Manual Steps to connect to a network via network request adb root adb shell cmd wifi network-requests-set-user-approved android yes abd shell cmd wifi add-request <ssid> open abd shell cmd wifi remove-request <ssid> open abd shell cmd wifi add-request <ssid> wpa2 <passphrase> abd shell cmd wifi remove-request <ssid> wpa2 Change-Id: Ib59fd1869d8827cc79d99058dca4dc9c8774b954
-rw-r--r--service/java/com/android/server/wifi/ClientModeImpl.java16
-rw-r--r--service/java/com/android/server/wifi/WifiNetworkFactory.java27
-rw-r--r--service/java/com/android/server/wifi/WifiShellCommand.java188
-rw-r--r--tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java48
4 files changed, 162 insertions, 117 deletions
diff --git a/service/java/com/android/server/wifi/ClientModeImpl.java b/service/java/com/android/server/wifi/ClientModeImpl.java
index 588564ed9..8579887a7 100644
--- a/service/java/com/android/server/wifi/ClientModeImpl.java
+++ b/service/java/com/android/server/wifi/ClientModeImpl.java
@@ -5895,6 +5895,22 @@ public class ClientModeImpl extends StateMachine {
}
/**
+ * Approve all access points from {@link WifiNetworkFactory} for the provided package.
+ * Used by shell commands.
+ */
+ public void setNetworkRequestUserApprovedApp(@NonNull String packageName, boolean approved) {
+ mNetworkFactory.setUserApprovedApp(packageName, approved);
+ }
+
+ /**
+ * Whether all access points are approved for the specified app.
+ * Used by shell commands.
+ */
+ public boolean hasNetworkRequestUserApprovedApp(@NonNull String packageName) {
+ return mNetworkFactory.hasUserApprovedApp(packageName);
+ }
+
+ /**
* Remove all approved access points from {@link WifiNetworkFactory} for the provided package.
*/
public void removeNetworkRequestUserApprovedAccessPointsForApp(@NonNull String packageName) {
diff --git a/service/java/com/android/server/wifi/WifiNetworkFactory.java b/service/java/com/android/server/wifi/WifiNetworkFactory.java
index 0af51f7c9..271bbefc4 100644
--- a/service/java/com/android/server/wifi/WifiNetworkFactory.java
+++ b/service/java/com/android/server/wifi/WifiNetworkFactory.java
@@ -128,6 +128,8 @@ public class WifiNetworkFactory extends NetworkFactory {
public final Map<String, LinkedHashSet<AccessPoint>> mUserApprovedAccessPointMap;
private WifiScanner mWifiScanner;
private CompanionDeviceManager mCompanionDeviceManager;
+ // Temporary approval set by shell commands.
+ private String mApprovedApp = null;
private int mGenericConnectionReqCount = 0;
// Request that is being actively processed. All new requests start out as an "active" request
@@ -1317,6 +1319,10 @@ public class WifiNetworkFactory extends NetworkFactory {
if (isAccessPointApprovedInInternalApprovalList(scanResult, requestorPackageName)) {
return scanResult;
}
+ // Shell approved app
+ if (TextUtils.equals(mApprovedApp, requestorPackageName)) {
+ return scanResult;
+ }
// no bypass approvals, show UI.
return null;
}
@@ -1439,6 +1445,26 @@ public class WifiNetworkFactory extends NetworkFactory {
}
/**
+ * Sets all access points approved for the specified app.
+ * Used by shell commands.
+ */
+ public void setUserApprovedApp(@NonNull String packageName, boolean approved) {
+ if (approved) {
+ mApprovedApp = packageName;
+ } else if (TextUtils.equals(packageName, mApprovedApp)) {
+ mApprovedApp = null;
+ }
+ }
+
+ /**
+ * Whether all access points are approved for the specified app.
+ * Used by shell commands.
+ */
+ public boolean hasUserApprovedApp(@NonNull String packageName) {
+ return TextUtils.equals(packageName, mApprovedApp);
+ }
+
+ /**
* Remove all user approved access points for the specified app.
*/
public void removeUserApprovedAccessPointsForApp(@NonNull String packageName) {
@@ -1453,6 +1479,7 @@ public class WifiNetworkFactory extends NetworkFactory {
*/
public void clear() {
mUserApprovedAccessPointMap.clear();
+ mApprovedApp = null;
Log.i(TAG, "Cleared all internal state");
saveToStore();
}
diff --git a/service/java/com/android/server/wifi/WifiShellCommand.java b/service/java/com/android/server/wifi/WifiShellCommand.java
index 5fa0490e8..7938aafca 100644
--- a/service/java/com/android/server/wifi/WifiShellCommand.java
+++ b/service/java/com/android/server/wifi/WifiShellCommand.java
@@ -23,6 +23,7 @@ import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.net.ConnectivityManager;
+import android.net.MacAddress;
import android.net.NetworkRequest;
import android.net.wifi.IActionListener;
import android.net.wifi.ScanResult;
@@ -49,6 +50,7 @@ import com.android.server.wifi.util.ScanResultUtil;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
@@ -64,11 +66,9 @@ import java.util.concurrent.TimeUnit;
* if command executed successfully.
* - onHelp: add a description string.
*
- * If additional state objects are necessary add them to the
- * constructor.
- *
- * Permissions: currently root permission is required for most
- * commands, which is checked using {@link #checkRootPermission()}.
+ * Permissions: currently root permission is required for some commands. Others will
+ * enforce the corresponding API permissions.
+ * TODO (b/152875610): Add unit tests.
*/
public class WifiShellCommand extends BasicShellCommandHandler {
private static String SHELL_PACKAGE_NAME = "com.android.shell";
@@ -140,18 +140,7 @@ public class WifiShellCommand extends BasicShellCommandHandler {
try {
switch (cmd) {
case "set-ipreach-disconnect": {
- boolean enabled;
- String nextArg = getNextArgRequired();
- if ("enabled".equals(nextArg)) {
- enabled = true;
- } else if ("disabled".equals(nextArg)) {
- enabled = false;
- } else {
- pw.println(
- "Invalid argument to 'set-ipreach-disconnect' - must be 'enabled'"
- + " or 'disabled'");
- return -1;
- }
+ boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
mClientModeImpl.setIpReachabilityDisconnectEnabled(enabled);
return 0;
}
@@ -184,36 +173,14 @@ public class WifiShellCommand extends BasicShellCommandHandler {
+ mClientModeImpl.getPollRssiIntervalMsecs());
return 0;
case "force-hi-perf-mode": {
- boolean enabled;
- String nextArg = getNextArgRequired();
- if ("enabled".equals(nextArg)) {
- enabled = true;
- } else if ("disabled".equals(nextArg)) {
- enabled = false;
- } else {
- pw.println(
- "Invalid argument to 'force-hi-perf-mode' - must be 'enabled'"
- + " or 'disabled'");
- return -1;
- }
+ boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
if (!mWifiLockManager.forceHiPerfMode(enabled)) {
pw.println("Command execution failed");
}
return 0;
}
case "force-low-latency-mode": {
- boolean enabled;
- String nextArg = getNextArgRequired();
- if ("enabled".equals(nextArg)) {
- enabled = true;
- } else if ("disabled".equals(nextArg)) {
- enabled = false;
- } else {
- pw.println(
- "Invalid argument to 'force-low-latency-mode' - must be 'enabled'"
- + " or 'disabled'");
- return -1;
- }
+ boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
if (!mWifiLockManager.forceLowLatencyMode(enabled)) {
pw.println("Command execution failed");
}
@@ -221,18 +188,7 @@ public class WifiShellCommand extends BasicShellCommandHandler {
}
case "network-suggestions-set-user-approved": {
String packageName = getNextArgRequired();
- boolean approved;
- String nextArg = getNextArgRequired();
- if ("yes".equals(nextArg)) {
- approved = true;
- } else if ("no".equals(nextArg)) {
- approved = false;
- } else {
- pw.println(
- "Invalid argument to 'network-suggestions-set-user-approved' "
- + "- must be 'yes' or 'no'");
- return -1;
- }
+ boolean approved = getNextArgRequiredTrueOrFalse("yes", "no");
mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(approved, packageName);
return 0;
}
@@ -247,7 +203,6 @@ public class WifiShellCommand extends BasicShellCommandHandler {
String arg1 = getNextArgRequired();
String arg2 = getNextArgRequired();
int carrierId = -1;
- boolean approved;
try {
carrierId = Integer.parseInt(arg1);
} catch (NumberFormatException e) {
@@ -256,16 +211,7 @@ public class WifiShellCommand extends BasicShellCommandHandler {
+ "- carrierId must be an Integer");
return -1;
}
- if ("yes".equals(arg2)) {
- approved = true;
- } else if ("no".equals(arg2)) {
- approved = false;
- } else {
- pw.println("Invalid argument to "
- + "'imsi-protection-exemption-set-user-approved-for-carrier' "
- + "- must be 'yes' or 'no'");
- return -1;
- }
+ boolean approved = getNextArgRequiredTrueOrFalse("yes", "no");
mWifiNetworkSuggestionsManager
.setHasUserApprovedImsiPrivacyExemptionForCarrier(approved, carrierId);
return 0;
@@ -313,8 +259,8 @@ public class WifiShellCommand extends BasicShellCommandHandler {
return sendLinkProbe(pw);
}
case "force-softap-channel": {
- String nextArg = getNextArgRequired();
- if ("enabled".equals(nextArg)) {
+ boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
+ if (enabled) {
int apChannelMHz;
try {
apChannelMHz = Integer.parseInt(getNextArgRequired());
@@ -342,19 +288,14 @@ public class WifiShellCommand extends BasicShellCommandHandler {
mHostapdHal.enableForceSoftApChannel(apChannel, band);
return 0;
- } else if ("disabled".equals(nextArg)) {
+ } else {
mHostapdHal.disableForceSoftApChannel();
return 0;
- } else {
- pw.println(
- "Invalid argument to 'force-softap-channel' - must be 'enabled'"
- + " or 'disabled'");
- return -1;
}
}
case "force-country-code": {
- String nextArg = getNextArgRequired();
- if ("enabled".equals(nextArg)) {
+ boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
+ if (enabled) {
String countryCode = getNextArgRequired();
if (!(countryCode.length() == 2
&& countryCode.chars().allMatch(Character::isLetter))) {
@@ -364,14 +305,9 @@ public class WifiShellCommand extends BasicShellCommandHandler {
}
mWifiCountryCode.enableForceCountryCode(countryCode);
return 0;
- } else if ("disabled".equals(nextArg)) {
+ } else {
mWifiCountryCode.disableForceCountryCode();
return 0;
- } else {
- pw.println(
- "Invalid argument to 'force-country-code' - must be 'enabled'"
- + " or 'disabled'");
- return -1;
}
}
case "get-country-code": {
@@ -380,18 +316,7 @@ public class WifiShellCommand extends BasicShellCommandHandler {
return 0;
}
case "set-wifi-watchdog": {
- boolean enabled;
- String nextArg = getNextArgRequired();
- if ("enabled".equals(nextArg)) {
- enabled = true;
- } else if ("disabled".equals(nextArg)) {
- enabled = false;
- } else {
- pw.println(
- "Invalid argument to 'set-wifi-watchdog' - must be 'enabled'"
- + " or 'disabled'");
- return -1;
- }
+ boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
mWifiLastResortWatchdog.setWifiWatchdogFeature(enabled);
return 0;
}
@@ -401,18 +326,7 @@ public class WifiShellCommand extends BasicShellCommandHandler {
return 0;
}
case "set-wifi-enabled": {
- boolean enabled;
- String nextArg = getNextArgRequired();
- if ("enabled".equals(nextArg)) {
- enabled = true;
- } else if ("disabled".equals(nextArg)) {
- enabled = false;
- } else {
- pw.println(
- "Invalid argument to 'set-wifi-enabled' - must be 'enabled'"
- + " or 'disabled'");
- return -1;
- }
+ boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
mWifiService.setWifiEnabled(SHELL_PACKAGE_NAME, enabled);
return 0;
}
@@ -544,21 +458,11 @@ public class WifiShellCommand extends BasicShellCommandHandler {
}
}
break;
- case "set-verbose-logging":
- boolean enabled;
- String nextArg = getNextArgRequired();
- if ("enabled".equals(nextArg)) {
- enabled = true;
- } else if ("disabled".equals(nextArg)) {
- enabled = false;
- } else {
- pw.println(
- "Invalid argument to 'set-verbose-logging' - must be 'enabled'"
- + " or 'disabled'");
- return -1;
- }
+ case "set-verbose-logging": {
+ boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
mWifiService.enableVerboseLogging(enabled ? 1 : 0);
break;
+ }
case "add-suggestion":
mWifiService.addNetworkSuggestions(
Arrays.asList(buildSuggestion(pw)), SHELL_PACKAGE_NAME, null);
@@ -649,9 +553,24 @@ public class WifiShellCommand extends BasicShellCommandHandler {
}
}
break;
+ case "network-requests-set-user-approved": {
+ String packageName = getNextArgRequired();
+ boolean approved = getNextArgRequiredTrueOrFalse("yes", "no");
+ mClientModeImpl.setNetworkRequestUserApprovedApp(packageName, approved);
+ return 0;
+ }
+ case "network-requests-has-user-approved": {
+ String packageName = getNextArgRequired();
+ boolean hasUserApproved =
+ mClientModeImpl.hasNetworkRequestUserApprovedApp(packageName);
+ pw.println(hasUserApproved ? "yes" : "no");
+ return 0;
+ }
default:
return handleDefaultCommands(cmd);
}
+ } catch (IllegalArgumentException e) {
+ pw.println("Invalid args for " + cmd + ": " + e);
} catch (Exception e) {
pw.println("Exception while executing WifiShellCommand: ");
e.printStackTrace(pw);
@@ -659,6 +578,19 @@ public class WifiShellCommand extends BasicShellCommandHandler {
return -1;
}
+ private boolean getNextArgRequiredTrueOrFalse(String trueString, String falseString)
+ throws IllegalArgumentException {
+ String nextArg = getNextArgRequired();
+ if (trueString.equals(nextArg)) {
+ return true;
+ } else if (falseString.equals(nextArg)) {
+ return false;
+ } else {
+ throw new IllegalArgumentException("Expected '" + trueString + "' or '" + falseString
+ + "' as next arg but got '" + nextArg + "'");
+ }
+ }
+
private WifiNetworkSuggestion buildSuggestion(PrintWriter pw) {
String ssid = getNextArgRequired();
String type = getNextArgRequired();
@@ -698,6 +630,19 @@ public class WifiShellCommand extends BasicShellCommandHandler {
pw.println("Unknown network type " + type);
return null;
}
+ // Permission approval bypass is only available to requests with both ssid & bssid set.
+ // So, find scan result with the best rssi level to set in the request.
+ ScanResult matchingScanResult =
+ mWifiService.getScanResults(SHELL_PACKAGE_NAME, null)
+ .stream()
+ .filter(s -> s.SSID.equals(ssid))
+ .max(Comparator.comparingInt(s -> s.level))
+ .orElse(null);
+ if (matchingScanResult != null) {
+ specifierBuilder.setBssid(MacAddress.fromString(matchingScanResult.BSSID));
+ } else {
+ pw.println("No matching bssid found, request will need UI approval");
+ }
return new NetworkRequest.Builder()
.addTransportType(TRANSPORT_WIFI)
.removeCapability(NET_CAPABILITY_INTERNET)
@@ -858,6 +803,8 @@ public class WifiShellCommand extends BasicShellCommandHandler {
pw.println(" Initiates wifi settings reset");
pw.println(" add-request <ssid> open|owe|wpa2|wpa3 [<passphrase>]");
pw.println(" Add a network request with provided params");
+ pw.println(" Use 'network-requests-set-user-approved android yes'"
+ + " to pre-approve requests added via rooted shell (Not persisted)");
pw.println(" <ssid> - SSID of the network");
pw.println(" open|owe|wpa2|wpa3 - Security type of the network.");
pw.println(" - Use 'open' or 'owe' for networks with no passphrase");
@@ -872,6 +819,13 @@ public class WifiShellCommand extends BasicShellCommandHandler {
pw.println(" Removes all active requests added via shell");
pw.println(" list-requests");
pw.println(" Lists the requested networks added via shell");
+ pw.println(" network-requests-set-user-approved <package name> yes|no");
+ pw.println(" Sets whether network requests from the app is approved or not.");
+ pw.println(" Note: Only 1 such app can be approved from the shell at a time");
+ pw.println(" network-requests-has-user-approved <package name>");
+ pw.println(" Queries whether network requests from the app is approved or not.");
+ pw.println(" Note: This only returns whether the app was set via the " +
+ "'network-requests-set-user-approved' shell command");
}
@Override
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java
index afa9ffd53..684fd1449 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java
@@ -2510,6 +2510,54 @@ public class WifiNetworkFactoryTest extends WifiBaseTest {
/**
* Verify the user approval bypass for a specific request for an access point that was already
+ * approved previously via shell command and the scan result is present in the cached scan
+ * results.
+ */
+ @Test
+ public void
+ testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchApprovedViaShellWithCache()
+ throws Exception {
+ // Setup scan data for WPA-PSK networks.
+ setupScanData(SCAN_RESULT_TYPE_WPA_PSK,
+ TEST_SSID_1, TEST_SSID_2, TEST_SSID_3, TEST_SSID_4);
+
+ // Choose the matching scan result.
+ ScanResult matchingScanResult = mTestScanDatas[0].getResults()[0];
+
+ // Setup shell approval for the scan result.
+ mWifiNetworkFactory.setUserApprovedApp(TEST_PACKAGE_NAME_1, true);
+
+ // simulate no cache expiry
+ when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
+ // Simulate the cached results matching.
+ when(mWifiScanner.getSingleScanResults())
+ .thenReturn(Arrays.asList(mTestScanDatas[0].getResults()));
+
+ PatternMatcher ssidPatternMatch =
+ new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL);
+ Pair<MacAddress, MacAddress> bssidPatternMatch =
+ Pair.create(MacAddress.fromString(matchingScanResult.BSSID),
+ MacAddress.BROADCAST_ADDRESS);
+ attachWifiNetworkSpecifierAndAppInfo(
+ ssidPatternMatch, bssidPatternMatch,
+ WifiConfigurationTestUtil.createPskNetwork(), TEST_UID_1, TEST_PACKAGE_NAME_1);
+ mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0);
+
+ // Verify we did not trigger the UI for the second request.
+ verify(mContext, never()).startActivityAsUser(any(), any());
+ // Verify we did not trigger a scan.
+ verify(mWifiScanner, never()).startScan(any(), any(), any(), any());
+ // Verify we did not trigger the match callback.
+ verify(mNetworkRequestMatchCallback, never()).onMatch(anyList());
+ // Verify that we sent a connection attempt to ClientModeImpl
+ verify(mClientModeImpl).connect(eq(null), anyInt(),
+ any(Binder.class), mConnectListenerArgumentCaptor.capture(), anyInt(), anyInt());
+
+ verify(mWifiMetrics).incrementNetworkRequestApiNumUserApprovalBypass();
+ }
+
+ /**
+ * Verify the user approval bypass for a specific request for an access point that was already
* approved previously and the scan result is present in the cached scan results, but the
* results are stale.
*/