1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wifi; 18 19 import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.DISABLE_REASON_INFOS; 20 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.content.Context; 24 import android.net.wifi.ScanResult; 25 import android.net.wifi.WifiConfiguration; 26 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus; 27 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus.DisableReasonInfo; 28 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NetworkSelectionDisableReason; 29 import android.net.wifi.WifiSsid; 30 import android.util.ArrayMap; 31 import android.util.ArraySet; 32 import android.util.LocalLog; 33 import android.util.Log; 34 import android.util.SparseArray; 35 36 import com.android.internal.annotations.VisibleForTesting; 37 import com.android.server.wifi.util.WifiPermissionsUtil; 38 import com.android.wifi.resources.R; 39 40 import java.io.FileDescriptor; 41 import java.io.PrintWriter; 42 import java.lang.annotation.Retention; 43 import java.lang.annotation.RetentionPolicy; 44 import java.util.ArrayList; 45 import java.util.Calendar; 46 import java.util.Collections; 47 import java.util.HashSet; 48 import java.util.LinkedList; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Set; 52 import java.util.concurrent.TimeUnit; 53 import java.util.stream.Collectors; 54 import java.util.stream.Stream; 55 56 /** 57 * This class manages the addition and removal of BSSIDs to the BSSID blocklist, which is used 58 * for firmware roaming and network selection. 59 */ 60 public class WifiBlocklistMonitor { 61 // A special type association rejection 62 public static final int REASON_AP_UNABLE_TO_HANDLE_NEW_STA = 0; 63 // No internet 64 public static final int REASON_NETWORK_VALIDATION_FAILURE = 1; 65 // Wrong password error 66 public static final int REASON_WRONG_PASSWORD = 2; 67 // Incorrect EAP credentials 68 public static final int REASON_EAP_FAILURE = 3; 69 // Other association rejection failures 70 public static final int REASON_ASSOCIATION_REJECTION = 4; 71 // Association timeout failures. 72 public static final int REASON_ASSOCIATION_TIMEOUT = 5; 73 // Other authentication failures 74 public static final int REASON_AUTHENTICATION_FAILURE = 6; 75 // DHCP failures 76 public static final int REASON_DHCP_FAILURE = 7; 77 // Abnormal disconnect error 78 public static final int REASON_ABNORMAL_DISCONNECT = 8; 79 // AP initiated disconnect for a given duration. 80 public static final int REASON_FRAMEWORK_DISCONNECT_MBO_OCE = 9; 81 // Avoid connecting to the failed AP when trying to reconnect on other available candidates. 82 public static final int REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT = 10; 83 // The connected scorer has disconnected this network. 84 public static final int REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE = 11; 85 // Non-local disconnection in the middle of connecting state 86 public static final int REASON_NONLOCAL_DISCONNECT_CONNECTING = 12; 87 // Constant being used to keep track of how many failure reasons there are. 88 public static final int NUMBER_REASON_CODES = 13; 89 public static final int INVALID_REASON = -1; 90 91 @IntDef(prefix = { "REASON_" }, value = { 92 REASON_AP_UNABLE_TO_HANDLE_NEW_STA, 93 REASON_NETWORK_VALIDATION_FAILURE, 94 REASON_WRONG_PASSWORD, 95 REASON_EAP_FAILURE, 96 REASON_ASSOCIATION_REJECTION, 97 REASON_ASSOCIATION_TIMEOUT, 98 REASON_AUTHENTICATION_FAILURE, 99 REASON_DHCP_FAILURE, 100 REASON_ABNORMAL_DISCONNECT, 101 REASON_FRAMEWORK_DISCONNECT_MBO_OCE, 102 REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT, 103 REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE, 104 REASON_NONLOCAL_DISCONNECT_CONNECTING 105 }) 106 @Retention(RetentionPolicy.SOURCE) 107 public @interface FailureReason {} 108 109 // To be filled with values from the overlay. 110 private static final int[] FAILURE_COUNT_DISABLE_THRESHOLD = new int[NUMBER_REASON_CODES]; 111 private boolean mFailureCountDisableThresholdArrayInitialized = false; 112 private static final long ABNORMAL_DISCONNECT_RESET_TIME_MS = TimeUnit.HOURS.toMillis(3); 113 private static final int MIN_RSSI_DIFF_TO_UNBLOCK_BSSID = 5; 114 @VisibleForTesting 115 public static final int NUM_CONSECUTIVE_FAILURES_PER_NETWORK_EXP_BACKOFF = 5; 116 @VisibleForTesting 117 public static final long WIFI_CONFIG_MAX_DISABLE_DURATION_MILLIS = TimeUnit.HOURS.toMillis(18); 118 private static final String TAG = "WifiBlocklistMonitor"; 119 120 private final Context mContext; 121 private final WifiLastResortWatchdog mWifiLastResortWatchdog; 122 private final WifiConnectivityHelper mConnectivityHelper; 123 private final Clock mClock; 124 private final LocalLog mLocalLog; 125 private final WifiScoreCard mWifiScoreCard; 126 private final ScoringParams mScoringParams; 127 private final WifiMetrics mWifiMetrics; 128 private final WifiPermissionsUtil mWifiPermissionsUtil; 129 private final Map<Integer, BssidDisableReason> mBssidDisableReasons = 130 buildBssidDisableReasons(); 131 private final SparseArray<DisableReasonInfo> mDisableReasonInfo; 132 133 // Map of bssid to BssidStatus 134 private Map<String, BssidStatus> mBssidStatusMap = new ArrayMap<>(); 135 private Set<String> mDisabledSsids = new ArraySet<>(); 136 137 // Internal logger to make sure imporatant logs do not get lost. 138 private BssidBlocklistMonitorLogger mBssidBlocklistMonitorLogger = 139 new BssidBlocklistMonitorLogger(60); 140 141 // Map of ssid to Allowlist SSIDs 142 private Map<String, List<String>> mSsidAllowlistMap = new ArrayMap<>(); 143 private Set<WifiSsid> mSsidsAllowlistForNetworkSelection = new ArraySet<>(); 144 145 /** 146 * Verbose logging flag. Toggled by developer options. 147 */ 148 private boolean mVerboseLoggingEnabled = false; 149 150 buildBssidDisableReasons()151 private Map<Integer, BssidDisableReason> buildBssidDisableReasons() { 152 Map<Integer, BssidDisableReason> result = new ArrayMap<>(); 153 result.put(REASON_AP_UNABLE_TO_HANDLE_NEW_STA, new BssidDisableReason( 154 "REASON_AP_UNABLE_TO_HANDLE_NEW_STA", false, false)); 155 result.put(REASON_NETWORK_VALIDATION_FAILURE, new BssidDisableReason( 156 "REASON_NETWORK_VALIDATION_FAILURE", true, false)); 157 result.put(REASON_WRONG_PASSWORD, new BssidDisableReason( 158 "REASON_WRONG_PASSWORD", false, true)); 159 result.put(REASON_EAP_FAILURE, new BssidDisableReason( 160 "REASON_EAP_FAILURE", true, true)); 161 result.put(REASON_ASSOCIATION_REJECTION, new BssidDisableReason( 162 "REASON_ASSOCIATION_REJECTION", true, true)); 163 result.put(REASON_ASSOCIATION_TIMEOUT, new BssidDisableReason( 164 "REASON_ASSOCIATION_TIMEOUT", true, true)); 165 result.put(REASON_AUTHENTICATION_FAILURE, new BssidDisableReason( 166 "REASON_AUTHENTICATION_FAILURE", true, true)); 167 result.put(REASON_DHCP_FAILURE, new BssidDisableReason( 168 "REASON_DHCP_FAILURE", true, false)); 169 result.put(REASON_ABNORMAL_DISCONNECT, new BssidDisableReason( 170 "REASON_ABNORMAL_DISCONNECT", true, false)); 171 result.put(REASON_FRAMEWORK_DISCONNECT_MBO_OCE, new BssidDisableReason( 172 "REASON_FRAMEWORK_DISCONNECT_MBO_OCE", false, false)); 173 result.put(REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT, new BssidDisableReason( 174 "REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT", false, false)); 175 result.put(REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE, new BssidDisableReason( 176 "REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE", true, false)); 177 // TODO: b/174166637, add the same reason code in SSID blocklist and mark ignoreIfOnlyBssid 178 // to true once it is covered in SSID blocklist. 179 result.put(REASON_NONLOCAL_DISCONNECT_CONNECTING, new BssidDisableReason( 180 "REASON_NONLOCAL_DISCONNECT_CONNECTING", true, false)); 181 return result; 182 } 183 184 class BssidDisableReason { 185 public final String reasonString; 186 public final boolean isLowRssiSensitive; 187 public final boolean ignoreIfOnlyBssid; 188 BssidDisableReason(String reasonString, boolean isLowRssiSensitive, boolean ignoreIfOnlyBssid)189 BssidDisableReason(String reasonString, boolean isLowRssiSensitive, 190 boolean ignoreIfOnlyBssid) { 191 this.reasonString = reasonString; 192 this.isLowRssiSensitive = isLowRssiSensitive; 193 this.ignoreIfOnlyBssid = ignoreIfOnlyBssid; 194 } 195 } 196 197 /** 198 * Create a new instance of WifiBlocklistMonitor 199 */ WifiBlocklistMonitor(Context context, WifiConnectivityHelper connectivityHelper, WifiLastResortWatchdog wifiLastResortWatchdog, Clock clock, LocalLog localLog, WifiScoreCard wifiScoreCard, ScoringParams scoringParams, WifiMetrics wifiMetrics, WifiPermissionsUtil wifiPermissionsUtil)200 WifiBlocklistMonitor(Context context, WifiConnectivityHelper connectivityHelper, 201 WifiLastResortWatchdog wifiLastResortWatchdog, Clock clock, LocalLog localLog, 202 WifiScoreCard wifiScoreCard, ScoringParams scoringParams, WifiMetrics wifiMetrics, 203 WifiPermissionsUtil wifiPermissionsUtil) { 204 mContext = context; 205 mConnectivityHelper = connectivityHelper; 206 mWifiLastResortWatchdog = wifiLastResortWatchdog; 207 mClock = clock; 208 mLocalLog = localLog; 209 mWifiScoreCard = wifiScoreCard; 210 mScoringParams = scoringParams; 211 mDisableReasonInfo = DISABLE_REASON_INFOS.clone(); 212 mWifiMetrics = wifiMetrics; 213 mWifiPermissionsUtil = wifiPermissionsUtil; 214 loadCustomConfigsForDisableReasonInfos(); 215 } 216 217 // A helper to log debugging information in the local log buffer, which can 218 // be retrieved in bugreport. localLog(String log)219 private void localLog(String log) { 220 mLocalLog.log(log); 221 } 222 223 /** 224 * calculates the blocklist duration based on the current failure streak with exponential 225 * backoff. 226 * @param failureStreak should be greater or equal to 0. 227 * @return duration to block the BSSID in milliseconds 228 */ getBlocklistDurationWithExponentialBackoff(int failureStreak, int baseBlocklistDurationMs)229 private long getBlocklistDurationWithExponentialBackoff(int failureStreak, 230 int baseBlocklistDurationMs) { 231 failureStreak = Math.min(failureStreak, mContext.getResources().getInteger( 232 R.integer.config_wifiBssidBlocklistMonitorFailureStreakCap)); 233 if (failureStreak < 1) { 234 return baseBlocklistDurationMs; 235 } 236 return (long) (Math.pow(2.0, (double) failureStreak) * baseBlocklistDurationMs); 237 } 238 239 /** 240 * Dump the local log buffer and other internal state of WifiBlocklistMonitor. 241 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)242 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 243 pw.println("Dump of WifiBlocklistMonitor"); 244 mLocalLog.dump(fd, pw, args); 245 pw.println("WifiBlocklistMonitor - Bssid blocklist begin ----"); 246 mBssidStatusMap.values().stream().forEach(entry -> pw.println(entry)); 247 pw.println("WifiBlocklistMonitor - Bssid blocklist end ----"); 248 mBssidBlocklistMonitorLogger.dump(pw); 249 } 250 addToBlocklist(@onNull BssidStatus entry, long durationMs, @FailureReason int reason, int rssi)251 private void addToBlocklist(@NonNull BssidStatus entry, long durationMs, 252 @FailureReason int reason, int rssi) { 253 entry.setAsBlocked(durationMs, reason, rssi); 254 localLog(TAG + " addToBlocklist: bssid=" + entry.bssid + ", ssid=" + entry.ssid 255 + ", durationMs=" + durationMs + ", reason=" + getFailureReasonString(reason) 256 + ", rssi=" + rssi); 257 } 258 259 /** 260 * increments the number of failures for the given bssid and returns the number of failures so 261 * far. 262 * @return the BssidStatus for the BSSID 263 */ incrementFailureCountForBssid( @onNull String bssid, @NonNull String ssid, int reasonCode)264 private @NonNull BssidStatus incrementFailureCountForBssid( 265 @NonNull String bssid, @NonNull String ssid, int reasonCode) { 266 BssidStatus status = getOrCreateBssidStatus(bssid, ssid); 267 status.incrementFailureCount(reasonCode); 268 return status; 269 } 270 271 /** 272 * Get the BssidStatus representing the BSSID or create a new one if it doesn't exist. 273 */ getOrCreateBssidStatus(@onNull String bssid, @NonNull String ssid)274 private @NonNull BssidStatus getOrCreateBssidStatus(@NonNull String bssid, 275 @NonNull String ssid) { 276 BssidStatus status = mBssidStatusMap.get(bssid); 277 if (status == null || !ssid.equals(status.ssid)) { 278 if (status != null) { 279 localLog("getOrCreateBssidStatus: BSSID=" + bssid + ", SSID changed from " 280 + status.ssid + " to " + ssid); 281 } 282 status = new BssidStatus(bssid, ssid); 283 mBssidStatusMap.put(bssid, status); 284 } 285 return status; 286 } 287 288 /** 289 * Set a list of SSIDs that will always be enabled for network selection. 290 */ setSsidsAllowlist(@onNull List<WifiSsid> ssids)291 public void setSsidsAllowlist(@NonNull List<WifiSsid> ssids) { 292 mSsidsAllowlistForNetworkSelection = new ArraySet<>(ssids); 293 } 294 295 /** 296 * Get the list of SSIDs that will always be enabled for network selection. 297 */ getSsidsAllowlist()298 public List<WifiSsid> getSsidsAllowlist() { 299 return new ArrayList<>(mSsidsAllowlistForNetworkSelection); 300 } 301 isValidNetworkAndFailureReasonForBssidBlocking(String bssid, WifiConfiguration config, @FailureReason int reasonCode)302 private boolean isValidNetworkAndFailureReasonForBssidBlocking(String bssid, 303 WifiConfiguration config, @FailureReason int reasonCode) { 304 if (bssid == null || config == null 305 || bssid.equals(ClientModeImpl.SUPPLICANT_BSSID_ANY) 306 || reasonCode < 0 || reasonCode >= NUMBER_REASON_CODES) { 307 Log.e(TAG, "Invalid input: BSSID=" + bssid + ", config=" + config 308 + ", reasonCode=" + reasonCode); 309 return false; 310 } 311 return !isConfigExemptFromBlocklist(config); 312 } 313 isConfigExemptFromBlocklist(@onNull WifiConfiguration config)314 private boolean isConfigExemptFromBlocklist(@NonNull WifiConfiguration config) { 315 try { 316 // Only enterprise owned configs that are in the doNoBlocklist are exempt from 317 // blocklisting. 318 WifiSsid wifiSsid = WifiSsid.fromString(config.SSID); 319 return mSsidsAllowlistForNetworkSelection.contains(wifiSsid) 320 && mWifiPermissionsUtil.isAdmin(config.creatorUid, config.creatorName); 321 } catch (IllegalArgumentException e) { 322 Log.e(TAG, "Failed to convert raw ssid=" + config.SSID + " to WifiSsid"); 323 return false; 324 } 325 } 326 shouldWaitForWatchdogToTriggerFirst(String bssid, @FailureReason int reasonCode)327 private boolean shouldWaitForWatchdogToTriggerFirst(String bssid, 328 @FailureReason int reasonCode) { 329 boolean isWatchdogRelatedFailure = reasonCode == REASON_ASSOCIATION_REJECTION 330 || reasonCode == REASON_AUTHENTICATION_FAILURE 331 || reasonCode == REASON_DHCP_FAILURE; 332 return isWatchdogRelatedFailure && mWifiLastResortWatchdog.shouldIgnoreBssidUpdate(bssid); 333 } 334 335 /** 336 * Block any attempts to auto-connect to the BSSID for the specified duration. 337 * This is meant to be used by features that need wifi to avoid a BSSID for a certain duration, 338 * and thus will not increase the failure streak counters. 339 * @param bssid identifies the AP to block. 340 * @param config identifies the WifiConfiguration. 341 * @param durationMs duration in millis to block. 342 * @param blockReason reason for blocking the BSSID. 343 * @param rssi the latest RSSI observed. 344 */ blockBssidForDurationMs(@onNull String bssid, WifiConfiguration config, long durationMs, @FailureReason int blockReason, int rssi)345 public void blockBssidForDurationMs(@NonNull String bssid, WifiConfiguration config, 346 long durationMs, @FailureReason int blockReason, int rssi) { 347 if (durationMs <= 0 || !isValidNetworkAndFailureReasonForBssidBlocking( 348 bssid, config, blockReason)) { 349 Log.e(TAG, "Invalid input: BSSID=" + bssid + ", config=" + config 350 + ", durationMs=" + durationMs + ", blockReason=" + blockReason 351 + ", rssi=" + rssi); 352 return; 353 } 354 BssidStatus status = getOrCreateBssidStatus(bssid, config.SSID); 355 if (status.isInBlocklist 356 && status.blocklistEndTimeMs - mClock.getWallClockMillis() > durationMs) { 357 // Return because this BSSID is already being blocked for a longer time. 358 return; 359 } 360 addToBlocklist(status, durationMs, blockReason, rssi); 361 } 362 getFailureReasonString(@ailureReason int reasonCode)363 private String getFailureReasonString(@FailureReason int reasonCode) { 364 if (reasonCode == INVALID_REASON) { 365 return "INVALID_REASON"; 366 } 367 BssidDisableReason disableReason = mBssidDisableReasons.get(reasonCode); 368 if (disableReason == null) { 369 return "REASON_UNKNOWN"; 370 } 371 return disableReason.reasonString; 372 } 373 getFailureThresholdForReason(@ailureReason int reasonCode)374 private int getFailureThresholdForReason(@FailureReason int reasonCode) { 375 if (mFailureCountDisableThresholdArrayInitialized) { 376 return FAILURE_COUNT_DISABLE_THRESHOLD[reasonCode]; 377 } 378 FAILURE_COUNT_DISABLE_THRESHOLD[REASON_AP_UNABLE_TO_HANDLE_NEW_STA] = 379 mContext.getResources().getInteger( 380 R.integer.config_wifiBssidBlocklistMonitorApUnableToHandleNewStaThreshold); 381 FAILURE_COUNT_DISABLE_THRESHOLD[REASON_NETWORK_VALIDATION_FAILURE] = 382 mContext.getResources().getInteger(R.integer 383 .config_wifiBssidBlocklistMonitorNetworkValidationFailureThreshold); 384 FAILURE_COUNT_DISABLE_THRESHOLD[REASON_WRONG_PASSWORD] = 385 mContext.getResources().getInteger( 386 R.integer.config_wifiBssidBlocklistMonitorWrongPasswordThreshold); 387 FAILURE_COUNT_DISABLE_THRESHOLD[REASON_EAP_FAILURE] = 388 mContext.getResources().getInteger( 389 R.integer.config_wifiBssidBlocklistMonitorEapFailureThreshold); 390 FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ASSOCIATION_REJECTION] = 391 mContext.getResources().getInteger( 392 R.integer.config_wifiBssidBlocklistMonitorAssociationRejectionThreshold); 393 FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ASSOCIATION_TIMEOUT] = 394 mContext.getResources().getInteger( 395 R.integer.config_wifiBssidBlocklistMonitorAssociationTimeoutThreshold); 396 FAILURE_COUNT_DISABLE_THRESHOLD[REASON_AUTHENTICATION_FAILURE] = 397 mContext.getResources().getInteger( 398 R.integer.config_wifiBssidBlocklistMonitorAuthenticationFailureThreshold); 399 FAILURE_COUNT_DISABLE_THRESHOLD[REASON_DHCP_FAILURE] = 400 mContext.getResources().getInteger( 401 R.integer.config_wifiBssidBlocklistMonitorDhcpFailureThreshold); 402 FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ABNORMAL_DISCONNECT] = 403 mContext.getResources().getInteger( 404 R.integer.config_wifiBssidBlocklistMonitorAbnormalDisconnectThreshold); 405 FAILURE_COUNT_DISABLE_THRESHOLD[REASON_NONLOCAL_DISCONNECT_CONNECTING] = 406 mContext.getResources().getInteger(R.integer 407 .config_wifiBssidBlocklistMonitorNonlocalDisconnectConnectingThreshold); 408 mFailureCountDisableThresholdArrayInitialized = true; 409 return FAILURE_COUNT_DISABLE_THRESHOLD[reasonCode]; 410 } 411 handleBssidConnectionFailureInternal(String bssid, String ssid, @FailureReason int reasonCode, int rssi)412 private boolean handleBssidConnectionFailureInternal(String bssid, String ssid, 413 @FailureReason int reasonCode, int rssi) { 414 BssidStatus entry = incrementFailureCountForBssid(bssid, ssid, reasonCode); 415 int failureThreshold = getFailureThresholdForReason(reasonCode); 416 int currentStreak = mWifiScoreCard.getBssidBlocklistStreak(ssid, bssid, reasonCode); 417 if (currentStreak > 0 || entry.failureCount[reasonCode] >= failureThreshold) { 418 // To rule out potential device side issues, don't add to blocklist if 419 // WifiLastResortWatchdog is still not triggered 420 if (shouldWaitForWatchdogToTriggerFirst(bssid, reasonCode)) { 421 localLog("Ignoring failure to wait for watchdog to trigger first."); 422 return false; 423 } 424 int baseBlockDurationMs = getBaseBlockDurationForReason(reasonCode); 425 addToBlocklist(entry, 426 getBlocklistDurationWithExponentialBackoff(currentStreak, baseBlockDurationMs), 427 reasonCode, rssi); 428 mWifiScoreCard.incrementBssidBlocklistStreak(ssid, bssid, reasonCode); 429 return true; 430 } 431 return false; 432 } 433 getBaseBlockDurationForReason(int blockReason)434 private int getBaseBlockDurationForReason(int blockReason) { 435 switch (blockReason) { 436 case REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE: 437 return mContext.getResources().getInteger(R.integer 438 .config_wifiBssidBlocklistMonitorConnectedScoreBaseBlockDurationMs); 439 default: 440 return mContext.getResources().getInteger( 441 R.integer.config_wifiBssidBlocklistMonitorBaseBlockDurationMs); 442 } 443 } 444 445 /** 446 * Note a failure event on a bssid and perform appropriate actions. 447 * @return True if the blocklist has been modified. 448 */ handleBssidConnectionFailure(String bssid, WifiConfiguration config, @FailureReason int reasonCode, int rssi)449 public boolean handleBssidConnectionFailure(String bssid, WifiConfiguration config, 450 @FailureReason int reasonCode, int rssi) { 451 if (!isValidNetworkAndFailureReasonForBssidBlocking(bssid, config, reasonCode)) { 452 return false; 453 } 454 String ssid = config.SSID; 455 BssidDisableReason bssidDisableReason = mBssidDisableReasons.get(reasonCode); 456 if (bssidDisableReason == null) { 457 Log.e(TAG, "Bssid disable reason not found. ReasonCode=" + reasonCode); 458 return false; 459 } 460 if (bssidDisableReason.ignoreIfOnlyBssid && !mDisabledSsids.contains(ssid) 461 && mWifiLastResortWatchdog.isBssidOnlyApOfSsid(bssid)) { 462 localLog("Ignoring BSSID failure due to no other APs available. BSSID=" + bssid); 463 return false; 464 } 465 if (reasonCode == REASON_ABNORMAL_DISCONNECT) { 466 long connectionTime = mWifiScoreCard.getBssidConnectionTimestampMs(ssid, bssid); 467 // only count disconnects that happen shortly after a connection. 468 if (mClock.getWallClockMillis() - connectionTime 469 > mContext.getResources().getInteger( 470 R.integer.config_wifiBssidBlocklistAbnormalDisconnectTimeWindowMs)) { 471 return false; 472 } 473 } 474 return handleBssidConnectionFailureInternal(bssid, ssid, reasonCode, rssi); 475 } 476 477 /** 478 * To be called when a WifiConfiguration is either temporarily disabled or permanently disabled. 479 * @param ssid of the WifiConfiguration that is disabled. 480 */ handleWifiConfigurationDisabled(String ssid)481 public void handleWifiConfigurationDisabled(String ssid) { 482 if (ssid != null) { 483 mDisabledSsids.add(ssid); 484 } 485 } 486 487 /** 488 * Note a connection success event on a bssid and clear appropriate failure counters. 489 */ handleBssidConnectionSuccess(@onNull String bssid, @NonNull String ssid)490 public void handleBssidConnectionSuccess(@NonNull String bssid, @NonNull String ssid) { 491 mDisabledSsids.remove(ssid); 492 /** 493 * First reset the blocklist streak. 494 * This needs to be done even if a BssidStatus is not found, since the BssidStatus may 495 * have been removed due to blocklist timeout. 496 */ 497 mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_AP_UNABLE_TO_HANDLE_NEW_STA); 498 mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_WRONG_PASSWORD); 499 mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_EAP_FAILURE); 500 mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ASSOCIATION_REJECTION); 501 mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ASSOCIATION_TIMEOUT); 502 mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_AUTHENTICATION_FAILURE); 503 mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, 504 REASON_NONLOCAL_DISCONNECT_CONNECTING); 505 506 long connectionTime = mClock.getWallClockMillis(); 507 long prevConnectionTime = mWifiScoreCard.setBssidConnectionTimestampMs( 508 ssid, bssid, connectionTime); 509 if (connectionTime - prevConnectionTime > ABNORMAL_DISCONNECT_RESET_TIME_MS) { 510 mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ABNORMAL_DISCONNECT); 511 mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, 512 REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE); 513 } 514 515 BssidStatus status = mBssidStatusMap.get(bssid); 516 if (status == null) { 517 return; 518 } 519 // Clear the L2 failure counters 520 status.failureCount[REASON_AP_UNABLE_TO_HANDLE_NEW_STA] = 0; 521 status.failureCount[REASON_WRONG_PASSWORD] = 0; 522 status.failureCount[REASON_EAP_FAILURE] = 0; 523 status.failureCount[REASON_ASSOCIATION_REJECTION] = 0; 524 status.failureCount[REASON_ASSOCIATION_TIMEOUT] = 0; 525 status.failureCount[REASON_AUTHENTICATION_FAILURE] = 0; 526 status.failureCount[REASON_NONLOCAL_DISCONNECT_CONNECTING] = 0; 527 if (connectionTime - prevConnectionTime > ABNORMAL_DISCONNECT_RESET_TIME_MS) { 528 status.failureCount[REASON_ABNORMAL_DISCONNECT] = 0; 529 } 530 } 531 532 /** 533 * Note a successful network validation on a BSSID and clear appropriate failure counters. 534 * And then remove the BSSID from blocklist. 535 */ handleNetworkValidationSuccess(@onNull String bssid, @NonNull String ssid)536 public void handleNetworkValidationSuccess(@NonNull String bssid, @NonNull String ssid) { 537 mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_NETWORK_VALIDATION_FAILURE); 538 BssidStatus status = mBssidStatusMap.get(bssid); 539 if (status == null) { 540 return; 541 } 542 status.failureCount[REASON_NETWORK_VALIDATION_FAILURE] = 0; 543 /** 544 * Network validation may take more than 1 tries to succeed. 545 * remove the BSSID from blocklist to make sure we are not accidentally blocking good 546 * BSSIDs. 547 **/ 548 if (status.isInBlocklist) { 549 mBssidBlocklistMonitorLogger.logBssidUnblocked(status, "Network validation success"); 550 mBssidStatusMap.remove(bssid); 551 } 552 } 553 554 /** 555 * Note a successful DHCP provisioning and clear appropriate faliure counters. 556 */ handleDhcpProvisioningSuccess(@onNull String bssid, @NonNull String ssid)557 public void handleDhcpProvisioningSuccess(@NonNull String bssid, @NonNull String ssid) { 558 mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_DHCP_FAILURE); 559 BssidStatus status = mBssidStatusMap.get(bssid); 560 if (status == null) { 561 return; 562 } 563 status.failureCount[REASON_DHCP_FAILURE] = 0; 564 } 565 566 /** 567 * Note the removal of a network from the Wifi stack's internal database and reset 568 * appropriate failure counters. 569 * @param ssid 570 */ handleNetworkRemoved(@onNull String ssid)571 public void handleNetworkRemoved(@NonNull String ssid) { 572 clearBssidBlocklistForSsid(ssid); 573 mWifiScoreCard.resetBssidBlocklistStreakForSsid(ssid); 574 } 575 576 /** 577 * Clears the blocklist for BSSIDs associated with the input SSID only. 578 * @param ssid 579 */ clearBssidBlocklistForSsid(@onNull String ssid)580 public void clearBssidBlocklistForSsid(@NonNull String ssid) { 581 int prevSize = mBssidStatusMap.size(); 582 mBssidStatusMap.entrySet().removeIf(e -> { 583 BssidStatus status = e.getValue(); 584 if (status.ssid == null) { 585 return false; 586 } 587 if (status.ssid.equals(ssid)) { 588 mBssidBlocklistMonitorLogger.logBssidUnblocked( 589 status, "clearBssidBlocklistForSsid"); 590 return true; 591 } 592 return false; 593 }); 594 int diff = prevSize - mBssidStatusMap.size(); 595 if (diff > 0) { 596 localLog(TAG + " clearBssidBlocklistForSsid: SSID=" + ssid 597 + ", num BSSIDs cleared=" + diff); 598 } 599 } 600 601 /** 602 * Clears the BSSID blocklist and failure counters. 603 */ clearBssidBlocklist()604 public void clearBssidBlocklist() { 605 if (mBssidStatusMap.size() > 0) { 606 int prevSize = mBssidStatusMap.size(); 607 for (BssidStatus status : mBssidStatusMap.values()) { 608 mBssidBlocklistMonitorLogger.logBssidUnblocked(status, "clearBssidBlocklist"); 609 } 610 mBssidStatusMap.clear(); 611 localLog(TAG + " clearBssidBlocklist: num BSSIDs cleared=" 612 + (prevSize - mBssidStatusMap.size())); 613 } 614 mDisabledSsids.clear(); 615 } 616 617 /** 618 * @param ssid 619 * @return the number of BSSIDs currently in the blocklist for the |ssid|. 620 */ updateAndGetNumBlockedBssidsForSsid(@onNull String ssid)621 public int updateAndGetNumBlockedBssidsForSsid(@NonNull String ssid) { 622 return (int) updateAndGetBssidBlocklistInternal() 623 .filter(entry -> ssid.equals(entry.ssid)).count(); 624 } 625 getNumBlockedBssidsForSsids(@onNull Set<String> ssids)626 private int getNumBlockedBssidsForSsids(@NonNull Set<String> ssids) { 627 if (ssids.isEmpty()) { 628 return 0; 629 } 630 return (int) mBssidStatusMap.values().stream() 631 .filter(entry -> entry.isInBlocklist && ssids.contains(entry.ssid)) 632 .count(); 633 } 634 635 /** 636 * Overloaded version of updateAndGetBssidBlocklist. 637 * Accepts a @Nullable String ssid as input, and updates the firmware roaming 638 * configuration if the blocklist for the input ssid has been changed. 639 * @param ssids set of ssids to update firmware roaming configuration for. 640 * @return Set of BSSIDs currently in the blocklist 641 */ updateAndGetBssidBlocklistForSsids(@onNull Set<String> ssids)642 public Set<String> updateAndGetBssidBlocklistForSsids(@NonNull Set<String> ssids) { 643 int numBefore = getNumBlockedBssidsForSsids(ssids); 644 Set<String> bssidBlocklist = updateAndGetBssidBlocklist(); 645 if (getNumBlockedBssidsForSsids(ssids) != numBefore) { 646 updateFirmwareRoamingConfiguration(ssids); 647 } 648 return bssidBlocklist; 649 } 650 651 /** 652 * Gets the BSSIDs that are currently in the blocklist. 653 * @return Set of BSSIDs currently in the blocklist 654 */ updateAndGetBssidBlocklist()655 public Set<String> updateAndGetBssidBlocklist() { 656 return updateAndGetBssidBlocklistInternal() 657 .map(entry -> entry.bssid) 658 .collect(Collectors.toSet()); 659 } 660 661 /** 662 * Gets the list of block reasons for BSSIDs currently in the blocklist. 663 * @return The set of unique reasons for blocking BSSIDs with this SSID. 664 */ getFailureReasonsForSsid(@onNull String ssid)665 public Set<Integer> getFailureReasonsForSsid(@NonNull String ssid) { 666 if (ssid == null) { 667 return Collections.emptySet(); 668 } 669 return mBssidStatusMap.values().stream() 670 .filter(entry -> entry.isInBlocklist && ssid.equals(entry.ssid)) 671 .map(entry -> entry.blockReason) 672 .collect(Collectors.toSet()); 673 } 674 675 /** 676 * Attempts to re-enable BSSIDs that likely experienced failures due to low RSSI. 677 * @param scanDetails 678 * @return the list of ScanDetails for which BSSIDs were re-enabled. 679 */ tryEnablingBlockedBssids(List<ScanDetail> scanDetails)680 public @NonNull List<ScanDetail> tryEnablingBlockedBssids(List<ScanDetail> scanDetails) { 681 if (scanDetails == null) { 682 return Collections.EMPTY_LIST; 683 } 684 List<ScanDetail> results = new ArrayList<>(); 685 for (ScanDetail scanDetail : scanDetails) { 686 ScanResult scanResult = scanDetail.getScanResult(); 687 if (scanResult == null) { 688 continue; 689 } 690 BssidStatus status = mBssidStatusMap.get(scanResult.BSSID); 691 if (status == null || !status.isInBlocklist 692 || !isLowRssiSensitiveFailure(status.blockReason)) { 693 continue; 694 } 695 int sufficientRssi = mScoringParams.getSufficientRssi(scanResult.frequency); 696 int goodRssi = mScoringParams.getGoodRssi(scanResult.frequency); 697 boolean rssiMinDiffAchieved = scanResult.level - status.lastRssi 698 >= MIN_RSSI_DIFF_TO_UNBLOCK_BSSID; 699 boolean sufficientRssiBreached = 700 status.lastRssi < sufficientRssi && scanResult.level >= sufficientRssi; 701 boolean goodRssiBreached = status.lastRssi < goodRssi && scanResult.level >= goodRssi; 702 if (rssiMinDiffAchieved && (sufficientRssiBreached || goodRssiBreached)) { 703 mBssidBlocklistMonitorLogger.logBssidUnblocked( 704 status, "rssi significantly improved"); 705 mBssidStatusMap.remove(status.bssid); 706 results.add(scanDetail); 707 } 708 } 709 return results; 710 } 711 isLowRssiSensitiveFailure(int blockReason)712 private boolean isLowRssiSensitiveFailure(int blockReason) { 713 return mBssidDisableReasons.get(blockReason) == null ? false 714 : mBssidDisableReasons.get(blockReason).isLowRssiSensitive; 715 } 716 717 /** 718 * Removes expired BssidStatus entries and then return remaining entries in the blocklist. 719 * @return Stream of BssidStatus for BSSIDs that are in the blocklist. 720 */ updateAndGetBssidBlocklistInternal()721 private Stream<BssidStatus> updateAndGetBssidBlocklistInternal() { 722 Stream.Builder<BssidStatus> builder = Stream.builder(); 723 long curTime = mClock.getWallClockMillis(); 724 mBssidStatusMap.entrySet().removeIf(e -> { 725 BssidStatus status = e.getValue(); 726 if (status.isInBlocklist) { 727 if (status.blocklistEndTimeMs < curTime) { 728 mBssidBlocklistMonitorLogger.logBssidUnblocked( 729 status, "updateAndGetBssidBlocklistInternal"); 730 return true; 731 } 732 builder.accept(status); 733 } 734 return false; 735 }); 736 return builder.build(); 737 } 738 739 /** 740 * Sends the BSSIDs belonging to the input SSID down to the firmware to prevent auto-roaming 741 * to those BSSIDs. 742 * @param ssids 743 */ updateFirmwareRoamingConfiguration(@onNull Set<String> ssids)744 public void updateFirmwareRoamingConfiguration(@NonNull Set<String> ssids) { 745 if (!mConnectivityHelper.isFirmwareRoamingSupported()) { 746 return; 747 } 748 ArrayList<String> bssidBlocklist = updateAndGetBssidBlocklistInternal() 749 .filter(entry -> ssids.contains(entry.ssid)) 750 .sorted((o1, o2) -> (int) (o2.blocklistEndTimeMs - o1.blocklistEndTimeMs)) 751 .map(entry -> entry.bssid) 752 .collect(Collectors.toCollection(ArrayList::new)); 753 int fwMaxBlocklistSize = mConnectivityHelper.getMaxNumBlocklistBssid(); 754 if (fwMaxBlocklistSize <= 0) { 755 Log.e(TAG, "Invalid max BSSID blocklist size: " + fwMaxBlocklistSize); 756 return; 757 } 758 // Having the blocklist size exceeding firmware max limit is unlikely because we have 759 // already flitered based on SSID. But just in case this happens, we are prioritizing 760 // sending down BSSIDs blocked for the longest time. 761 if (bssidBlocklist.size() > fwMaxBlocklistSize) { 762 bssidBlocklist = new ArrayList<String>(bssidBlocklist.subList(0, 763 fwMaxBlocklistSize)); 764 } 765 766 // Collect all the allowed SSIDs 767 Set<String> allowedSsidSet = new HashSet<>(); 768 for (String ssid : ssids) { 769 List<String> allowedSsidsForSsid = mSsidAllowlistMap.get(ssid); 770 if (allowedSsidsForSsid != null) { 771 allowedSsidSet.addAll(allowedSsidsForSsid); 772 } 773 } 774 ArrayList<String> ssidAllowlist = new ArrayList<>(allowedSsidSet); 775 int allowlistSize = ssidAllowlist.size(); 776 int maxAllowlistSize = mConnectivityHelper.getMaxNumAllowlistSsid(); 777 if (maxAllowlistSize <= 0) { 778 Log.wtf(TAG, "Invalid max SSID allowlist size: " + maxAllowlistSize); 779 return; 780 } 781 if (allowlistSize > maxAllowlistSize) { 782 ssidAllowlist = new ArrayList<>(ssidAllowlist.subList(0, maxAllowlistSize)); 783 localLog("Trim down SSID allowlist size from " + allowlistSize + " to " 784 + ssidAllowlist.size()); 785 } 786 787 // plumb down to HAL 788 String message = "set firmware roaming configurations. " 789 + "bssidBlocklist="; 790 if (bssidBlocklist.size() == 0) { 791 message += "<EMPTY>"; 792 } else { 793 message += String.join(", ", bssidBlocklist); 794 } 795 if (!mConnectivityHelper.setFirmwareRoamingConfiguration(bssidBlocklist, ssidAllowlist)) { 796 Log.e(TAG, "Failed to " + message); 797 mBssidBlocklistMonitorLogger.log("Failed to " + message); 798 } else { 799 mBssidBlocklistMonitorLogger.log("Successfully " + message); 800 } 801 } 802 803 @VisibleForTesting getBssidBlocklistMonitorLoggerSize()804 public int getBssidBlocklistMonitorLoggerSize() { 805 return mBssidBlocklistMonitorLogger.size(); 806 } 807 808 private class BssidBlocklistMonitorLogger { 809 private LinkedList<String> mLogBuffer = new LinkedList<>(); 810 private int mBufferSize; 811 BssidBlocklistMonitorLogger(int bufferSize)812 BssidBlocklistMonitorLogger(int bufferSize) { 813 mBufferSize = bufferSize; 814 } 815 logBssidUnblocked(BssidStatus bssidStatus, String unblockReason)816 public void logBssidUnblocked(BssidStatus bssidStatus, String unblockReason) { 817 // only log history for Bssids that had been blocked. 818 if (bssidStatus == null || !bssidStatus.isInBlocklist) { 819 return; 820 } 821 StringBuilder sb = createStringBuilderWithLogTime(); 822 sb.append(", Bssid unblocked, Reason=" + unblockReason); 823 sb.append(", Unblocked BssidStatus={" + bssidStatus.toString() + "}"); 824 logInternal(sb.toString()); 825 } 826 827 // cache a single line of log message in the rotating buffer log(String message)828 public void log(String message) { 829 if (message == null) { 830 return; 831 } 832 StringBuilder sb = createStringBuilderWithLogTime(); 833 sb.append(" " + message); 834 logInternal(sb.toString()); 835 } 836 createStringBuilderWithLogTime()837 private StringBuilder createStringBuilderWithLogTime() { 838 StringBuilder sb = new StringBuilder(); 839 Calendar c = Calendar.getInstance(); 840 c.setTimeInMillis(mClock.getWallClockMillis()); 841 sb.append("logTime=").append(c.get(Calendar.MONTH)).append("-") 842 .append(c.get(Calendar.DAY_OF_MONTH)).append(" ") 843 .append(c.get(Calendar.HOUR_OF_DAY)).append(":") 844 .append(c.get(Calendar.MINUTE)).append(":") 845 .append(c.get(Calendar.SECOND)).append(".") 846 .append(c.get(Calendar.MILLISECOND)); 847 return sb; 848 } 849 logInternal(String message)850 private void logInternal(String message) { 851 mLogBuffer.add(message); 852 if (mLogBuffer.size() > mBufferSize) { 853 mLogBuffer.removeFirst(); 854 } 855 } 856 857 @VisibleForTesting size()858 public int size() { 859 return mLogBuffer.size(); 860 } 861 dump(PrintWriter pw)862 public void dump(PrintWriter pw) { 863 pw.println("WifiBlocklistMonitor - Bssid blocklist logs begin ----"); 864 for (String line : mLogBuffer) { 865 pw.println(line); 866 } 867 pw.println("List of SSIDs to never block:"); 868 for (WifiSsid ssid : mSsidsAllowlistForNetworkSelection) { 869 pw.println(ssid.toString()); 870 } 871 pw.println("WifiBlocklistMonitor - Bssid blocklist logs end ----"); 872 } 873 } 874 875 /** 876 * Helper class that counts the number of failures per BSSID. 877 */ 878 private class BssidStatus { 879 public final String bssid; 880 public final String ssid; 881 public final int[] failureCount = new int[NUMBER_REASON_CODES]; 882 public int blockReason = INVALID_REASON; // reason of blocking this BSSID 883 // The latest RSSI that's seen before this BSSID is added to blocklist. 884 public int lastRssi = 0; 885 886 // The following are used to flag how long this BSSID stays in the blocklist. 887 public boolean isInBlocklist; 888 public long blocklistEndTimeMs; 889 public long blocklistStartTimeMs; 890 BssidStatus(String bssid, String ssid)891 BssidStatus(String bssid, String ssid) { 892 this.bssid = bssid; 893 this.ssid = ssid; 894 } 895 896 /** 897 * increments the failure count for the reasonCode by 1. 898 * @return the incremented failure count 899 */ incrementFailureCount(int reasonCode)900 public int incrementFailureCount(int reasonCode) { 901 return ++failureCount[reasonCode]; 902 } 903 904 /** 905 * Set this BSSID as blocked for the specified duration. 906 * @param durationMs 907 * @param blockReason 908 * @param rssi 909 */ setAsBlocked(long durationMs, @FailureReason int blockReason, int rssi)910 public void setAsBlocked(long durationMs, @FailureReason int blockReason, int rssi) { 911 isInBlocklist = true; 912 blocklistStartTimeMs = mClock.getWallClockMillis(); 913 blocklistEndTimeMs = blocklistStartTimeMs + durationMs; 914 this.blockReason = blockReason; 915 lastRssi = rssi; 916 mWifiMetrics.incrementBssidBlocklistCount(blockReason); 917 } 918 919 @Override toString()920 public String toString() { 921 StringBuilder sb = new StringBuilder(); 922 sb.append("BSSID=" + bssid); 923 sb.append(", SSID=" + ssid); 924 sb.append(", isInBlocklist=" + isInBlocklist); 925 if (isInBlocklist) { 926 sb.append(", blockReason=" + getFailureReasonString(blockReason)); 927 sb.append(", lastRssi=" + lastRssi); 928 Calendar c = Calendar.getInstance(); 929 c.setTimeInMillis(blocklistStartTimeMs); 930 sb.append(", blocklistStartTime=").append(c.get(Calendar.MONTH)).append("-") 931 .append(c.get(Calendar.DAY_OF_MONTH)).append(" ") 932 .append(c.get(Calendar.HOUR_OF_DAY)).append(":") 933 .append(c.get(Calendar.MINUTE)).append(":") 934 .append(c.get(Calendar.SECOND)).append(".") 935 .append(c.get(Calendar.MILLISECOND)); 936 c.setTimeInMillis(blocklistEndTimeMs); 937 sb.append(", blocklistEndTime=").append(c.get(Calendar.MONTH)).append("-") 938 .append(c.get(Calendar.DAY_OF_MONTH)).append(" ") 939 .append(c.get(Calendar.HOUR_OF_DAY)).append(":") 940 .append(c.get(Calendar.MINUTE)).append(":") 941 .append(c.get(Calendar.SECOND)).append(".") 942 .append(c.get(Calendar.MILLISECOND)); 943 } 944 return sb.toString(); 945 } 946 } 947 948 /** 949 * Enable/disable verbose logging in WifiBlocklistMonitor. 950 */ enableVerboseLogging(boolean verbose)951 public void enableVerboseLogging(boolean verbose) { 952 mVerboseLoggingEnabled = verbose; 953 } 954 955 /** 956 * Modify the internal copy of DisableReasonInfo with custom configurations defined in 957 * an overlay. 958 */ loadCustomConfigsForDisableReasonInfos()959 private void loadCustomConfigsForDisableReasonInfos() { 960 mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION, 961 new DisableReasonInfo( 962 // Note that there is a space at the end of this string. Cannot fix 963 // since this string is persisted. 964 "NETWORK_SELECTION_DISABLED_ASSOCIATION_REJECTION ", 965 mContext.getResources().getInteger(R.integer 966 .config_wifiDisableReasonAssociationRejectionThreshold), 967 5 * 60 * 1000)); 968 969 mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE, 970 new DisableReasonInfo( 971 "NETWORK_SELECTION_DISABLED_AUTHENTICATION_FAILURE", 972 mContext.getResources().getInteger(R.integer 973 .config_wifiDisableReasonAuthenticationFailureThreshold), 974 5 * 60 * 1000)); 975 976 mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_DHCP_FAILURE, 977 new DisableReasonInfo( 978 "NETWORK_SELECTION_DISABLED_DHCP_FAILURE", 979 mContext.getResources().getInteger(R.integer 980 .config_wifiDisableReasonDhcpFailureThreshold), 981 5 * 60 * 1000)); 982 983 mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_NETWORK_NOT_FOUND, 984 new DisableReasonInfo( 985 "NETWORK_SELECTION_DISABLED_NETWORK_NOT_FOUND", 986 mContext.getResources().getInteger(R.integer 987 .config_wifiDisableReasonNetworkNotFoundThreshold), 988 5 * 60 * 1000)); 989 } 990 991 /** Update DisableReasonInfo with carrier configurations defined in an overlay. **/ loadCarrierConfigsForDisableReasonInfos()992 public void loadCarrierConfigsForDisableReasonInfos() { 993 int duration = mContext.getResources().getInteger( 994 R.integer.config_wifiDisableReasonAuthenticationFailureCarrierSpecificDurationMs); 995 DisableReasonInfo disableReasonInfo = new DisableReasonInfo( 996 "NETWORK_SELECTION_DISABLED_AUTHENTICATION_PRIVATE_EAP_ERROR", 997 mContext.getResources().getInteger(R.integer 998 .config_wifiDisableReasonAuthenticationFailureCarrierSpecificThreshold), 999 duration); 1000 mDisableReasonInfo.put( 1001 NetworkSelectionStatus.DISABLED_AUTHENTICATION_PRIVATE_EAP_ERROR, 1002 disableReasonInfo); 1003 } 1004 1005 /** 1006 * Returns true if the disable duration for this WifiConfiguration has passed. Returns false 1007 * if the WifiConfiguration is either not disabled or is permanently disabled. 1008 */ shouldEnableNetwork(WifiConfiguration config)1009 public boolean shouldEnableNetwork(WifiConfiguration config) { 1010 NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus(); 1011 if (networkStatus.isNetworkTemporaryDisabled()) { 1012 long timeDifferenceMs = 1013 mClock.getElapsedSinceBootMillis() - networkStatus.getDisableTime(); 1014 int disableReason = networkStatus.getNetworkSelectionDisableReason(); 1015 long disableTimeoutMs = (long) getNetworkSelectionDisableTimeoutMillis(disableReason); 1016 int exponentialBackoffCount = mWifiScoreCard.lookupNetwork(config.SSID) 1017 .getRecentStats().getCount(WifiScoreCard.CNT_CONSECUTIVE_CONNECTION_FAILURE) 1018 - NUM_CONSECUTIVE_FAILURES_PER_NETWORK_EXP_BACKOFF; 1019 for (int i = 0; i < exponentialBackoffCount; i++) { 1020 disableTimeoutMs *= 2; 1021 if (disableTimeoutMs > WIFI_CONFIG_MAX_DISABLE_DURATION_MILLIS) { 1022 disableTimeoutMs = WIFI_CONFIG_MAX_DISABLE_DURATION_MILLIS; 1023 break; 1024 } 1025 } 1026 if (timeDifferenceMs >= disableTimeoutMs) { 1027 return true; 1028 } 1029 } 1030 return false; 1031 } 1032 1033 /** 1034 * Update a network's status (both internal and public) according to the update reason and 1035 * its current state. This method is expects to directly modify the internal WifiConfiguration 1036 * that is stored by WifiConfigManager. 1037 * 1038 * @param config the internal WifiConfiguration to be updated. 1039 * @param reason reason code for update. 1040 * @return true if the input configuration has been updated, false otherwise. 1041 */ updateNetworkSelectionStatus(WifiConfiguration config, int reason)1042 public boolean updateNetworkSelectionStatus(WifiConfiguration config, int reason) { 1043 if (reason < 0 || reason >= NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX) { 1044 Log.e(TAG, "Invalid Network disable reason " + reason); 1045 return false; 1046 } 1047 NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus(); 1048 if (reason != NetworkSelectionStatus.DISABLED_NONE) { 1049 // Do not disable if in the exception list 1050 if (reason != NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER 1051 && isConfigExemptFromBlocklist(config)) { 1052 return false; 1053 } 1054 1055 // Do not update SSID blocklist with information if this is the only 1056 // SSID be observed. By ignoring it we will cause additional failures 1057 // which will trigger Watchdog. 1058 if (reason == NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION 1059 || reason == NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE 1060 || reason == NetworkSelectionStatus.DISABLED_DHCP_FAILURE) { 1061 if (mWifiLastResortWatchdog.shouldIgnoreSsidUpdate()) { 1062 if (mVerboseLoggingEnabled) { 1063 Log.v(TAG, "Ignore update network selection status " 1064 + "since Watchdog trigger is activated"); 1065 } 1066 return false; 1067 } 1068 } 1069 1070 networkStatus.incrementDisableReasonCounter(reason); 1071 // For network disable reasons, we should only update the status if we cross the 1072 // threshold. 1073 int disableReasonCounter = networkStatus.getDisableReasonCounter(reason); 1074 int disableReasonThreshold = getNetworkSelectionDisableThreshold(reason); 1075 if (disableReasonCounter < disableReasonThreshold) { 1076 if (mVerboseLoggingEnabled) { 1077 Log.v(TAG, "Disable counter for network " + config.getPrintableSsid() 1078 + " for reason " 1079 + NetworkSelectionStatus.getNetworkSelectionDisableReasonString(reason) 1080 + " is " + networkStatus.getDisableReasonCounter(reason) 1081 + " and threshold is " + disableReasonThreshold); 1082 } 1083 return true; 1084 } 1085 } 1086 setNetworkSelectionStatus(config, reason); 1087 return true; 1088 } 1089 1090 /** 1091 * Sets a network's status (both internal and public) according to the update reason and 1092 * its current state. 1093 * 1094 * This updates the network's {@link WifiConfiguration#mNetworkSelectionStatus} field and the 1095 * public {@link WifiConfiguration#status} field if the network is either enabled or 1096 * permanently disabled. 1097 * 1098 * @param config network to be updated. 1099 * @param reason reason code for update. 1100 */ setNetworkSelectionStatus(WifiConfiguration config, int reason)1101 private void setNetworkSelectionStatus(WifiConfiguration config, int reason) { 1102 NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus(); 1103 if (reason == NetworkSelectionStatus.DISABLED_NONE) { 1104 setNetworkSelectionEnabled(config); 1105 } else if (getNetworkSelectionDisableTimeoutMillis(reason) 1106 != DisableReasonInfo.PERMANENT_DISABLE_TIMEOUT) { 1107 setNetworkSelectionTemporarilyDisabled(config, reason); 1108 } else { 1109 setNetworkSelectionPermanentlyDisabled(config, reason); 1110 } 1111 localLog("setNetworkSelectionStatus: configKey=" + config.getProfileKey() 1112 + " networkStatus=" + networkStatus.getNetworkStatusString() + " disableReason=" 1113 + networkStatus.getNetworkSelectionDisableReasonString()); 1114 } 1115 1116 /** 1117 * Helper method to mark a network enabled for network selection. 1118 */ setNetworkSelectionEnabled(WifiConfiguration config)1119 private void setNetworkSelectionEnabled(WifiConfiguration config) { 1120 NetworkSelectionStatus status = config.getNetworkSelectionStatus(); 1121 if (status.getNetworkSelectionStatus() 1122 != NetworkSelectionStatus.NETWORK_SELECTION_ENABLED) { 1123 localLog("setNetworkSelectionEnabled: configKey=" + config.getProfileKey() 1124 + " old networkStatus=" + status.getNetworkStatusString() 1125 + " disableReason=" + status.getNetworkSelectionDisableReasonString()); 1126 } 1127 status.setNetworkSelectionStatus( 1128 NetworkSelectionStatus.NETWORK_SELECTION_ENABLED); 1129 status.setDisableTime( 1130 NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP); 1131 status.setNetworkSelectionDisableReason(NetworkSelectionStatus.DISABLED_NONE); 1132 1133 // Clear out all the disable reason counters. 1134 status.clearDisableReasonCounter(); 1135 if (config.status == WifiConfiguration.Status.DISABLED) { 1136 config.status = WifiConfiguration.Status.ENABLED; 1137 } 1138 } 1139 1140 /** 1141 * Helper method to mark a network temporarily disabled for network selection. 1142 */ setNetworkSelectionTemporarilyDisabled( WifiConfiguration config, int disableReason)1143 private void setNetworkSelectionTemporarilyDisabled( 1144 WifiConfiguration config, int disableReason) { 1145 NetworkSelectionStatus status = config.getNetworkSelectionStatus(); 1146 status.setNetworkSelectionStatus( 1147 NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED); 1148 // Only need a valid time filled in for temporarily disabled networks. 1149 status.setDisableTime(mClock.getElapsedSinceBootMillis()); 1150 status.setNetworkSelectionDisableReason(disableReason); 1151 handleWifiConfigurationDisabled(config.SSID); 1152 mWifiMetrics.incrementWificonfigurationBlocklistCount(disableReason); 1153 } 1154 1155 /** 1156 * Helper method to mark a network permanently disabled for network selection. 1157 */ setNetworkSelectionPermanentlyDisabled( WifiConfiguration config, int disableReason)1158 private void setNetworkSelectionPermanentlyDisabled( 1159 WifiConfiguration config, int disableReason) { 1160 NetworkSelectionStatus status = config.getNetworkSelectionStatus(); 1161 status.setNetworkSelectionStatus( 1162 NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED); 1163 status.setDisableTime( 1164 NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP); 1165 status.setNetworkSelectionDisableReason(disableReason); 1166 handleWifiConfigurationDisabled(config.SSID); 1167 config.status = WifiConfiguration.Status.DISABLED; 1168 mWifiMetrics.incrementWificonfigurationBlocklistCount(disableReason); 1169 } 1170 1171 /** 1172 * Network Selection disable reason thresholds. These numbers are used to debounce network 1173 * failures before we disable them. 1174 * 1175 * @param reason int reason code 1176 * @return the disable threshold, or -1 if not found. 1177 */ 1178 @VisibleForTesting getNetworkSelectionDisableThreshold(@etworkSelectionDisableReason int reason)1179 public int getNetworkSelectionDisableThreshold(@NetworkSelectionDisableReason int reason) { 1180 DisableReasonInfo info = mDisableReasonInfo.get(reason); 1181 if (info == null) { 1182 Log.e(TAG, "Unrecognized network disable reason code for disable threshold: " + reason); 1183 return -1; 1184 } else { 1185 return info.mDisableThreshold; 1186 } 1187 } 1188 1189 /** 1190 * Network Selection disable timeout for each kind of error. After the timeout in milliseconds, 1191 * enable the network again. 1192 */ 1193 @VisibleForTesting getNetworkSelectionDisableTimeoutMillis(@etworkSelectionDisableReason int reason)1194 public int getNetworkSelectionDisableTimeoutMillis(@NetworkSelectionDisableReason int reason) { 1195 DisableReasonInfo info = mDisableReasonInfo.get(reason); 1196 if (info == null) { 1197 Log.e(TAG, "Unrecognized network disable reason code for disable timeout: " + reason); 1198 return -1; 1199 } else { 1200 return info.mDisableTimeoutMillis; 1201 } 1202 } 1203 1204 /** 1205 * Sets the allowlist ssids for the given ssid 1206 */ setAllowlistSsids(@onNull String ssid, @NonNull List<String> ssidAllowlist)1207 public void setAllowlistSsids(@NonNull String ssid, @NonNull List<String> ssidAllowlist) { 1208 mSsidAllowlistMap.put(ssid, ssidAllowlist); 1209 } 1210 } 1211