WifiLastResortWatchdog.java revision 8fe3e3497daf08b71ffc8c33cb7b139df6667448
1/* 2 * Copyright (C) 2016 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 17package com.android.server.wifi; 18 19import android.net.wifi.ScanResult; 20import android.net.wifi.WifiConfiguration; 21import android.util.Log; 22import android.util.Pair; 23 24import java.util.HashMap; 25import java.util.Iterator; 26import java.util.List; 27import java.util.Map; 28 29/** 30 * This Class is a Work-In-Progress, intended behavior is as follows: 31 * Essentially this class automates a user toggling 'Airplane Mode' when WiFi "won't work". 32 * IF each available saved network has failed connecting more times than the FAILURE_THRESHOLD 33 * THEN Watchdog will restart Supplicant, wifi driver and return WifiStateMachine to InitialState. 34 */ 35public class WifiLastResortWatchdog { 36 private static final String TAG = "WifiLastResortWatchdog"; 37 private static final boolean VDBG = false; 38 private static final boolean DBG = true; 39 /** 40 * Association Failure code 41 */ 42 public static final int FAILURE_CODE_ASSOCIATION = 1; 43 /** 44 * Authentication Failure code 45 */ 46 public static final int FAILURE_CODE_AUTHENTICATION = 2; 47 /** 48 * Dhcp Failure code 49 */ 50 public static final int FAILURE_CODE_DHCP = 3; 51 /** 52 * Maximum number of scan results received since we last saw a BSSID. 53 * If it is not seen before this limit is reached, the network is culled 54 */ 55 public static final int MAX_BSSID_AGE = 10; 56 /** 57 * BSSID used to increment failure counts against ALL bssids associated with a particular SSID 58 */ 59 public static final String BSSID_ANY = "any"; 60 /** 61 * Failure count that each available networks must meet to possibly trigger the Watchdog 62 */ 63 public static final int FAILURE_THRESHOLD = 7; 64 /** 65 * Cached WifiConfigurations of available networks seen within MAX_BSSID_AGE scan results 66 * Key:BSSID, Value:Counters of failure types 67 */ 68 private Map<String, AvailableNetworkFailureCount> mRecentAvailableNetworks = new HashMap<>(); 69 /** 70 * Map of SSID to <FailureCount, AP count>, used to count failures & number of access points 71 * belonging to an SSID. 72 */ 73 private Map<String, Pair<AvailableNetworkFailureCount, Integer>> mSsidFailureCount = 74 new HashMap<>(); 75 76 /** 77 * Refreshes recentAvailableNetworks with the latest available networks 78 * Adds new networks, removes old ones that have timed out. Should be called after Wifi 79 * framework decides what networks it is potentially connecting to. 80 * @param availableNetworks ScanDetail & Config list of potential connection 81 * candidates 82 */ 83 public void updateAvailableNetworks( 84 List<Pair<ScanDetail, WifiConfiguration>> availableNetworks) { 85 if (VDBG) Log.v(TAG, "updateAvailableNetworks: size = " + availableNetworks.size()); 86 // Add new networks to mRecentAvailableNetworks 87 if (availableNetworks != null) { 88 for (Pair<ScanDetail, WifiConfiguration> pair : availableNetworks) { 89 final ScanDetail scanDetail = pair.first; 90 final WifiConfiguration config = pair.second; 91 ScanResult scanResult = scanDetail.getScanResult(); 92 if (scanResult == null) continue; 93 String bssid = scanResult.BSSID; 94 String ssid = "\"" + scanDetail.getSSID() + "\""; 95 if (VDBG) Log.v(TAG, " " + bssid + ": " + scanDetail.getSSID()); 96 // Cache the scanResult & WifiConfig 97 AvailableNetworkFailureCount availableNetworkFailureCount = 98 mRecentAvailableNetworks.get(bssid); 99 if (availableNetworkFailureCount == null) { 100 // New network is available 101 availableNetworkFailureCount = new AvailableNetworkFailureCount(config); 102 availableNetworkFailureCount.ssid = ssid; 103 104 // Count AP for this SSID 105 Pair<AvailableNetworkFailureCount, Integer> ssidFailsAndApCount = 106 mSsidFailureCount.get(ssid); 107 if (ssidFailsAndApCount == null) { 108 // This is a new SSID, create new FailureCount for it and set AP count to 1 109 ssidFailsAndApCount = Pair.create(new AvailableNetworkFailureCount(config), 110 1); 111 } else { 112 final Integer numberOfAps = ssidFailsAndApCount.second; 113 // This is not a new SSID, increment the AP count for it 114 ssidFailsAndApCount = Pair.create(ssidFailsAndApCount.first, 115 numberOfAps + 1); 116 } 117 mSsidFailureCount.put(ssid, ssidFailsAndApCount); 118 } 119 // refresh config 120 availableNetworkFailureCount.config = config; 121 // If we saw a network, set its Age to -1 here, aging iteration will set it to 0 122 availableNetworkFailureCount.age = -1; 123 mRecentAvailableNetworks.put(bssid, availableNetworkFailureCount); 124 } 125 } 126 127 // Iterate through available networks updating timeout counts & removing networks. 128 Iterator<Map.Entry<String, AvailableNetworkFailureCount>> it = 129 mRecentAvailableNetworks.entrySet().iterator(); 130 while (it.hasNext()) { 131 Map.Entry<String, AvailableNetworkFailureCount> entry = it.next(); 132 if (entry.getValue().age < MAX_BSSID_AGE - 1) { 133 entry.getValue().age++; 134 } else { 135 // Decrement this SSID : AP count 136 String ssid = entry.getValue().ssid; 137 Pair<AvailableNetworkFailureCount, Integer> ssidFails = 138 mSsidFailureCount.get(ssid); 139 if (ssidFails != null) { 140 Integer apCount = ssidFails.second - 1; 141 if (apCount > 0) { 142 ssidFails = Pair.create(ssidFails.first, apCount); 143 mSsidFailureCount.put(ssid, ssidFails); 144 } else { 145 mSsidFailureCount.remove(ssid); 146 } 147 } else { 148 if (DBG) { 149 Log.d(TAG, "updateAvailableNetworks: SSID to AP count mismatch for " 150 + ssid); 151 } 152 } 153 it.remove(); 154 } 155 } 156 if (VDBG) Log.v(TAG, toString()); 157 } 158 159 /** 160 * Increments the failure reason count for the given bssid. Performs a check to see if we have 161 * exceeded a failure threshold for all available networks, and executes the last resort restart 162 * @param bssid of the network that has failed connection, can be "any" 163 * @param reason Message id from WifiStateMachine for this failure 164 * @return true if watchdog triggers, returned for test visibility 165 */ 166 public boolean noteConnectionFailureAndTriggerIfNeeded(String ssid, String bssid, int reason) { 167 if (VDBG) { 168 Log.v(TAG, "noteConnectionFailureAndTriggerIfNeeded: [" + ssid + ", " + bssid + ", " 169 + reason + "]"); 170 } 171 // Update failure count for the failing network 172 updateFailureCountForNetwork(ssid, bssid, reason); 173 return false; 174 } 175 176 /** 177 * Increments the failure reason count for the given network, in 'mSsidFailureCount' 178 * Failures are counted per SSID, either; by using the ssid string when the bssid is "any" 179 * or by looking up the ssid attached to a specific bssid 180 * An unused set of counts is also kept which is bssid specific, in 'mRecentAvailableNetworks' 181 * @param ssid of the network that has failed connection 182 * @param bssid of the network that has failed connection, can be "any" 183 * @param reason Message id from WifiStateMachine for this failure 184 */ 185 private void updateFailureCountForNetwork(String ssid, String bssid, int reason) { 186 if (VDBG) { 187 Log.v(TAG, "updateFailureCountForNetwork: [" + ssid + ", " + bssid + ", " 188 + reason + "]"); 189 } 190 if (BSSID_ANY.equals(bssid)) { 191 incrementSsidFailureCount(ssid, reason); 192 } else { 193 // Bssid count is actually unused except for logging purposes 194 // SSID count is incremented within the BSSID counting method 195 incrementBssidFailureCount(ssid, bssid, reason); 196 } 197 } 198 199 /** 200 * Update the per-SSID failure count 201 * @param ssid the ssid to increment failure count for 202 * @param reason the failure type to increment count for 203 */ 204 private void incrementSsidFailureCount(String ssid, int reason) { 205 Pair<AvailableNetworkFailureCount, Integer> ssidFails = mSsidFailureCount.get(ssid); 206 if (ssidFails == null) { 207 if (DBG) { 208 Log.v(TAG, "updateFailureCountForNetwork: No networks for ssid = " + ssid); 209 } 210 return; 211 } 212 AvailableNetworkFailureCount failureCount = ssidFails.first; 213 failureCount.incrementFailureCount(reason); 214 } 215 216 /** 217 * Update the per-BSSID failure count 218 * @param bssid the bssid to increment failure count for 219 * @param reason the failure type to increment count for 220 */ 221 private void incrementBssidFailureCount(String ssid, String bssid, int reason) { 222 AvailableNetworkFailureCount availableNetworkFailureCount = 223 mRecentAvailableNetworks.get(bssid); 224 if (availableNetworkFailureCount == null) { 225 if (DBG) { 226 Log.d(TAG, "updateFailureCountForNetwork: Unable to find Network [" + ssid 227 + ", " + bssid + "]"); 228 } 229 return; 230 } 231 if (!availableNetworkFailureCount.ssid.equals(ssid)) { 232 if (DBG) { 233 Log.d(TAG, "updateFailureCountForNetwork: Failed connection attempt has" 234 + " wrong ssid. Failed [" + ssid + ", " + bssid + "], buffered [" 235 + availableNetworkFailureCount.ssid + ", " + bssid + "]"); 236 } 237 return; 238 } 239 if (availableNetworkFailureCount.config == null) { 240 if (VDBG) { 241 Log.v(TAG, "updateFailureCountForNetwork: network has no config [" 242 + ssid + ", " + bssid + "]"); 243 } 244 } 245 availableNetworkFailureCount.incrementFailureCount(reason); 246 incrementSsidFailureCount(ssid, reason); 247 } 248 249 /** 250 * Clear failure counts for each network in recentAvailableNetworks 251 */ 252 private void clearAllFailureCounts() { 253 if (VDBG) Log.v(TAG, "clearAllFailureCounts."); 254 for (Map.Entry<String, AvailableNetworkFailureCount> entry 255 : mRecentAvailableNetworks.entrySet()) { 256 final AvailableNetworkFailureCount failureCount = entry.getValue(); 257 entry.getValue().resetCounts(); 258 } 259 for (Map.Entry<String, Pair<AvailableNetworkFailureCount, Integer>> entry 260 : mSsidFailureCount.entrySet()) { 261 final AvailableNetworkFailureCount failureCount = entry.getValue().first; 262 failureCount.resetCounts(); 263 } 264 } 265 /** 266 * Gets the buffer of recently available networks 267 */ 268 Map<String, AvailableNetworkFailureCount> getRecentAvailableNetworks() { 269 return mRecentAvailableNetworks; 270 } 271 272 /** 273 * Prints all networks & counts within mRecentAvailableNetworks to string 274 */ 275 public String toString() { 276 StringBuilder sb = new StringBuilder(); 277 sb.append("WifiLastResortWatchdog: " + mRecentAvailableNetworks.size() + " networks..."); 278 for (Map.Entry<String, AvailableNetworkFailureCount> entry 279 : mRecentAvailableNetworks.entrySet()) { 280 sb.append("\n " + entry.getKey() + ": " + entry.getValue()); 281 } 282 sb.append("\nmSsidFailureCount:"); 283 for (Map.Entry<String, Pair<AvailableNetworkFailureCount, Integer>> entry : 284 mSsidFailureCount.entrySet()) { 285 final AvailableNetworkFailureCount failureCount = entry.getValue().first; 286 final Integer apCount = entry.getValue().second; 287 sb.append("\n" + entry.getKey() + ": " + apCount + ", " 288 + failureCount.toString()); 289 } 290 return sb.toString(); 291 } 292 293 /** 294 * @param bssid bssid to check the failures for 295 * @return true if any failure count is over FAILURE_THRESHOLD 296 */ 297 public boolean isOverFailureThreshold(String bssid) { 298 if ((getFailureCount(bssid, FAILURE_CODE_ASSOCIATION) >= FAILURE_THRESHOLD) 299 || (getFailureCount(bssid, FAILURE_CODE_AUTHENTICATION) >= FAILURE_THRESHOLD) 300 || (getFailureCount(bssid, FAILURE_CODE_DHCP) >= FAILURE_THRESHOLD)) { 301 return true; 302 } 303 return false; 304 } 305 306 /** 307 * Get the failure count for a specific bssid. This actually checks the ssid attached to the 308 * BSSID and returns the SSID count 309 * @param reason failure reason to get count for 310 */ 311 public int getFailureCount(String bssid, int reason) { 312 AvailableNetworkFailureCount availableNetworkFailureCount = 313 mRecentAvailableNetworks.get(bssid); 314 if (availableNetworkFailureCount == null) { 315 return 0; 316 } 317 String ssid = availableNetworkFailureCount.ssid; 318 Pair<AvailableNetworkFailureCount, Integer> ssidFails = mSsidFailureCount.get(ssid); 319 if (ssidFails == null) { 320 if (DBG) { 321 Log.d(TAG, "getFailureCount: Could not find SSID count for " + ssid); 322 } 323 return 0; 324 } 325 final AvailableNetworkFailureCount failCount = ssidFails.first; 326 switch (reason) { 327 case FAILURE_CODE_ASSOCIATION: 328 return failCount.associationRejection; 329 case FAILURE_CODE_AUTHENTICATION: 330 return failCount.authenticationFailure; 331 case FAILURE_CODE_DHCP: 332 return failCount.dhcpFailure; 333 default: 334 return 0; 335 } 336 } 337 338 /** 339 * This class holds the failure counts for an 'available network' (one of the potential 340 * candidates for connection, as determined by framework). 341 */ 342 public static class AvailableNetworkFailureCount { 343 /** 344 * WifiConfiguration associated with this network. Can be null for Ephemeral networks 345 */ 346 public WifiConfiguration config; 347 /** 348 * SSID of the network (from ScanDetail) 349 */ 350 public String ssid = ""; 351 /** 352 * Number of times network has failed due to Association Rejection 353 */ 354 public int associationRejection = 0; 355 /** 356 * Number of times network has failed due to Authentication Failure or SSID_TEMP_DISABLED 357 */ 358 public int authenticationFailure = 0; 359 /** 360 * Number of times network has failed due to DHCP failure 361 */ 362 public int dhcpFailure = 0; 363 /** 364 * Number of scanResults since this network was last seen 365 */ 366 public int age = 0; 367 368 AvailableNetworkFailureCount(WifiConfiguration config) { 369 config = config; 370 } 371 372 /** 373 * @param reason failure reason to increment count for 374 */ 375 public void incrementFailureCount(int reason) { 376 switch (reason) { 377 case FAILURE_CODE_ASSOCIATION: 378 associationRejection++; 379 break; 380 case FAILURE_CODE_AUTHENTICATION: 381 authenticationFailure++; 382 break; 383 case FAILURE_CODE_DHCP: 384 dhcpFailure++; 385 break; 386 default: //do nothing 387 } 388 } 389 390 /** 391 * Set all failure counts for this network to 0 392 */ 393 void resetCounts() { 394 associationRejection = 0; 395 authenticationFailure = 0; 396 dhcpFailure = 0; 397 } 398 399 public String toString() { 400 return ssid + ", HasEverConnected: " + ((config != null) 401 ? config.getNetworkSelectionStatus().getHasEverConnected() : false) 402 + ", Failures: {" 403 + "Assoc: " + associationRejection 404 + ", Auth: " + authenticationFailure 405 + ", Dhcp: " + dhcpFailure 406 + "}" 407 + ", Age: " + age; 408 } 409 } 410} 411