diff options
author | Roshan Pius <rpius@google.com> | 2018-11-06 17:43:02 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2018-11-06 17:43:02 +0000 |
commit | d2a20c1a3975668bc5aeab53a8f0daae6662366a (patch) | |
tree | 37b5ec26895032b06815f6b78c2abde4c8a5eea6 /service | |
parent | 894960003ef7ccb181dee54da59fe787f471e406 (diff) | |
parent | 3ff9e5cb3fe2fab47365bdf3ffb75139c099b3da (diff) |
Merge changes from topics "network_request_match_callback", "wifi_network_specifier"
* changes:
WifiNetworkFactory: Implement network matching using network specifier
ScanResultMatchInfo: Refactor network type retrieval
WifiServiceImpl: Network request match callback registration
WifiNetworkFactory: Always allow requests from signature app
WifiNetworkFactory: Trigger periodic scans
WifiConfigurationUtil: Validation for network specifier
WifiNetworkFactory: Implement |acceptRequest|
Diffstat (limited to 'service')
6 files changed, 794 insertions, 52 deletions
diff --git a/service/java/com/android/server/wifi/ClientModeImpl.java b/service/java/com/android/server/wifi/ClientModeImpl.java index dd852945b..b401515d5 100644 --- a/service/java/com/android/server/wifi/ClientModeImpl.java +++ b/service/java/com/android/server/wifi/ClientModeImpl.java @@ -38,6 +38,7 @@ import android.net.IpConfiguration; import android.net.KeepalivePacketData; import android.net.LinkProperties; import android.net.MacAddress; +import android.net.MatchAllNetworkSpecifier; import android.net.Network; import android.net.NetworkAgent; import android.net.NetworkCapabilities; @@ -50,6 +51,7 @@ import android.net.StaticIpConfiguration; import android.net.TrafficStats; import android.net.dhcp.DhcpClient; import android.net.ip.IpClient; +import android.net.wifi.INetworkRequestMatchCallback; import android.net.wifi.RssiPacketCountInfo; import android.net.wifi.ScanResult; import android.net.wifi.SupplicantState; @@ -793,15 +795,18 @@ public class ClientModeImpl extends StateMachine { // TODO - needs to be a bit more dynamic mDfltNetworkCapabilities = new NetworkCapabilities(mNetworkCapabilitiesFilter); + NetworkCapabilities factoryNetworkCapabilities = + new NetworkCapabilities(mNetworkCapabilitiesFilter); + factoryNetworkCapabilities.setNetworkSpecifier(new MatchAllNetworkSpecifier()); // Make the network factories. mNetworkFactory = mWifiInjector.makeWifiNetworkFactory( - mNetworkCapabilitiesFilter, mWifiConnectivityManager); + factoryNetworkCapabilities, mWifiConnectivityManager); // We can't filter untrusted network in the capabilities filter because a trusted // network would still satisfy a request that accepts untrusted ones. // We need a second network factory for untrusted network requests because we need a // different score filter for these requests. mUntrustedNetworkFactory = mWifiInjector.makeUntrustedWifiNetworkFactory( - mNetworkCapabilitiesFilter, mWifiConnectivityManager); + factoryNetworkCapabilities, mWifiConnectivityManager); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_ON); @@ -1063,6 +1068,7 @@ public class ClientModeImpl extends StateMachine { mWifiConfigManager.enableVerboseLogging(verbose); mSupplicantStateTracker.enableVerboseLogging(verbose); mPasspointManager.enableVerboseLogging(verbose); + mNetworkFactory.enableVerboseLogging(verbose); } private static final String SYSTEM_PROPERTY_LOG_CONTROL_WIFIHAL = "log.tag.WifiHAL"; @@ -5763,4 +5769,20 @@ public class ClientModeImpl extends StateMachine { resultMsg.recycle(); return result; } + + /** + * Add a network request match callback to {@link WifiNetworkFactory}. + */ + public void addNetworkRequestMatchCallback(IBinder binder, + INetworkRequestMatchCallback callback, + int callbackIdentifier) { + mNetworkFactory.addCallback(binder, callback, callbackIdentifier); + } + + /** + * Remove a network request match callback from {@link WifiNetworkFactory}. + */ + public void removeNetworkRequestMatchCallback(int callbackIdentifier) { + mNetworkFactory.removeCallback(callbackIdentifier); + } } diff --git a/service/java/com/android/server/wifi/ScanResultMatchInfo.java b/service/java/com/android/server/wifi/ScanResultMatchInfo.java index ad29c2312..72fab6bc0 100644 --- a/service/java/com/android/server/wifi/ScanResultMatchInfo.java +++ b/service/java/com/android/server/wifi/ScanResultMatchInfo.java @@ -15,11 +15,15 @@ */ package com.android.server.wifi; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import com.android.server.wifi.util.ScanResultUtil; +import java.lang.annotation.Retention; import java.util.Objects; /** @@ -31,6 +35,15 @@ public class ScanResultMatchInfo { public static final int NETWORK_TYPE_PSK = 2; public static final int NETWORK_TYPE_EAP = 3; + @Retention(SOURCE) + @IntDef(prefix = { "NETWORK_TYPE_" }, value = { + NETWORK_TYPE_OPEN, + NETWORK_TYPE_WEP, + NETWORK_TYPE_PSK, + NETWORK_TYPE_EAP + }) + public @interface NetworkType {} + /** * SSID of the network. */ @@ -38,29 +51,51 @@ public class ScanResultMatchInfo { /** * Security Type of the network. */ - public int networkType; + public @NetworkType int networkType; /** - * Get the ScanResultMatchInfo for the given WifiConfiguration + * Fetch network type from network configuration. */ - public static ScanResultMatchInfo fromWifiConfiguration(WifiConfiguration config) { - ScanResultMatchInfo info = new ScanResultMatchInfo(); - info.networkSsid = config.SSID; + public static @NetworkType int getNetworkType(WifiConfiguration config) { if (WifiConfigurationUtil.isConfigForPskNetwork(config)) { - info.networkType = NETWORK_TYPE_PSK; + return NETWORK_TYPE_PSK; } else if (WifiConfigurationUtil.isConfigForEapNetwork(config)) { - info.networkType = NETWORK_TYPE_EAP; + return NETWORK_TYPE_EAP; } else if (WifiConfigurationUtil.isConfigForWepNetwork(config)) { - info.networkType = NETWORK_TYPE_WEP; + return NETWORK_TYPE_WEP; } else if (WifiConfigurationUtil.isConfigForOpenNetwork(config)) { - info.networkType = NETWORK_TYPE_OPEN; - } else { - throw new IllegalArgumentException("Invalid WifiConfiguration: " + config); + return NETWORK_TYPE_OPEN; } + throw new IllegalArgumentException("Invalid WifiConfiguration: " + config); + } + + /** + * Get the ScanResultMatchInfo for the given WifiConfiguration + */ + public static ScanResultMatchInfo fromWifiConfiguration(WifiConfiguration config) { + ScanResultMatchInfo info = new ScanResultMatchInfo(); + info.networkSsid = config.SSID; + info.networkType = getNetworkType(config); return info; } /** + * Fetch network type from scan result. + */ + public static @NetworkType int getNetworkType(ScanResult scanResult) { + if (ScanResultUtil.isScanResultForPskNetwork(scanResult)) { + return NETWORK_TYPE_PSK; + } else if (ScanResultUtil.isScanResultForEapNetwork(scanResult)) { + return NETWORK_TYPE_EAP; + } else if (ScanResultUtil.isScanResultForWepNetwork(scanResult)) { + return NETWORK_TYPE_WEP; + } else if (ScanResultUtil.isScanResultForOpenNetwork(scanResult)) { + return NETWORK_TYPE_OPEN; + } + throw new IllegalArgumentException("Invalid ScanResult: " + scanResult); + } + + /** * Get the ScanResultMatchInfo for the given ScanResult */ public static ScanResultMatchInfo fromScanResult(ScanResult scanResult) { @@ -70,17 +105,7 @@ public class ScanResultMatchInfo { // However, according to our public documentation ths {@link WifiConfiguration#SSID} can // either have a hex string or quoted ASCII string SSID. info.networkSsid = ScanResultUtil.createQuotedSSID(scanResult.SSID); - if (ScanResultUtil.isScanResultForPskNetwork(scanResult)) { - info.networkType = NETWORK_TYPE_PSK; - } else if (ScanResultUtil.isScanResultForEapNetwork(scanResult)) { - info.networkType = NETWORK_TYPE_EAP; - } else if (ScanResultUtil.isScanResultForWepNetwork(scanResult)) { - info.networkType = NETWORK_TYPE_WEP; - } else if (ScanResultUtil.isScanResultForOpenNetwork(scanResult)) { - info.networkType = NETWORK_TYPE_OPEN; - } else { - throw new IllegalArgumentException("Invalid ScanResult: " + scanResult); - } + info.networkType = getNetworkType(scanResult); return info; } diff --git a/service/java/com/android/server/wifi/WifiConfigurationUtil.java b/service/java/com/android/server/wifi/WifiConfigurationUtil.java index 0258834ea..9bf6e327d 100644 --- a/service/java/com/android/server/wifi/WifiConfigurationUtil.java +++ b/service/java/com/android/server/wifi/WifiConfigurationUtil.java @@ -16,15 +16,21 @@ package com.android.server.wifi; +import static com.android.server.wifi.util.NativeUtil.addEnclosingQuotes; + import android.content.pm.UserInfo; import android.net.IpConfiguration; +import android.net.MacAddress; import android.net.StaticIpConfiguration; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiEnterpriseConfig; +import android.net.wifi.WifiNetworkSpecifier; import android.net.wifi.WifiScanner; +import android.os.PatternMatcher; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wifi.util.NativeUtil; @@ -50,16 +56,21 @@ public class WifiConfigurationUtil { /** * Constants used for validating external config objects. */ - private static final int ENCLOSING_QUTOES_LEN = 2; - private static final int SSID_UTF_8_MIN_LEN = 1 + ENCLOSING_QUTOES_LEN; - private static final int SSID_UTF_8_MAX_LEN = 32 + ENCLOSING_QUTOES_LEN; + private static final int ENCLOSING_QUOTES_LEN = 2; + private static final int SSID_UTF_8_MIN_LEN = 1 + ENCLOSING_QUOTES_LEN; + private static final int SSID_UTF_8_MAX_LEN = 32 + ENCLOSING_QUOTES_LEN; private static final int SSID_HEX_MIN_LEN = 2; private static final int SSID_HEX_MAX_LEN = 64; - private static final int PSK_ASCII_MIN_LEN = 8 + ENCLOSING_QUTOES_LEN; - private static final int PSK_ASCII_MAX_LEN = 63 + ENCLOSING_QUTOES_LEN; + private static final int PSK_ASCII_MIN_LEN = 8 + ENCLOSING_QUOTES_LEN; + private static final int PSK_ASCII_MAX_LEN = 63 + ENCLOSING_QUOTES_LEN; private static final int PSK_HEX_LEN = 64; @VisibleForTesting public static final String PASSWORD_MASK = "*"; + private static final String MATCH_EMPTY_SSID_PATTERN_PATH = ""; + private static final Pair<MacAddress, MacAddress> MATCH_NONE_BSSID_PATTERN = + new Pair(MacAddress.BROADCAST_ADDRESS, MacAddress.BROADCAST_ADDRESS); + private static final Pair<MacAddress, MacAddress> MATCH_ALL_BSSID_PATTERN = + new Pair(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); /** * Check whether a network configuration is visible to a user or any of its managed profiles. @@ -319,6 +330,34 @@ public class WifiConfigurationUtil { return true; } + private static boolean validateBssid(MacAddress bssid) { + if (bssid == null) return true; + if (bssid.getAddressType() != MacAddress.TYPE_UNICAST) { + Log.e(TAG, "validateBssid failed: invalid bssid"); + return false; + } + return true; + } + + private static boolean validateBssid(String bssid) { + if (bssid == null) return true; + if (bssid.isEmpty()) { + Log.e(TAG, "validateBssid failed: empty string"); + return false; + } + MacAddress bssidMacAddress; + try { + bssidMacAddress = MacAddress.fromString(bssid); + } catch (IllegalArgumentException e) { + Log.e(TAG, "validateBssid failed: malformed string: " + bssid); + return false; + } + if (!validateBssid(bssidMacAddress)) { + return false; + } + return true; + } + private static boolean validatePsk(String psk, boolean isAdd) { if (isAdd) { if (psk == null) { @@ -461,13 +500,14 @@ public class WifiConfigurationUtil { * * This method checks for the following parameters: * 1. {@link WifiConfiguration#SSID} - * 2. {@link WifiConfiguration#preSharedKey} - * 3. {@link WifiConfiguration#allowedKeyManagement} - * 4. {@link WifiConfiguration#allowedProtocols} - * 5. {@link WifiConfiguration#allowedAuthAlgorithms} - * 6. {@link WifiConfiguration#allowedGroupCiphers} - * 7. {@link WifiConfiguration#allowedPairwiseCiphers} - * 8. {@link WifiConfiguration#getIpConfiguration()} + * 2. {@link WifiConfiguration#BSSID} + * 3. {@link WifiConfiguration#preSharedKey} + * 4. {@link WifiConfiguration#allowedKeyManagement} + * 5. {@link WifiConfiguration#allowedProtocols} + * 6. {@link WifiConfiguration#allowedAuthAlgorithms} + * 7. {@link WifiConfiguration#allowedGroupCiphers} + * 8. {@link WifiConfiguration#allowedPairwiseCiphers} + * 9. {@link WifiConfiguration#getIpConfiguration()} * * @param config {@link WifiConfiguration} received from an external application. * @param isAdd {@link #VALIDATE_FOR_ADD} to indicate a network config received for an add, @@ -480,6 +520,9 @@ public class WifiConfigurationUtil { if (!validateSsid(config.SSID, isAdd)) { return false; } + if (!validateBssid(config.BSSID)) { + return false; + } if (!validateBitSets(config)) { return false; } @@ -497,6 +540,134 @@ public class WifiConfigurationUtil { return true; } + private static boolean validateBssidPattern( + Pair<MacAddress, MacAddress> bssidPatternMatcher) { + if (bssidPatternMatcher == null) return true; + MacAddress baseAddress = bssidPatternMatcher.first; + MacAddress mask = bssidPatternMatcher.second; + if (baseAddress.getAddressType() != MacAddress.TYPE_UNICAST) { + Log.e(TAG, "validateBssidPatternMatcher failed : invalid base address: " + baseAddress); + return false; + } + if (mask.equals(MacAddress.ALL_ZEROS_ADDRESS) + && !baseAddress.equals(MacAddress.ALL_ZEROS_ADDRESS)) { + Log.e(TAG, "validateBssidPatternMatcher failed : invalid mask/base: " + mask + "/" + + baseAddress); + return false; + } + // TBD: Can we do any more sanity checks? + return true; + } + + // TODO(b/113878056): Some of this is duplicated in {@link WifiNetworkConfigBuilder}. + // Merge them somehow?. + private static boolean isValidNetworkSpecifier(WifiNetworkSpecifier specifier) { + PatternMatcher ssidPatternMatcher = specifier.ssidPatternMatcher; + Pair<MacAddress, MacAddress> bssidPatternMatcher = specifier.bssidPatternMatcher; + if (ssidPatternMatcher == null || bssidPatternMatcher == null) { + return false; + } + if (ssidPatternMatcher.getPath() == null || bssidPatternMatcher.first == null + || bssidPatternMatcher.second == null) { + return false; + } + return true; + } + + private static boolean isMatchNoneNetworkSpecifier(WifiNetworkSpecifier specifier) { + PatternMatcher ssidPatternMatcher = specifier.ssidPatternMatcher; + Pair<MacAddress, MacAddress> bssidPatternMatcher = specifier.bssidPatternMatcher; + if (ssidPatternMatcher.getType() != PatternMatcher.PATTERN_PREFIX + && ssidPatternMatcher.getPath().equals(MATCH_EMPTY_SSID_PATTERN_PATH)) { + return true; + } + if (bssidPatternMatcher.equals(MATCH_NONE_BSSID_PATTERN)) { + return true; + } + return false; + } + + private static boolean isMatchAllNetworkSpecifier(WifiNetworkSpecifier specifier) { + PatternMatcher ssidPatternMatcher = specifier.ssidPatternMatcher; + Pair<MacAddress, MacAddress> bssidPatternMatcher = specifier.bssidPatternMatcher; + if (ssidPatternMatcher.match(MATCH_EMPTY_SSID_PATTERN_PATH) + && bssidPatternMatcher.equals(MATCH_ALL_BSSID_PATTERN)) { + return true; + } + return false; + } + + /** + * Validate the configuration received from an external application inside + * {@link WifiNetworkSpecifier}. + * + * This method checks for the following parameters: + * 1. {@link WifiNetworkSpecifier#ssidPatternMatcher} + * 2. {@link WifiNetworkSpecifier#bssidPatternMatcher} + * 3. {@link WifiConfiguration#SSID} + * 4. {@link WifiConfiguration#BSSID} + * 5. {@link WifiConfiguration#preSharedKey} + * 6. {@link WifiConfiguration#allowedKeyManagement} + * 7. {@link WifiConfiguration#allowedProtocols} + * 8. {@link WifiConfiguration#allowedAuthAlgorithms} + * 9. {@link WifiConfiguration#allowedGroupCiphers} + * 10. {@link WifiConfiguration#allowedPairwiseCiphers} + * 11. {@link WifiConfiguration#getIpConfiguration()} + * + * @param specifier Instance of {@link WifiNetworkSpecifier}. + * @return true if the parameters are valid, false otherwise. + */ + public static boolean validateNetworkSpecifier(WifiNetworkSpecifier specifier) { + if (!isValidNetworkSpecifier(specifier)) { + Log.e(TAG, "validateNetworkSpecifier failed : invalid network specifier"); + return false; + } + if (isMatchNoneNetworkSpecifier(specifier)) { + Log.e(TAG, "validateNetworkSpecifier failed : match-none specifier"); + return false; + } + if (isMatchAllNetworkSpecifier(specifier)) { + Log.e(TAG, "validateNetworkSpecifier failed : match-all specifier"); + return false; + } + WifiConfiguration config = specifier.wifiConfiguration; + if (specifier.ssidPatternMatcher.getType() == PatternMatcher.PATTERN_LITERAL) { + // For literal SSID matches, the value should satisfy SSID requirements. + // WifiConfiguration.SSID needs quotes around ASCII SSID. + if (!validateSsid(addEnclosingQuotes(specifier.ssidPatternMatcher.getPath()), true)) { + return false; + } + } else { + if (config.hiddenSSID) { + Log.e(TAG, "validateNetworkSpecifier failed : ssid pattern not supported " + + "for hidden networks"); + return false; + } + } + if (Objects.equals(specifier.bssidPatternMatcher.second, MacAddress.BROADCAST_ADDRESS)) { + // For literal BSSID matches, the value should satisfy MAC address requirements. + if (!validateBssid(specifier.bssidPatternMatcher.first)) { + return false; + } + } else { + if (!validateBssidPattern(specifier.bssidPatternMatcher)) { + return false; + } + } + if (!validateBitSets(config)) { + return false; + } + if (!validateKeyMgmt(config.allowedKeyManagement)) { + return false; + } + if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK) + && !validatePsk(config.preSharedKey, true)) { + return false; + } + // TBD: Validate some enterprise params as well in the future here. + return true; + } + /** * Check if the provided two networks are the same. * Note: This does not check if network selection BSSID's are the same. diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java index 53b669e4c..e708e2bde 100644 --- a/service/java/com/android/server/wifi/WifiInjector.java +++ b/service/java/com/android/server/wifi/WifiInjector.java @@ -18,6 +18,7 @@ package com.android.server.wifi; import android.annotation.NonNull; import android.app.ActivityManager; +import android.app.AlarmManager; import android.app.AppOpsManager; import android.content.Context; import android.hardware.SystemSensorManager; @@ -548,7 +549,10 @@ public class WifiInjector { public WifiNetworkFactory makeWifiNetworkFactory( NetworkCapabilities nc, WifiConnectivityManager wifiConnectivityManager) { return new WifiNetworkFactory( - mWifiCoreHandlerThread.getLooper(), mContext, nc, wifiConnectivityManager); + mWifiCoreHandlerThread.getLooper(), mContext, nc, + (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE), + (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE), + mClock, this, wifiConnectivityManager, mWifiPermissionsUtil); } /** diff --git a/service/java/com/android/server/wifi/WifiNetworkFactory.java b/service/java/com/android/server/wifi/WifiNetworkFactory.java index 489601d0d..9da64d231 100644 --- a/service/java/com/android/server/wifi/WifiNetworkFactory.java +++ b/service/java/com/android/server/wifi/WifiNetworkFactory.java @@ -16,63 +16,524 @@ package com.android.server.wifi; +import static com.android.internal.util.Preconditions.checkNotNull; +import static com.android.server.wifi.util.NativeUtil.addEnclosingQuotes; + +import android.app.ActivityManager; +import android.app.AlarmManager; import android.content.Context; +import android.net.MacAddress; import android.net.NetworkCapabilities; import android.net.NetworkFactory; import android.net.NetworkRequest; +import android.net.NetworkSpecifier; +import android.net.wifi.INetworkRequestMatchCallback; +import android.net.wifi.INetworkRequestUserSelectionCallback; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiNetworkSpecifier; +import android.net.wifi.WifiScanner; +import android.os.Handler; +import android.os.IBinder; import android.os.Looper; +import android.os.RemoteException; +import android.os.WorkSource; import android.util.Log; +import android.util.Pair; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.wifi.util.ExternalCallbackTracker; +import com.android.server.wifi.util.WifiPermissionsUtil; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * Network factory to handle trusted wifi network requests. */ public class WifiNetworkFactory extends NetworkFactory { private static final String TAG = "WifiNetworkFactory"; + @VisibleForTesting private static final int SCORE_FILTER = 60; + @VisibleForTesting + public static final int PERIODIC_SCAN_INTERVAL_MS = 10 * 1000; // 10 seconds + private final Context mContext; + private final ActivityManager mActivityManager; + private final AlarmManager mAlarmManager; + private final Clock mClock; + private final Handler mHandler; + private final WifiInjector mWifiInjector; private final WifiConnectivityManager mWifiConnectivityManager; - private int mConnectionReqCount = 0; + private final WifiPermissionsUtil mWifiPermissionsUtil; + private final WifiScanner.ScanSettings mScanSettings; + private final NetworkFactoryScanListener mScanListener; + private final NetworkFactoryAlarmListener mPeriodicScanTimerListener; + private final ExternalCallbackTracker<INetworkRequestMatchCallback> mRegisteredCallbacks; + private WifiScanner mWifiScanner; + + private int mGenericConnectionReqCount = 0; + private NetworkRequest mActiveSpecificNetworkRequest; + private WifiNetworkSpecifier mActiveSpecificNetworkRequestSpecifier; + private List<WifiConfiguration> mActiveMatchedNetworks; + // Verbose logging flag. + private boolean mVerboseLoggingEnabled = false; + private boolean mPeriodicScanTimerSet = false; + + // Scan listener for scan requests. + private class NetworkFactoryScanListener implements WifiScanner.ScanListener { + @Override + public void onSuccess() { + // Scan request succeeded, wait for results to report to external clients. + if (mVerboseLoggingEnabled) { + Log.d(TAG, "Scan request succeeded"); + } + } + + @Override + public void onFailure(int reason, String description) { + Log.e(TAG, "Scan failure received. reason: " + reason + + ", description: " + description); + // TODO(b/113878056): Retry scan to workaround any transient scan failures. + scheduleNextPeriodicScan(); + } + + @Override + public void onResults(WifiScanner.ScanData[] scanDatas) { + if (mVerboseLoggingEnabled) { + Log.d(TAG, "Scan results received"); + } + // For single scans, the array size should always be 1. + if (scanDatas.length != 1) { + Log.wtf(TAG, "Found more than 1 batch of scan results, Ignoring..."); + return; + } + WifiScanner.ScanData scanData = scanDatas[0]; + ScanResult[] scanResults = scanData.getResults(); + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Received " + scanResults.length + " scan results"); + } + List<WifiConfiguration> matchedNetworks = + getNetworksMatchingActiveNetworkRequest(scanResults); + sendNetworkRequestMatchCallbacksForActiveRequest(matchedNetworks); + mActiveMatchedNetworks = matchedNetworks; + + // Didn't find a match, schedule the next scan. + scheduleNextPeriodicScan(); + } + + @Override + public void onFullResult(ScanResult fullScanResult) { + // Ignore for single scans. + } - public WifiNetworkFactory(Looper l, Context c, NetworkCapabilities f, - WifiConnectivityManager connectivityManager) { - super(l, c, TAG, f); + @Override + public void onPeriodChanged(int periodInMs) { + // Ignore for single scans. + } + }; + + private class NetworkFactoryAlarmListener implements AlarmManager.OnAlarmListener { + @Override + public void onAlarm() { + // Trigger the next scan. + startScan(); + } + } + + // Callback result from settings UI. + private class NetworkFactoryUserSelectionCallback extends + INetworkRequestUserSelectionCallback.Stub { + @Override + public void select(WifiConfiguration wifiConfiguration) { + mHandler.post(() -> { + if (mActiveSpecificNetworkRequest == null) { + Log.e(TAG, "Stale callback select received"); + return; + } + // TODO(b/113878056): Trigger network connection to |wificonfiguration|. + }); + } + + @Override + public void reject() { + mHandler.post(() -> { + if (mActiveSpecificNetworkRequest == null) { + Log.e(TAG, "Stale callback reject received"); + return; + } + // TODO(b/113878056): Clear the active network request. + }); + } + } + + public WifiNetworkFactory(Looper looper, Context context, NetworkCapabilities nc, + ActivityManager activityManager, AlarmManager alarmManager, + Clock clock, WifiInjector wifiInjector, + WifiConnectivityManager connectivityManager, + WifiPermissionsUtil wifiPermissionsUtil) { + super(looper, context, TAG, nc); + mContext = context; + mActivityManager = activityManager; + mAlarmManager = alarmManager; + mClock = clock; + mHandler = new Handler(looper); + mWifiInjector = wifiInjector; mWifiConnectivityManager = connectivityManager; + mWifiPermissionsUtil = wifiPermissionsUtil; + // Create the scan settings. + mScanSettings = new WifiScanner.ScanSettings(); + mScanSettings.type = WifiScanner.TYPE_HIGH_ACCURACY; + mScanSettings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS; + mScanSettings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; + mScanListener = new NetworkFactoryScanListener(); + mPeriodicScanTimerListener = new NetworkFactoryAlarmListener(); + mRegisteredCallbacks = new ExternalCallbackTracker<INetworkRequestMatchCallback>(mHandler); setScoreFilter(SCORE_FILTER); } + /** + * Enable verbose logging. + */ + public void enableVerboseLogging(int verbose) { + mVerboseLoggingEnabled = (verbose > 0); + } + + /** + * Add a new callback for network request match handling. + */ + public void addCallback(IBinder binder, INetworkRequestMatchCallback callback, + int callbackIdentifier) { + if (!mRegisteredCallbacks.add(binder, callback, callbackIdentifier)) { + Log.e(TAG, "Failed to add callback"); + return; + } + // Register our user selection callback immediately. + try { + callback.onUserSelectionCallbackRegistration(new NetworkFactoryUserSelectionCallback()); + } catch (RemoteException e) { + Log.e(TAG, "Unable to invoke user selection registration callback " + callback, e); + } + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Adding callback. Num callbacks: " + mRegisteredCallbacks.getNumCallbacks()); + } + } + + /** + * Remove an existing callback for network request match handling. + */ + public void removeCallback(int callbackIdentifier) { + mRegisteredCallbacks.remove(callbackIdentifier); + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Removing callback. Num callbacks: " + + mRegisteredCallbacks.getNumCallbacks()); + } + } + + /** + * Check whether to accept the new network connection request. + * + * All the validation of the incoming request is done in this method. + */ + @Override + public boolean acceptRequest(NetworkRequest networkRequest, int score) { + NetworkSpecifier ns = networkRequest.networkCapabilities.getNetworkSpecifier(); + // Generic wifi request. Always accept. + if (ns == null) { + // Generic wifi request. Always accept. + } else { + // Invalid network specifier. + if (!(ns instanceof WifiNetworkSpecifier)) { + Log.e(TAG, "Invalid network specifier mentioned. Rejecting"); + return false; + } + + WifiNetworkSpecifier wns = (WifiNetworkSpecifier) ns; + if (!WifiConfigurationUtil.validateNetworkSpecifier(wns)) { + Log.e(TAG, "Invalid network specifier." + + " Rejecting request from " + wns.requestorUid); + return false; + } + // Only allow specific wifi network request from foreground app/service. + if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(wns.requestorUid) + && !isRequestFromForegroundAppOrService(wns.requestorUid)) { + Log.e(TAG, "Request not from foreground app or service." + + " Rejecting request from " + wns.requestorUid); + return false; + } + // If there is a pending request, only proceed if the new request is from a foreground + // app. + if (mActiveSpecificNetworkRequest != null + && !mWifiPermissionsUtil.checkNetworkSettingsPermission(wns.requestorUid) + && !isRequestFromForegroundApp(wns.requestorUid)) { + WifiNetworkSpecifier aWns = + (WifiNetworkSpecifier) mActiveSpecificNetworkRequest + .networkCapabilities + .getNetworkSpecifier(); + if (isRequestFromForegroundApp(aWns.requestorUid)) { + Log.e(TAG, "Already processing active request from a foreground app " + + aWns.requestorUid + ". Rejecting request from " + wns.requestorUid); + return false; + } + } + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Accepted network request with specifier from fg " + + (isRequestFromForegroundApp(wns.requestorUid) ? "app" : "service")); + } + } + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Accepted network request " + networkRequest); + } + return true; + } + + /** + * Handle new network connection requests. + * + * The assumption here is that {@link #acceptRequest(NetworkRequest, int)} has already sanitized + * the incoming request. + */ @Override protected void needNetworkFor(NetworkRequest networkRequest, int score) { - if (++mConnectionReqCount == 1) { - mWifiConnectivityManager.setTrustedConnectionAllowed(true); + NetworkSpecifier ns = networkRequest.networkCapabilities.getNetworkSpecifier(); + if (ns == null) { + // Generic wifi request. Turn on auto-join if necessary. + if (++mGenericConnectionReqCount == 1) { + mWifiConnectivityManager.setTrustedConnectionAllowed(true); + } + } else { + // Invalid network specifier. + if (!(ns instanceof WifiNetworkSpecifier)) { + Log.e(TAG, "Invalid network specifier mentioned. Rejecting"); + return; + } + retrieveWifiScanner(); + + // Store the active network request. + mActiveSpecificNetworkRequest = new NetworkRequest(networkRequest); + WifiNetworkSpecifier wns = (WifiNetworkSpecifier) ns; + mActiveSpecificNetworkRequestSpecifier = new WifiNetworkSpecifier( + wns.ssidPatternMatcher, wns.bssidPatternMatcher, wns.wifiConfiguration, + wns.requestorUid); + + // Trigger periodic scans for finding a network in the request. + startPeriodicScans(); + // Disable Auto-join so that NetworkFactory can take control of the network selection. + // TODO(b/117979585): Defer turning off auto-join. + mWifiConnectivityManager.enable(false); } } @Override protected void releaseNetworkFor(NetworkRequest networkRequest) { - if (mConnectionReqCount == 0) { - Log.e(TAG, "No valid network request to release"); - return; - } - if (--mConnectionReqCount == 0) { - mWifiConnectivityManager.setTrustedConnectionAllowed(false); + NetworkSpecifier ns = networkRequest.networkCapabilities.getNetworkSpecifier(); + if (ns == null) { + // Generic wifi request. Turn off auto-join if necessary. + if (mGenericConnectionReqCount == 0) { + Log.e(TAG, "No valid network request to release"); + return; + } + if (--mGenericConnectionReqCount == 0) { + mWifiConnectivityManager.setTrustedConnectionAllowed(false); + } + } else { + // Invalid network specifier. + if (!(ns instanceof WifiNetworkSpecifier)) { + Log.e(TAG, "Invalid network specifier mentioned. Ingoring"); + return; + } + if (!mActiveSpecificNetworkRequest.equals(networkRequest)) { + Log.e(TAG, "Network specifier does not match the active request. Ignoring"); + return; + } + // Release the active network request. + mActiveSpecificNetworkRequest = null; + mActiveSpecificNetworkRequestSpecifier = null; + // Cancel the periodic scans. + cancelPeriodicScans(); + // Re-enable Auto-join (if there is a generic request pending). + if (mGenericConnectionReqCount > 0) { + mWifiConnectivityManager.enable(true); + } } } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { super.dump(fd, pw, args); - pw.println(TAG + ": mConnectionReqCount " + mConnectionReqCount); + pw.println(TAG + ": mGenericConnectionReqCount " + mGenericConnectionReqCount); + pw.println(TAG + ": mActiveSpecificNetworkRequest " + mActiveSpecificNetworkRequest); } /** * Check if there is at-least one connection request. */ public boolean hasConnectionRequests() { - return mConnectionReqCount > 0; + return mGenericConnectionReqCount > 0 || mActiveSpecificNetworkRequest != null; + } + + /** + * Check if the request comes from foreground app/service. + */ + private boolean isRequestFromForegroundAppOrService(int requestorUid) { + try { + String requestorPackageName = mContext.getPackageManager().getNameForUid(requestorUid); + return mActivityManager.getPackageImportance(requestorPackageName) + <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; + } catch (SecurityException e) { + Log.e(TAG, "Failed to check the app state", e); + return false; + } + } + + /** + * Check if the request comes from foreground app. + */ + private boolean isRequestFromForegroundApp(int requestorUid) { + try { + String requestorPackageName = mContext.getPackageManager().getNameForUid(requestorUid); + return mActivityManager.getPackageImportance(requestorPackageName) + <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; + } catch (SecurityException e) { + Log.e(TAG, "Failed to check the app state", e); + return false; + } + } + + /** + * Helper method to populate WifiScanner handle. This is done lazily because + * WifiScanningService is started after WifiService. + */ + private void retrieveWifiScanner() { + if (mWifiScanner != null) return; + mWifiScanner = mWifiInjector.getWifiScanner(); + checkNotNull(mWifiScanner); + } + + private void startPeriodicScans() { + if (mActiveSpecificNetworkRequestSpecifier == null) { + Log.e(TAG, "Periodic scan triggered when there is no active network request. " + + "Ignoring..."); + return; + } + WifiNetworkSpecifier wns = mActiveSpecificNetworkRequestSpecifier; + WifiConfiguration wifiConfiguration = wns.wifiConfiguration; + if (wifiConfiguration.hiddenSSID) { + mScanSettings.hiddenNetworks = new WifiScanner.ScanSettings.HiddenNetwork[1]; + // Can't search for SSID pattern in hidden networks. + mScanSettings.hiddenNetworks[0] = + new WifiScanner.ScanSettings.HiddenNetwork( + addEnclosingQuotes(wns.ssidPatternMatcher.getPath())); + } + startScan(); + } + + private void cancelPeriodicScans() { + if (mPeriodicScanTimerSet) { + mAlarmManager.cancel(mPeriodicScanTimerListener); + mPeriodicScanTimerSet = false; + } + // Clear the hidden networks field after each request. + mScanSettings.hiddenNetworks = null; + } + + private void scheduleNextPeriodicScan() { + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + mClock.getElapsedSinceBootMillis() + PERIODIC_SCAN_INTERVAL_MS, + TAG, mPeriodicScanTimerListener, mHandler); + mPeriodicScanTimerSet = true; + } + + private void startScan() { + if (mActiveSpecificNetworkRequestSpecifier == null) { + Log.e(TAG, "Scan triggered when there is no active network request. Ignoring..."); + return; + } + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Starting the next scan for " + mActiveSpecificNetworkRequestSpecifier); + } + // Create a worksource using the caller's UID. + WorkSource workSource = new WorkSource(mActiveSpecificNetworkRequestSpecifier.requestorUid); + mWifiScanner.startScan(mScanSettings, mScanListener, workSource); + } + + private boolean doesScanResultMatchWifiNetworkSpecifier( + WifiNetworkSpecifier wns, ScanResult scanResult) { + if (!wns.ssidPatternMatcher.match(scanResult.SSID)) { + return false; + } + MacAddress bssid = MacAddress.fromString(scanResult.BSSID); + MacAddress matchBaseAddress = wns.bssidPatternMatcher.first; + MacAddress matchMask = wns.bssidPatternMatcher.second; + if (!bssid.matches(matchBaseAddress, matchMask)) { + return false; + } + if (ScanResultMatchInfo.getNetworkType(wns.wifiConfiguration) + != ScanResultMatchInfo.getNetworkType(scanResult)) { + return false; + } + return true; + } + + // Loops through the scan results and finds scan results matching the active network + // request. Returns a list of WifiConfiguration representing all the networks that + // match the active network request's specifier. + private List<WifiConfiguration> getNetworksMatchingActiveNetworkRequest( + ScanResult[] scanResults) { + // There could be multiple bssid's (i.e ScanResult) within the same ssid matching the + // specifier, we need to collapse all of them into a single network represented by a + // WifiConfiguration object. + // Use a map keyed in by pair of SSID & network type to collect the list of unique + // networks (i.e wificonfiguration objects) matching the specifier. + if (mActiveSpecificNetworkRequest == null) { + Log.e(TAG, "Scan results received with no active network request. Ignoring..."); + return new ArrayList<>(); + } + Map<Pair<String, Integer>, WifiConfiguration> matchedNetworks = new HashMap<>(); + WifiNetworkSpecifier wns = (WifiNetworkSpecifier) + mActiveSpecificNetworkRequest.networkCapabilities.getNetworkSpecifier(); + checkNotNull(wns); + + for (ScanResult scanResult : scanResults) { + if (doesScanResultMatchWifiNetworkSpecifier(wns, scanResult)) { + // Use the WifiConfiguration provided in the request & fill in the SSID + // & BSSID fields from ScanResult. + WifiConfiguration matchedNetwork = new WifiConfiguration(wns.wifiConfiguration); + matchedNetwork.SSID = addEnclosingQuotes(scanResult.SSID); + matchedNetwork.BSSID = scanResult.BSSID; + matchedNetworks.put( + Pair.create(matchedNetwork.SSID, + ScanResultMatchInfo.getNetworkType(matchedNetwork)), + matchedNetwork); + } + } + if (mVerboseLoggingEnabled) { + Log.e(TAG, "List of networks matching the active request " + + matchedNetworks.values()); + } + return new ArrayList<>(matchedNetworks.values()); + } + + private void sendNetworkRequestMatchCallbacksForActiveRequest( + List<WifiConfiguration> matchedNetworks) { + if (mRegisteredCallbacks.getNumCallbacks() == 0) { + Log.e(TAG, "No callback registered for sending network request matches. " + + "Ignoring..."); + return; + } + for (INetworkRequestMatchCallback callback : mRegisteredCallbacks.getCallbacks()) { + try { + callback.onMatch(matchedNetworks); + } catch (RemoteException e) { + Log.e(TAG, "Unable to invoke network request match callback " + callback, e); + } + } } } diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java index 6206d9b15..4b1766d79 100644 --- a/service/java/com/android/server/wifi/WifiServiceImpl.java +++ b/service/java/com/android/server/wifi/WifiServiceImpl.java @@ -58,6 +58,7 @@ import android.net.Network; import android.net.NetworkUtils; import android.net.Uri; import android.net.ip.IpClient; +import android.net.wifi.INetworkRequestMatchCallback; import android.net.wifi.ISoftApCallback; import android.net.wifi.ITrafficStateCallback; import android.net.wifi.IWifiManager; @@ -75,6 +76,7 @@ import android.os.AsyncTask; import android.os.BatteryStats; import android.os.Binder; import android.os.Bundle; +import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; @@ -2822,7 +2824,7 @@ public class WifiServiceImpl extends IWifiManager.Stub { /** * see {@link android.net.wifi.WifiManager#registerTrafficStateCallback( - * TrafficStateCallback, Handler)} + * WifiManager.TrafficStateCallback, Handler)} * * @param binder IBinder instance to allow cleanup if the app dies * @param callback Traffic State callback to register @@ -2890,4 +2892,61 @@ public class WifiServiceImpl extends IWifiManager.Stub { private static boolean hasAutomotiveFeature(Context context) { return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); } + + /** + * see {@link android.net.wifi.WifiManager#registerNetworkRequestMatchCallback( + * WifiManager.NetworkRequestMatchCallback, Handler)} ( + * + * @param binder IBinder instance to allow cleanup if the app dies + * @param callback Network Request Match callback to register + * @param callbackIdentifier Unique ID of the registering callback. This ID will be used to + * unregister the callback. + * See {@link #unregisterNetworkRequestMatchCallback(int)} (int)} + * + * @throws SecurityException if the caller does not have permission to register a callback + * @throws RemoteException if remote exception happens + * @throws IllegalArgumentException if the arguments are null or invalid + */ + @Override + public void registerNetworkRequestMatchCallback(IBinder binder, + INetworkRequestMatchCallback callback, + int callbackIdentifier) { + // verify arguments + if (binder == null) { + throw new IllegalArgumentException("Binder must not be null"); + } + if (callback == null) { + throw new IllegalArgumentException("Callback must not be null"); + } + enforceNetworkSettingsPermission(); + if (mVerboseLoggingEnabled) { + mLog.info("registerNetworkRequestMatchCallback uid=%") + .c(Binder.getCallingUid()).flush(); + } + // Post operation to handler thread + mClientHandler.post(() -> { + mClientModeImpl.addNetworkRequestMatchCallback(binder, callback, callbackIdentifier); + }); + } + + /** + * see {@link android.net.wifi.WifiManager#unregisterNetworkRequestMatchCallback( + * WifiManager.NetworkRequestMatchCallback)} + * + * @param callbackIdentifier Unique ID of the callback to be unregistered. + * + * @throws SecurityException if the caller does not have permission to register a callback + */ + @Override + public void unregisterNetworkRequestMatchCallback(int callbackIdentifier) { + enforceNetworkSettingsPermission(); + if (mVerboseLoggingEnabled) { + mLog.info("unregisterNetworkRequestMatchCallback uid=%") + .c(Binder.getCallingUid()).flush(); + } + // Post operation to handler thread + mClientHandler.post(() -> { + mClientModeImpl.removeNetworkRequestMatchCallback(callbackIdentifier); + }); + } } |