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