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