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, * &quot;<code>x</code>&quot;
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 &quot;&#8195;&#8212;&#8195;&quot;
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