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