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