WifiMonitor.java revision b98d878f43748a64c68ffe05ce64c5b7c72fe922
1/* 2 * Copyright (C) 2008 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 android.net.wifi; 18 19import android.util.Log; 20import android.util.Config; 21import android.net.NetworkInfo; 22 23import java.util.regex.Pattern; 24import java.util.regex.Matcher; 25 26/** 27 * Listens for events from the wpa_supplicant server, and passes them on 28 * to the {@link WifiStateMachine} for handling. Runs in its own thread. 29 * 30 * @hide 31 */ 32public class WifiMonitor { 33 34 private static final String TAG = "WifiMonitor"; 35 36 /** Events we receive from the supplicant daemon */ 37 38 private static final int CONNECTED = 1; 39 private static final int DISCONNECTED = 2; 40 private static final int STATE_CHANGE = 3; 41 private static final int SCAN_RESULTS = 4; 42 private static final int LINK_SPEED = 5; 43 private static final int TERMINATING = 6; 44 private static final int DRIVER_STATE = 7; 45 private static final int EAP_FAILURE = 8; 46 private static final int UNKNOWN = 9; 47 48 /** All events coming from the supplicant start with this prefix */ 49 private static final String eventPrefix = "CTRL-EVENT-"; 50 private static final int eventPrefixLen = eventPrefix.length(); 51 52 /** All WPA events coming from the supplicant start with this prefix */ 53 private static final String wpaEventPrefix = "WPA:"; 54 private static final String passwordKeyMayBeIncorrectEvent = 55 "pre-shared key may be incorrect"; 56 57 /* WPS events */ 58 private static final String wpsOverlapEvent = "WPS-OVERLAP-DETECTED"; 59 60 /** 61 * Names of events from wpa_supplicant (minus the prefix). In the 62 * format descriptions, * "<code>x</code>" 63 * designates a dynamic value that needs to be parsed out from the event 64 * string 65 */ 66 /** 67 * <pre> 68 * CTRL-EVENT-CONNECTED - Connection to xx:xx:xx:xx:xx:xx completed 69 * </pre> 70 * <code>xx:xx:xx:xx:xx:xx</code> is the BSSID of the associated access point 71 */ 72 private static final String connectedEvent = "CONNECTED"; 73 /** 74 * <pre> 75 * CTRL-EVENT-DISCONNECTED - Disconnect event - remove keys 76 * </pre> 77 */ 78 private static final String disconnectedEvent = "DISCONNECTED"; 79 /** 80 * <pre> 81 * CTRL-EVENT-STATE-CHANGE x 82 * </pre> 83 * <code>x</code> is the numerical value of the new state. 84 */ 85 private static final String stateChangeEvent = "STATE-CHANGE"; 86 /** 87 * <pre> 88 * CTRL-EVENT-SCAN-RESULTS ready 89 * </pre> 90 */ 91 private static final String scanResultsEvent = "SCAN-RESULTS"; 92 93 /** 94 * <pre> 95 * CTRL-EVENT-LINK-SPEED x Mb/s 96 * </pre> 97 * {@code x} is the link speed in Mb/sec. 98 */ 99 private static final String linkSpeedEvent = "LINK-SPEED"; 100 /** 101 * <pre> 102 * CTRL-EVENT-TERMINATING - signal x 103 * </pre> 104 * <code>x</code> is the signal that caused termination. 105 */ 106 private static final String terminatingEvent = "TERMINATING"; 107 /** 108 * <pre> 109 * CTRL-EVENT-DRIVER-STATE state 110 * </pre> 111 * <code>state</code> is either STARTED or STOPPED 112 */ 113 private static final String driverStateEvent = "DRIVER-STATE"; 114 /** 115 * <pre> 116 * CTRL-EVENT-EAP-FAILURE EAP authentication failed 117 * </pre> 118 */ 119 private static final String eapFailureEvent = "EAP-FAILURE"; 120 121 /** 122 * This indicates an authentication failure on EAP FAILURE event 123 */ 124 private static final String eapAuthFailure = "EAP authentication failed"; 125 126 /** 127 * Regex pattern for extracting an Ethernet-style MAC address from a string. 128 * Matches a strings like the following:<pre> 129 * CTRL-EVENT-CONNECTED - Connection to 00:1e:58:ec:d5:6d completed (reauth) [id=1 id_str=]</pre> 130 */ 131 private static Pattern mConnectedEventPattern = 132 Pattern.compile("((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) .* \\[id=([0-9]+) "); 133 134 private final WifiStateMachine mWifiStateMachine; 135 136 /** 137 * This indicates the supplicant connection for the monitor is closed 138 */ 139 private static final String monitorSocketClosed = "connection closed"; 140 141 /** 142 * This indicates a read error on the monitor socket conenction 143 */ 144 private static final String wpaRecvError = "recv error"; 145 146 /** 147 * Tracks consecutive receive errors 148 */ 149 private int mRecvErrors = 0; 150 151 /** 152 * Max errors before we close supplicant connection 153 */ 154 private static final int MAX_RECV_ERRORS = 10; 155 156 public WifiMonitor(WifiStateMachine wifiStateMachine) { 157 mWifiStateMachine = wifiStateMachine; 158 } 159 160 public void startMonitoring() { 161 new MonitorThread().start(); 162 } 163 164 class MonitorThread extends Thread { 165 public MonitorThread() { 166 super("WifiMonitor"); 167 } 168 169 public void run() { 170 171 if (connectToSupplicant()) { 172 // Send a message indicating that it is now possible to send commands 173 // to the supplicant 174 mWifiStateMachine.notifySupplicantConnection(); 175 } else { 176 mWifiStateMachine.notifySupplicantLost(); 177 return; 178 } 179 180 //noinspection InfiniteLoopStatement 181 for (;;) { 182 String eventStr = WifiNative.waitForEvent(); 183 184 // Skip logging the common but mostly uninteresting scan-results event 185 if (Config.LOGD && eventStr.indexOf(scanResultsEvent) == -1) { 186 Log.v(TAG, "Event [" + eventStr + "]"); 187 } 188 if (!eventStr.startsWith(eventPrefix)) { 189 if (eventStr.startsWith(wpaEventPrefix) && 190 0 < eventStr.indexOf(passwordKeyMayBeIncorrectEvent)) { 191 mWifiStateMachine.notifyAuthenticationFailure(); 192 } else if (eventStr.startsWith(wpsOverlapEvent)) { 193 mWifiStateMachine.notifyWpsOverlap(); 194 } 195 continue; 196 } 197 198 String eventName = eventStr.substring(eventPrefixLen); 199 int nameEnd = eventName.indexOf(' '); 200 if (nameEnd != -1) 201 eventName = eventName.substring(0, nameEnd); 202 if (eventName.length() == 0) { 203 if (Config.LOGD) Log.i(TAG, "Received wpa_supplicant event with empty event name"); 204 continue; 205 } 206 /* 207 * Map event name into event enum 208 */ 209 int event; 210 if (eventName.equals(connectedEvent)) 211 event = CONNECTED; 212 else if (eventName.equals(disconnectedEvent)) 213 event = DISCONNECTED; 214 else if (eventName.equals(stateChangeEvent)) 215 event = STATE_CHANGE; 216 else if (eventName.equals(scanResultsEvent)) 217 event = SCAN_RESULTS; 218 else if (eventName.equals(linkSpeedEvent)) 219 event = LINK_SPEED; 220 else if (eventName.equals(terminatingEvent)) 221 event = TERMINATING; 222 else if (eventName.equals(driverStateEvent)) 223 event = DRIVER_STATE; 224 else if (eventName.equals(eapFailureEvent)) 225 event = EAP_FAILURE; 226 else 227 event = UNKNOWN; 228 229 String eventData = eventStr; 230 if (event == DRIVER_STATE || event == LINK_SPEED) 231 eventData = eventData.split(" ")[1]; 232 else if (event == STATE_CHANGE || event == EAP_FAILURE) { 233 int ind = eventStr.indexOf(" "); 234 if (ind != -1) { 235 eventData = eventStr.substring(ind + 1); 236 } 237 } else { 238 int ind = eventStr.indexOf(" - "); 239 if (ind != -1) { 240 eventData = eventStr.substring(ind + 3); 241 } 242 } 243 244 if (event == STATE_CHANGE) { 245 handleSupplicantStateChange(eventData); 246 } else if (event == DRIVER_STATE) { 247 handleDriverEvent(eventData); 248 } else if (event == TERMINATING) { 249 /** 250 * If monitor socket is closed, we have already 251 * stopped the supplicant, simply exit the monitor thread 252 */ 253 if (eventData.startsWith(monitorSocketClosed)) { 254 if (Config.LOGD) { 255 Log.d(TAG, "Monitor socket is closed, exiting thread"); 256 } 257 break; 258 } 259 260 /** 261 * Close the supplicant connection if we see 262 * too many recv errors 263 */ 264 if (eventData.startsWith(wpaRecvError)) { 265 if (++mRecvErrors > MAX_RECV_ERRORS) { 266 if (Config.LOGD) { 267 Log.d(TAG, "too many recv errors, closing connection"); 268 } 269 } else { 270 continue; 271 } 272 } 273 274 // notify and exit 275 mWifiStateMachine.notifySupplicantLost(); 276 break; 277 } else if (event == EAP_FAILURE) { 278 if (eventData.startsWith(eapAuthFailure)) { 279 mWifiStateMachine.notifyAuthenticationFailure(); 280 } 281 } else { 282 handleEvent(event, eventData); 283 } 284 mRecvErrors = 0; 285 } 286 } 287 288 private boolean connectToSupplicant() { 289 int connectTries = 0; 290 291 while (true) { 292 if (WifiNative.connectToSupplicant()) { 293 return true; 294 } 295 if (connectTries++ < 5) { 296 nap(1); 297 } else { 298 break; 299 } 300 } 301 return false; 302 } 303 304 private void handleDriverEvent(String state) { 305 if (state == null) { 306 return; 307 } 308 if (state.equals("STOPPED")) { 309 mWifiStateMachine.notifyDriverStopped(); 310 } else if (state.equals("STARTED")) { 311 mWifiStateMachine.notifyDriverStarted(); 312 } else if (state.equals("HANGED")) { 313 mWifiStateMachine.notifyDriverHung(); 314 } 315 } 316 317 /** 318 * Handle all supplicant events except STATE-CHANGE 319 * @param event the event type 320 * @param remainder the rest of the string following the 321 * event name and " — " 322 */ 323 void handleEvent(int event, String remainder) { 324 switch (event) { 325 case DISCONNECTED: 326 handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED, remainder); 327 break; 328 329 case CONNECTED: 330 handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED, remainder); 331 break; 332 333 case SCAN_RESULTS: 334 mWifiStateMachine.notifyScanResultsAvailable(); 335 break; 336 337 case UNKNOWN: 338 break; 339 } 340 } 341 342 /** 343 * Handle the supplicant STATE-CHANGE event 344 * @param dataString New supplicant state string in the format: 345 * id=network-id state=new-state 346 */ 347 private void handleSupplicantStateChange(String dataString) { 348 String[] dataTokens = dataString.split(" "); 349 350 String BSSID = null; 351 int networkId = -1; 352 int newState = -1; 353 for (String token : dataTokens) { 354 String[] nameValue = token.split("="); 355 if (nameValue.length != 2) { 356 continue; 357 } 358 359 if (nameValue[0].equals("BSSID")) { 360 BSSID = nameValue[1]; 361 continue; 362 } 363 364 int value; 365 try { 366 value = Integer.parseInt(nameValue[1]); 367 } catch (NumberFormatException e) { 368 Log.w(TAG, "STATE-CHANGE non-integer parameter: " + token); 369 continue; 370 } 371 372 if (nameValue[0].equals("id")) { 373 networkId = value; 374 } else if (nameValue[0].equals("state")) { 375 newState = value; 376 } 377 } 378 379 if (newState == -1) return; 380 381 SupplicantState newSupplicantState = SupplicantState.INVALID; 382 for (SupplicantState state : SupplicantState.values()) { 383 if (state.ordinal() == newState) { 384 newSupplicantState = state; 385 break; 386 } 387 } 388 if (newSupplicantState == SupplicantState.INVALID) { 389 Log.w(TAG, "Invalid supplicant state: " + newState); 390 } 391 mWifiStateMachine.notifySupplicantStateChange(networkId, BSSID, newSupplicantState); 392 } 393 } 394 395 private void handleNetworkStateChange(NetworkInfo.DetailedState newState, String data) { 396 String BSSID = null; 397 int networkId = -1; 398 if (newState == NetworkInfo.DetailedState.CONNECTED) { 399 Matcher match = mConnectedEventPattern.matcher(data); 400 if (!match.find()) { 401 if (Config.LOGD) Log.d(TAG, "Could not find BSSID in CONNECTED event string"); 402 } else { 403 BSSID = match.group(1); 404 try { 405 networkId = Integer.parseInt(match.group(2)); 406 } catch (NumberFormatException e) { 407 networkId = -1; 408 } 409 } 410 } 411 mWifiStateMachine.notifyNetworkStateChange(newState, BSSID, networkId); 412 } 413 414 /** 415 * Sleep for a period of time. 416 * @param secs the number of seconds to sleep 417 */ 418 private static void nap(int secs) { 419 try { 420 Thread.sleep(secs * 1000); 421 } catch (InterruptedException ignore) { 422 } 423 } 424} 425