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 android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.content.Context; 22 import android.net.wifi.WifiManager; 23 import android.util.ArrayMap; 24 import android.util.LocalLog; 25 import android.util.Log; 26 27 import com.android.internal.annotations.VisibleForTesting; 28 import com.android.wifi.resources.R; 29 30 import java.io.FileDescriptor; 31 import java.io.PrintWriter; 32 import java.lang.annotation.Retention; 33 import java.lang.annotation.RetentionPolicy; 34 import java.util.ArrayList; 35 import java.util.Calendar; 36 import java.util.LinkedList; 37 import java.util.Map; 38 import java.util.Set; 39 import java.util.concurrent.TimeUnit; 40 import java.util.stream.Collectors; 41 import java.util.stream.Stream; 42 43 /** 44 * This class manages the addition and removal of BSSIDs to the BSSID blocklist, which is used 45 * for firmware roaming and network selection. 46 */ 47 public class BssidBlocklistMonitor { 48 // A special type association rejection 49 public static final int REASON_AP_UNABLE_TO_HANDLE_NEW_STA = 0; 50 // No internet 51 public static final int REASON_NETWORK_VALIDATION_FAILURE = 1; 52 // Wrong password error 53 public static final int REASON_WRONG_PASSWORD = 2; 54 // Incorrect EAP credentials 55 public static final int REASON_EAP_FAILURE = 3; 56 // Other association rejection failures 57 public static final int REASON_ASSOCIATION_REJECTION = 4; 58 // Associated timeout failures, when the RSSI is good 59 public static final int REASON_ASSOCIATION_TIMEOUT = 5; 60 // Other authentication failures 61 public static final int REASON_AUTHENTICATION_FAILURE = 6; 62 // DHCP failures 63 public static final int REASON_DHCP_FAILURE = 7; 64 // Abnormal disconnect error 65 public static final int REASON_ABNORMAL_DISCONNECT = 8; 66 // Constant being used to keep track of how many failure reasons there are. 67 public static final int NUMBER_REASON_CODES = 9; 68 69 @IntDef(prefix = { "REASON_" }, value = { 70 REASON_AP_UNABLE_TO_HANDLE_NEW_STA, 71 REASON_NETWORK_VALIDATION_FAILURE, 72 REASON_WRONG_PASSWORD, 73 REASON_EAP_FAILURE, 74 REASON_ASSOCIATION_REJECTION, 75 REASON_ASSOCIATION_TIMEOUT, 76 REASON_AUTHENTICATION_FAILURE, 77 REASON_DHCP_FAILURE 78 }) 79 @Retention(RetentionPolicy.SOURCE) 80 public @interface FailureReason {} 81 82 // To be filled with values from the overlay. 83 private static final int[] FAILURE_COUNT_DISABLE_THRESHOLD = new int[NUMBER_REASON_CODES]; 84 private boolean mFailureCountDisableThresholdArrayInitialized = false; 85 private static final String[] FAILURE_REASON_STRINGS = { 86 "REASON_AP_UNABLE_TO_HANDLE_NEW_STA", 87 "REASON_NETWORK_VALIDATION_FAILURE", 88 "REASON_WRONG_PASSWORD", 89 "REASON_EAP_FAILURE", 90 "REASON_ASSOCIATION_REJECTION", 91 "REASON_ASSOCIATION_TIMEOUT", 92 "REASON_AUTHENTICATION_FAILURE", 93 "REASON_DHCP_FAILURE", 94 "REASON_ABNORMAL_DISCONNECT" 95 }; 96 private static final String FAILURE_BSSID_BLOCKED_BY_FRAMEWORK_REASON_STRING = 97 "BlockedByFramework"; 98 private static final long ABNORMAL_DISCONNECT_RESET_TIME_MS = TimeUnit.HOURS.toMillis(3); 99 private static final String TAG = "BssidBlocklistMonitor"; 100 101 private final Context mContext; 102 private final WifiLastResortWatchdog mWifiLastResortWatchdog; 103 private final WifiConnectivityHelper mConnectivityHelper; 104 private final Clock mClock; 105 private final LocalLog mLocalLog; 106 private final Calendar mCalendar; 107 private final WifiScoreCard mWifiScoreCard; 108 109 // Map of bssid to BssidStatus 110 private Map<String, BssidStatus> mBssidStatusMap = new ArrayMap<>(); 111 112 // Keeps history of 30 blocked BSSIDs that were most recently removed. 113 private BssidStatusHistoryLogger mBssidStatusHistoryLogger = new BssidStatusHistoryLogger(30); 114 115 /** 116 * Create a new instance of BssidBlocklistMonitor 117 */ BssidBlocklistMonitor(Context context, WifiConnectivityHelper connectivityHelper, WifiLastResortWatchdog wifiLastResortWatchdog, Clock clock, LocalLog localLog, WifiScoreCard wifiScoreCard)118 BssidBlocklistMonitor(Context context, WifiConnectivityHelper connectivityHelper, 119 WifiLastResortWatchdog wifiLastResortWatchdog, Clock clock, LocalLog localLog, 120 WifiScoreCard wifiScoreCard) { 121 mContext = context; 122 mConnectivityHelper = connectivityHelper; 123 mWifiLastResortWatchdog = wifiLastResortWatchdog; 124 mClock = clock; 125 mLocalLog = localLog; 126 mCalendar = Calendar.getInstance(); 127 mWifiScoreCard = wifiScoreCard; 128 } 129 130 // A helper to log debugging information in the local log buffer, which can 131 // be retrieved in bugreport. localLog(String log)132 private void localLog(String log) { 133 mLocalLog.log(log); 134 } 135 136 /** 137 * calculates the blocklist duration based on the current failure streak with exponential 138 * backoff. 139 * @param failureStreak should be greater or equal to 0. 140 * @return duration to block the BSSID in milliseconds 141 */ getBlocklistDurationWithExponentialBackoff(int failureStreak, int baseBlocklistDurationMs)142 private long getBlocklistDurationWithExponentialBackoff(int failureStreak, 143 int baseBlocklistDurationMs) { 144 failureStreak = Math.min(failureStreak, mContext.getResources().getInteger( 145 R.integer.config_wifiBssidBlocklistMonitorFailureStreakCap)); 146 if (failureStreak < 1) { 147 return baseBlocklistDurationMs; 148 } 149 return (long) (Math.pow(2.0, (double) failureStreak) * baseBlocklistDurationMs); 150 } 151 152 /** 153 * Dump the local log buffer and other internal state of BssidBlocklistMonitor. 154 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)155 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 156 pw.println("Dump of BssidBlocklistMonitor"); 157 pw.println("BssidBlocklistMonitor - Bssid blocklist begin ----"); 158 mBssidStatusMap.values().stream().forEach(entry -> pw.println(entry)); 159 pw.println("BssidBlocklistMonitor - Bssid blocklist end ----"); 160 mBssidStatusHistoryLogger.dump(pw); 161 } 162 addToBlocklist(@onNull BssidStatus entry, long durationMs, String reasonString)163 private void addToBlocklist(@NonNull BssidStatus entry, long durationMs, String reasonString) { 164 entry.addToBlocklist(durationMs, reasonString); 165 localLog(TAG + " addToBlocklist: bssid=" + entry.bssid + ", ssid=" + entry.ssid 166 + ", durationMs=" + durationMs + ", reason=" + reasonString); 167 } 168 169 /** 170 * increments the number of failures for the given bssid and returns the number of failures so 171 * far. 172 * @return the BssidStatus for the BSSID 173 */ incrementFailureCountForBssid( @onNull String bssid, @NonNull String ssid, int reasonCode)174 private @NonNull BssidStatus incrementFailureCountForBssid( 175 @NonNull String bssid, @NonNull String ssid, int reasonCode) { 176 BssidStatus status = getOrCreateBssidStatus(bssid, ssid); 177 status.incrementFailureCount(reasonCode); 178 return status; 179 } 180 181 /** 182 * Get the BssidStatus representing the BSSID or create a new one if it doesn't exist. 183 */ getOrCreateBssidStatus(@onNull String bssid, @NonNull String ssid)184 private @NonNull BssidStatus getOrCreateBssidStatus(@NonNull String bssid, 185 @NonNull String ssid) { 186 BssidStatus status = mBssidStatusMap.get(bssid); 187 if (status == null || !ssid.equals(status.ssid)) { 188 if (status != null) { 189 localLog("getOrCreateBssidStatus: BSSID=" + bssid + ", SSID changed from " 190 + status.ssid + " to " + ssid); 191 } 192 status = new BssidStatus(bssid, ssid); 193 mBssidStatusMap.put(bssid, status); 194 } 195 return status; 196 } 197 isValidNetworkAndFailureReason(String bssid, String ssid, @FailureReason int reasonCode)198 private boolean isValidNetworkAndFailureReason(String bssid, String ssid, 199 @FailureReason int reasonCode) { 200 if (bssid == null || ssid == null || WifiManager.UNKNOWN_SSID.equals(ssid) 201 || bssid.equals(ClientModeImpl.SUPPLICANT_BSSID_ANY) 202 || reasonCode < 0 || reasonCode >= NUMBER_REASON_CODES) { 203 Log.e(TAG, "Invalid input: BSSID=" + bssid + ", SSID=" + ssid 204 + ", reasonCode=" + reasonCode); 205 return false; 206 } 207 return true; 208 } 209 shouldWaitForWatchdogToTriggerFirst(String bssid, @FailureReason int reasonCode)210 private boolean shouldWaitForWatchdogToTriggerFirst(String bssid, 211 @FailureReason int reasonCode) { 212 boolean isWatchdogRelatedFailure = reasonCode == REASON_ASSOCIATION_REJECTION 213 || reasonCode == REASON_AUTHENTICATION_FAILURE 214 || reasonCode == REASON_DHCP_FAILURE; 215 return isWatchdogRelatedFailure && mWifiLastResortWatchdog.shouldIgnoreBssidUpdate(bssid); 216 } 217 218 /** 219 * Block any attempts to auto-connect to the BSSID for the specified duration. 220 */ blockBssidForDurationMs(@onNull String bssid, @NonNull String ssid, long durationMs)221 public void blockBssidForDurationMs(@NonNull String bssid, @NonNull String ssid, 222 long durationMs) { 223 if (bssid == null || ssid == null || WifiManager.UNKNOWN_SSID.equals(ssid) 224 || bssid.equals(ClientModeImpl.SUPPLICANT_BSSID_ANY) || durationMs <= 0) { 225 Log.e(TAG, "Invalid input: BSSID=" + bssid + ", SSID=" + ssid 226 + ", durationMs=" + durationMs); 227 return; 228 } 229 BssidStatus status = getOrCreateBssidStatus(bssid, ssid); 230 if (status.isInBlocklist 231 && status.blocklistEndTimeMs - mClock.getWallClockMillis() > durationMs) { 232 // Return because this BSSID is already being blocked for a longer time. 233 return; 234 } 235 addToBlocklist(status, durationMs, FAILURE_BSSID_BLOCKED_BY_FRAMEWORK_REASON_STRING); 236 } 237 getFailureReasonString(@ailureReason int reasonCode)238 private String getFailureReasonString(@FailureReason int reasonCode) { 239 if (reasonCode >= FAILURE_REASON_STRINGS.length) { 240 return "REASON_UNKNOWN"; 241 } 242 return FAILURE_REASON_STRINGS[reasonCode]; 243 } 244 getFailureThresholdForReason(@ailureReason int reasonCode)245 private int getFailureThresholdForReason(@FailureReason int reasonCode) { 246 if (mFailureCountDisableThresholdArrayInitialized) { 247 return FAILURE_COUNT_DISABLE_THRESHOLD[reasonCode]; 248 } 249 FAILURE_COUNT_DISABLE_THRESHOLD[REASON_AP_UNABLE_TO_HANDLE_NEW_STA] = 250 mContext.getResources().getInteger( 251 R.integer.config_wifiBssidBlocklistMonitorApUnableToHandleNewStaThreshold); 252 FAILURE_COUNT_DISABLE_THRESHOLD[REASON_NETWORK_VALIDATION_FAILURE] = 253 mContext.getResources().getInteger(R.integer 254 .config_wifiBssidBlocklistMonitorNetworkValidationFailureThreshold); 255 FAILURE_COUNT_DISABLE_THRESHOLD[REASON_WRONG_PASSWORD] = 256 mContext.getResources().getInteger( 257 R.integer.config_wifiBssidBlocklistMonitorWrongPasswordThreshold); 258 FAILURE_COUNT_DISABLE_THRESHOLD[REASON_EAP_FAILURE] = 259 mContext.getResources().getInteger( 260 R.integer.config_wifiBssidBlocklistMonitorEapFailureThreshold); 261 FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ASSOCIATION_REJECTION] = 262 mContext.getResources().getInteger( 263 R.integer.config_wifiBssidBlocklistMonitorAssociationRejectionThreshold); 264 FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ASSOCIATION_TIMEOUT] = 265 mContext.getResources().getInteger( 266 R.integer.config_wifiBssidBlocklistMonitorAssociationTimeoutThreshold); 267 FAILURE_COUNT_DISABLE_THRESHOLD[REASON_AUTHENTICATION_FAILURE] = 268 mContext.getResources().getInteger( 269 R.integer.config_wifiBssidBlocklistMonitorAuthenticationFailureThreshold); 270 FAILURE_COUNT_DISABLE_THRESHOLD[REASON_DHCP_FAILURE] = 271 mContext.getResources().getInteger( 272 R.integer.config_wifiBssidBlocklistMonitorDhcpFailureThreshold); 273 FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ABNORMAL_DISCONNECT] = 274 mContext.getResources().getInteger( 275 R.integer.config_wifiBssidBlocklistMonitorAbnormalDisconnectThreshold); 276 mFailureCountDisableThresholdArrayInitialized = true; 277 return FAILURE_COUNT_DISABLE_THRESHOLD[reasonCode]; 278 } 279 handleBssidConnectionFailureInternal(String bssid, String ssid, @FailureReason int reasonCode, boolean isLowRssi)280 private boolean handleBssidConnectionFailureInternal(String bssid, String ssid, 281 @FailureReason int reasonCode, boolean isLowRssi) { 282 BssidStatus entry = incrementFailureCountForBssid(bssid, ssid, reasonCode); 283 int failureThreshold = getFailureThresholdForReason(reasonCode); 284 int currentStreak = mWifiScoreCard.getBssidBlocklistStreak(ssid, bssid, reasonCode); 285 if (currentStreak > 0 || entry.failureCount[reasonCode] >= failureThreshold) { 286 // To rule out potential device side issues, don't add to blocklist if 287 // WifiLastResortWatchdog is still not triggered 288 if (shouldWaitForWatchdogToTriggerFirst(bssid, reasonCode)) { 289 return false; 290 } 291 int baseBlockDurationMs = mContext.getResources().getInteger( 292 R.integer.config_wifiBssidBlocklistMonitorBaseBlockDurationMs); 293 if ((reasonCode == REASON_ASSOCIATION_TIMEOUT 294 || reasonCode == REASON_ABNORMAL_DISCONNECT) && isLowRssi) { 295 baseBlockDurationMs = mContext.getResources().getInteger( 296 R.integer.config_wifiBssidBlocklistMonitorBaseLowRssiBlockDurationMs); 297 } 298 addToBlocklist(entry, 299 getBlocklistDurationWithExponentialBackoff(currentStreak, baseBlockDurationMs), 300 getFailureReasonString(reasonCode)); 301 mWifiScoreCard.incrementBssidBlocklistStreak(ssid, bssid, reasonCode); 302 return true; 303 } 304 return false; 305 } 306 307 /** 308 * Note a failure event on a bssid and perform appropriate actions. 309 * @return True if the blocklist has been modified. 310 */ handleBssidConnectionFailure(String bssid, String ssid, @FailureReason int reasonCode, boolean isLowRssi)311 public boolean handleBssidConnectionFailure(String bssid, String ssid, 312 @FailureReason int reasonCode, boolean isLowRssi) { 313 if (!isValidNetworkAndFailureReason(bssid, ssid, reasonCode)) { 314 return false; 315 } 316 if (reasonCode == REASON_ABNORMAL_DISCONNECT) { 317 long connectionTime = mWifiScoreCard.getBssidConnectionTimestampMs(ssid, bssid); 318 // only count disconnects that happen shortly after a connection. 319 if (mClock.getWallClockMillis() - connectionTime 320 > mContext.getResources().getInteger( 321 R.integer.config_wifiBssidBlocklistAbnormalDisconnectTimeWindowMs)) { 322 return false; 323 } 324 } 325 return handleBssidConnectionFailureInternal(bssid, ssid, reasonCode, isLowRssi); 326 } 327 328 /** 329 * Note a connection success event on a bssid and clear appropriate failure counters. 330 */ handleBssidConnectionSuccess(@onNull String bssid, @NonNull String ssid)331 public void handleBssidConnectionSuccess(@NonNull String bssid, @NonNull String ssid) { 332 /** 333 * First reset the blocklist streak. 334 * This needs to be done even if a BssidStatus is not found, since the BssidStatus may 335 * have been removed due to blocklist timeout. 336 */ 337 mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_AP_UNABLE_TO_HANDLE_NEW_STA); 338 mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_WRONG_PASSWORD); 339 mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_EAP_FAILURE); 340 mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ASSOCIATION_REJECTION); 341 mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ASSOCIATION_TIMEOUT); 342 mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_AUTHENTICATION_FAILURE); 343 344 long connectionTime = mClock.getWallClockMillis(); 345 long prevConnectionTime = mWifiScoreCard.setBssidConnectionTimestampMs( 346 ssid, bssid, connectionTime); 347 if (connectionTime - prevConnectionTime > ABNORMAL_DISCONNECT_RESET_TIME_MS) { 348 mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ABNORMAL_DISCONNECT); 349 } 350 351 BssidStatus status = mBssidStatusMap.get(bssid); 352 if (status == null) { 353 return; 354 } 355 // Clear the L2 failure counters 356 status.failureCount[REASON_AP_UNABLE_TO_HANDLE_NEW_STA] = 0; 357 status.failureCount[REASON_WRONG_PASSWORD] = 0; 358 status.failureCount[REASON_EAP_FAILURE] = 0; 359 status.failureCount[REASON_ASSOCIATION_REJECTION] = 0; 360 status.failureCount[REASON_ASSOCIATION_TIMEOUT] = 0; 361 status.failureCount[REASON_AUTHENTICATION_FAILURE] = 0; 362 if (connectionTime - prevConnectionTime > ABNORMAL_DISCONNECT_RESET_TIME_MS) { 363 status.failureCount[REASON_ABNORMAL_DISCONNECT] = 0; 364 } 365 } 366 367 /** 368 * Note a successful network validation on a BSSID and clear appropriate failure counters. 369 * And then remove the BSSID from blocklist. 370 */ handleNetworkValidationSuccess(@onNull String bssid, @NonNull String ssid)371 public void handleNetworkValidationSuccess(@NonNull String bssid, @NonNull String ssid) { 372 mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_NETWORK_VALIDATION_FAILURE); 373 BssidStatus status = mBssidStatusMap.get(bssid); 374 if (status == null) { 375 return; 376 } 377 status.failureCount[REASON_NETWORK_VALIDATION_FAILURE] = 0; 378 /** 379 * Network validation may take more than 1 tries to succeed. 380 * remove the BSSID from blocklist to make sure we are not accidentally blocking good 381 * BSSIDs. 382 **/ 383 status.removeFromBlocklist(); 384 } 385 386 /** 387 * Note a successful DHCP provisioning and clear appropriate faliure counters. 388 */ handleDhcpProvisioningSuccess(@onNull String bssid, @NonNull String ssid)389 public void handleDhcpProvisioningSuccess(@NonNull String bssid, @NonNull String ssid) { 390 mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_DHCP_FAILURE); 391 BssidStatus status = mBssidStatusMap.get(bssid); 392 if (status == null) { 393 return; 394 } 395 status.failureCount[REASON_DHCP_FAILURE] = 0; 396 } 397 398 /** 399 * Note the removal of a network from the Wifi stack's internal database and reset 400 * appropriate failure counters. 401 * @param ssid 402 */ handleNetworkRemoved(@onNull String ssid)403 public void handleNetworkRemoved(@NonNull String ssid) { 404 clearBssidBlocklistForSsid(ssid); 405 mWifiScoreCard.resetBssidBlocklistStreakForSsid(ssid); 406 } 407 408 /** 409 * Clears the blocklist for BSSIDs associated with the input SSID only. 410 * @param ssid 411 */ clearBssidBlocklistForSsid(@onNull String ssid)412 public void clearBssidBlocklistForSsid(@NonNull String ssid) { 413 int prevSize = mBssidStatusMap.size(); 414 mBssidStatusMap.entrySet().removeIf(e -> { 415 BssidStatus status = e.getValue(); 416 if (status.ssid == null) { 417 return false; 418 } 419 if (status.ssid.equals(ssid)) { 420 mBssidStatusHistoryLogger.add(status, "clearBssidBlocklistForSsid"); 421 return true; 422 } 423 return false; 424 }); 425 int diff = prevSize - mBssidStatusMap.size(); 426 if (diff > 0) { 427 localLog(TAG + " clearBssidBlocklistForSsid: SSID=" + ssid 428 + ", num BSSIDs cleared=" + diff); 429 } 430 } 431 432 /** 433 * Clears the BSSID blocklist and failure counters. 434 */ clearBssidBlocklist()435 public void clearBssidBlocklist() { 436 if (mBssidStatusMap.size() > 0) { 437 int prevSize = mBssidStatusMap.size(); 438 for (BssidStatus status : mBssidStatusMap.values()) { 439 mBssidStatusHistoryLogger.add(status, "clearBssidBlocklist"); 440 } 441 mBssidStatusMap.clear(); 442 localLog(TAG + " clearBssidBlocklist: num BSSIDs cleared=" 443 + (prevSize - mBssidStatusMap.size())); 444 } 445 } 446 447 /** 448 * @param ssid 449 * @return the number of BSSIDs currently in the blocklist for the |ssid|. 450 */ getNumBlockedBssidsForSsid(@onNull String ssid)451 public int getNumBlockedBssidsForSsid(@NonNull String ssid) { 452 return (int) updateAndGetBssidBlocklistInternal() 453 .filter(entry -> ssid.equals(entry.ssid)).count(); 454 } 455 456 /** 457 * Gets the BSSIDs that are currently in the blocklist. 458 * @return Set of BSSIDs currently in the blocklist 459 */ updateAndGetBssidBlocklist()460 public Set<String> updateAndGetBssidBlocklist() { 461 return updateAndGetBssidBlocklistInternal() 462 .map(entry -> entry.bssid) 463 .collect(Collectors.toSet()); 464 } 465 466 /** 467 * Removes expired BssidStatus entries and then return remaining entries in the blocklist. 468 * @return Stream of BssidStatus for BSSIDs that are in the blocklist. 469 */ updateAndGetBssidBlocklistInternal()470 private Stream<BssidStatus> updateAndGetBssidBlocklistInternal() { 471 Stream.Builder<BssidStatus> builder = Stream.builder(); 472 long curTime = mClock.getWallClockMillis(); 473 mBssidStatusMap.entrySet().removeIf(e -> { 474 BssidStatus status = e.getValue(); 475 if (status.isInBlocklist) { 476 if (status.blocklistEndTimeMs < curTime) { 477 mBssidStatusHistoryLogger.add(status, "updateAndGetBssidBlocklistInternal"); 478 return true; 479 } 480 builder.accept(status); 481 } 482 return false; 483 }); 484 return builder.build(); 485 } 486 487 /** 488 * Sends the BSSIDs belonging to the input SSID down to the firmware to prevent auto-roaming 489 * to those BSSIDs. 490 * @param ssid 491 */ updateFirmwareRoamingConfiguration(@onNull String ssid)492 public void updateFirmwareRoamingConfiguration(@NonNull String ssid) { 493 if (!mConnectivityHelper.isFirmwareRoamingSupported()) { 494 return; 495 } 496 ArrayList<String> bssidBlocklist = updateAndGetBssidBlocklistInternal() 497 .filter(entry -> ssid.equals(entry.ssid)) 498 .sorted((o1, o2) -> (int) (o2.blocklistEndTimeMs - o1.blocklistEndTimeMs)) 499 .map(entry -> entry.bssid) 500 .collect(Collectors.toCollection(ArrayList::new)); 501 int fwMaxBlocklistSize = mConnectivityHelper.getMaxNumBlacklistBssid(); 502 if (fwMaxBlocklistSize <= 0) { 503 Log.e(TAG, "Invalid max BSSID blocklist size: " + fwMaxBlocklistSize); 504 return; 505 } 506 // Having the blocklist size exceeding firmware max limit is unlikely because we have 507 // already flitered based on SSID. But just in case this happens, we are prioritizing 508 // sending down BSSIDs blocked for the longest time. 509 if (bssidBlocklist.size() > fwMaxBlocklistSize) { 510 bssidBlocklist = new ArrayList<String>(bssidBlocklist.subList(0, 511 fwMaxBlocklistSize)); 512 } 513 // plumb down to HAL 514 if (!mConnectivityHelper.setFirmwareRoamingConfiguration(bssidBlocklist, 515 new ArrayList<String>())) { // TODO(b/36488259): SSID whitelist management. 516 } 517 } 518 519 @VisibleForTesting getBssidStatusHistoryLoggerSize()520 public int getBssidStatusHistoryLoggerSize() { 521 return mBssidStatusHistoryLogger.size(); 522 } 523 524 private class BssidStatusHistoryLogger { 525 private LinkedList<String> mLogHistory = new LinkedList<>(); 526 private int mBufferSize; 527 BssidStatusHistoryLogger(int bufferSize)528 BssidStatusHistoryLogger(int bufferSize) { 529 mBufferSize = bufferSize; 530 } 531 add(BssidStatus bssidStatus, String trigger)532 public void add(BssidStatus bssidStatus, String trigger) { 533 // only log history for Bssids that had been blocked. 534 if (bssidStatus == null || !bssidStatus.isInBlocklist) { 535 return; 536 } 537 StringBuilder sb = new StringBuilder(); 538 mCalendar.setTimeInMillis(mClock.getWallClockMillis()); 539 sb.append(", logTimeMs=" 540 + String.format("%tm-%td %tH:%tM:%tS.%tL", mCalendar, mCalendar, 541 mCalendar, mCalendar, mCalendar, mCalendar)); 542 sb.append(", trigger=" + trigger); 543 mLogHistory.add(bssidStatus.toString() + sb.toString()); 544 if (mLogHistory.size() > mBufferSize) { 545 mLogHistory.removeFirst(); 546 } 547 } 548 549 @VisibleForTesting size()550 public int size() { 551 return mLogHistory.size(); 552 } 553 dump(PrintWriter pw)554 public void dump(PrintWriter pw) { 555 pw.println("BssidBlocklistMonitor - Bssid blocklist history begin ----"); 556 for (String line : mLogHistory) { 557 pw.println(line); 558 } 559 pw.println("BssidBlocklistMonitor - Bssid blocklist history end ----"); 560 } 561 } 562 563 /** 564 * Helper class that counts the number of failures per BSSID. 565 */ 566 private class BssidStatus { 567 public final String bssid; 568 public final String ssid; 569 public final int[] failureCount = new int[NUMBER_REASON_CODES]; 570 private String mBlockReason = ""; // reason of blocking for logging only 571 572 // The following are used to flag how long this BSSID stays in the blocklist. 573 public boolean isInBlocklist; 574 public long blocklistEndTimeMs; 575 public long blocklistStartTimeMs; 576 BssidStatus(String bssid, String ssid)577 BssidStatus(String bssid, String ssid) { 578 this.bssid = bssid; 579 this.ssid = ssid; 580 } 581 582 /** 583 * increments the failure count for the reasonCode by 1. 584 * @return the incremented failure count 585 */ incrementFailureCount(int reasonCode)586 public int incrementFailureCount(int reasonCode) { 587 return ++failureCount[reasonCode]; 588 } 589 590 /** 591 * Add this BSSID to blocklist for the specified duration. 592 * @param durationMs 593 */ addToBlocklist(long durationMs, String blockReason)594 public void addToBlocklist(long durationMs, String blockReason) { 595 isInBlocklist = true; 596 blocklistStartTimeMs = mClock.getWallClockMillis(); 597 blocklistEndTimeMs = blocklistStartTimeMs + durationMs; 598 mBlockReason = blockReason; 599 } 600 601 /** 602 * Remove this BSSID from the blocklist. 603 */ removeFromBlocklist()604 public void removeFromBlocklist() { 605 mBssidStatusHistoryLogger.add(this, "removeFromBlocklist"); 606 isInBlocklist = false; 607 blocklistStartTimeMs = 0; 608 blocklistEndTimeMs = 0; 609 mBlockReason = ""; 610 localLog(TAG + " removeFromBlocklist BSSID=" + bssid); 611 } 612 613 @Override toString()614 public String toString() { 615 StringBuilder sb = new StringBuilder(); 616 sb.append("BSSID=" + bssid); 617 sb.append(", SSID=" + ssid); 618 sb.append(", isInBlocklist=" + isInBlocklist); 619 if (isInBlocklist) { 620 sb.append(", blockReason=" + mBlockReason); 621 mCalendar.setTimeInMillis(blocklistStartTimeMs); 622 sb.append(", blocklistStartTimeMs=" 623 + String.format("%tm-%td %tH:%tM:%tS.%tL", mCalendar, mCalendar, 624 mCalendar, mCalendar, mCalendar, mCalendar)); 625 mCalendar.setTimeInMillis(blocklistEndTimeMs); 626 sb.append(", blocklistEndTimeMs=" 627 + String.format("%tm-%td %tH:%tM:%tS.%tL", mCalendar, mCalendar, 628 mCalendar, mCalendar, mCalendar, mCalendar)); 629 } 630 return sb.toString(); 631 } 632 } 633 } 634