Home | History | Annotate | Download | only in wifi
      1 /*
      2  * Copyright (C) 2010 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 com.android.server.wifi;
     18 
     19 import android.os.BatteryStats;
     20 import android.os.RemoteException;
     21 import android.os.ServiceManager;
     22 import android.util.Slog;
     23 import com.android.internal.app.IBatteryStats;
     24 import com.android.internal.util.State;
     25 import com.android.internal.util.StateMachine;
     26 
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.net.wifi.SupplicantState;
     30 import android.net.wifi.WifiConfiguration;
     31 import android.net.wifi.WifiManager;
     32 import android.os.Handler;
     33 import android.os.Message;
     34 import android.os.Parcelable;
     35 import android.os.UserHandle;
     36 import android.util.Log;
     37 
     38 import java.io.FileDescriptor;
     39 import java.io.PrintWriter;
     40 
     41 /**
     42  * Tracks the state changes in supplicant and provides functionality
     43  * that is based on these state changes:
     44  * - detect a failed WPA handshake that loops indefinitely
     45  * - authentication failure handling
     46  */
     47 class SupplicantStateTracker extends StateMachine {
     48 
     49     private static final String TAG = "SupplicantStateTracker";
     50     private static boolean DBG = false;
     51 
     52     private final WifiStateMachine mWifiStateMachine;
     53     private final WifiConfigStore mWifiConfigStore;
     54     private final IBatteryStats mBatteryStats;
     55     private int mAuthenticationFailuresCount = 0;
     56     private int mAssociationRejectCount = 0;
     57     /* Indicates authentication failure in supplicant broadcast.
     58      * TODO: enhance auth failure reporting to include notification
     59      * for all type of failures: EAP, WPS & WPA networks */
     60     private boolean mAuthFailureInSupplicantBroadcast = false;
     61 
     62     /* Maximum retries on a authentication failure notification */
     63     private static final int MAX_RETRIES_ON_AUTHENTICATION_FAILURE = 2;
     64 
     65     /* Maximum retries on assoc rejection events */
     66     private static final int MAX_RETRIES_ON_ASSOCIATION_REJECT = 16;
     67 
     68     /* Tracks if networks have been disabled during a connection */
     69     private boolean mNetworksDisabledDuringConnect = false;
     70 
     71     private final Context mContext;
     72 
     73     private final State mUninitializedState = new UninitializedState();
     74     private final State mDefaultState = new DefaultState();
     75     private final State mInactiveState = new InactiveState();
     76     private final State mDisconnectState = new DisconnectedState();
     77     private final State mScanState = new ScanState();
     78     private final State mHandshakeState = new HandshakeState();
     79     private final State mCompletedState = new CompletedState();
     80     private final State mDormantState = new DormantState();
     81 
     82     void enableVerboseLogging(int verbose) {
     83         if (verbose > 0) {
     84             DBG = true;
     85         } else {
     86             DBG = false;
     87         }
     88     }
     89 
     90     public String getSupplicantStateName() {
     91         return getCurrentState().getName();
     92     }
     93 
     94     public SupplicantStateTracker(Context c, WifiStateMachine wsm, WifiConfigStore wcs, Handler t) {
     95         super(TAG, t.getLooper());
     96 
     97         mContext = c;
     98         mWifiStateMachine = wsm;
     99         mWifiConfigStore = wcs;
    100         mBatteryStats = (IBatteryStats)ServiceManager.getService(BatteryStats.SERVICE_NAME);
    101         addState(mDefaultState);
    102             addState(mUninitializedState, mDefaultState);
    103             addState(mInactiveState, mDefaultState);
    104             addState(mDisconnectState, mDefaultState);
    105             addState(mScanState, mDefaultState);
    106             addState(mHandshakeState, mDefaultState);
    107             addState(mCompletedState, mDefaultState);
    108             addState(mDormantState, mDefaultState);
    109 
    110         setInitialState(mUninitializedState);
    111         setLogRecSize(50);
    112         setLogOnlyTransitions(true);
    113         //start the state machine
    114         start();
    115     }
    116 
    117     private void handleNetworkConnectionFailure(int netId, int disableReason) {
    118         if (DBG) {
    119             Log.d(TAG, "handleNetworkConnectionFailure netId=" + Integer.toString(netId)
    120                     + " reason " + Integer.toString(disableReason)
    121                     + " mNetworksDisabledDuringConnect=" + mNetworksDisabledDuringConnect);
    122         }
    123 
    124         /* If other networks disabled during connection, enable them */
    125         if (mNetworksDisabledDuringConnect) {
    126             mWifiConfigStore.enableAllNetworks();
    127             mNetworksDisabledDuringConnect = false;
    128         }
    129         /* Disable failed network */
    130         mWifiConfigStore.disableNetwork(netId, disableReason);
    131     }
    132 
    133     private void transitionOnSupplicantStateChange(StateChangeResult stateChangeResult) {
    134         SupplicantState supState = (SupplicantState) stateChangeResult.state;
    135 
    136         if (DBG) Log.d(TAG, "Supplicant state: " + supState.toString() + "\n");
    137 
    138         switch (supState) {
    139            case DISCONNECTED:
    140                 transitionTo(mDisconnectState);
    141                 break;
    142             case INTERFACE_DISABLED:
    143                 //we should have received a disconnection already, do nothing
    144                 break;
    145             case SCANNING:
    146                 transitionTo(mScanState);
    147                 break;
    148             case AUTHENTICATING:
    149             case ASSOCIATING:
    150             case ASSOCIATED:
    151             case FOUR_WAY_HANDSHAKE:
    152             case GROUP_HANDSHAKE:
    153                 transitionTo(mHandshakeState);
    154                 break;
    155             case COMPLETED:
    156                 transitionTo(mCompletedState);
    157                 break;
    158             case DORMANT:
    159                 transitionTo(mDormantState);
    160                 break;
    161             case INACTIVE:
    162                 transitionTo(mInactiveState);
    163                 break;
    164             case UNINITIALIZED:
    165             case INVALID:
    166                 transitionTo(mUninitializedState);
    167                 break;
    168             default:
    169                 Log.e(TAG, "Unknown supplicant state " + supState);
    170                 break;
    171         }
    172     }
    173 
    174     private void sendSupplicantStateChangedBroadcast(SupplicantState state, boolean failedAuth) {
    175         int supplState;
    176         switch (state) {
    177             case DISCONNECTED: supplState = BatteryStats.WIFI_SUPPL_STATE_DISCONNECTED; break;
    178             case INTERFACE_DISABLED:
    179                 supplState = BatteryStats.WIFI_SUPPL_STATE_INTERFACE_DISABLED; break;
    180             case INACTIVE: supplState = BatteryStats.WIFI_SUPPL_STATE_INACTIVE; break;
    181             case SCANNING: supplState = BatteryStats.WIFI_SUPPL_STATE_SCANNING; break;
    182             case AUTHENTICATING: supplState = BatteryStats.WIFI_SUPPL_STATE_AUTHENTICATING; break;
    183             case ASSOCIATING: supplState = BatteryStats.WIFI_SUPPL_STATE_ASSOCIATING; break;
    184             case ASSOCIATED: supplState = BatteryStats.WIFI_SUPPL_STATE_ASSOCIATED; break;
    185             case FOUR_WAY_HANDSHAKE:
    186                 supplState = BatteryStats.WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE; break;
    187             case GROUP_HANDSHAKE: supplState = BatteryStats.WIFI_SUPPL_STATE_GROUP_HANDSHAKE; break;
    188             case COMPLETED: supplState = BatteryStats.WIFI_SUPPL_STATE_COMPLETED; break;
    189             case DORMANT: supplState = BatteryStats.WIFI_SUPPL_STATE_DORMANT; break;
    190             case UNINITIALIZED: supplState = BatteryStats.WIFI_SUPPL_STATE_UNINITIALIZED; break;
    191             case INVALID: supplState = BatteryStats.WIFI_SUPPL_STATE_INVALID; break;
    192             default:
    193                 Slog.w(TAG, "Unknown supplicant state " + state);
    194                 supplState = BatteryStats.WIFI_SUPPL_STATE_INVALID;
    195                 break;
    196         }
    197         try {
    198             mBatteryStats.noteWifiSupplicantStateChanged(supplState, failedAuth);
    199         } catch (RemoteException e) {
    200             // Won't happen.
    201         }
    202         Intent intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
    203         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
    204                 | Intent.FLAG_RECEIVER_REPLACE_PENDING);
    205         intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable) state);
    206         if (failedAuth) {
    207             intent.putExtra(
    208                 WifiManager.EXTRA_SUPPLICANT_ERROR,
    209                 WifiManager.ERROR_AUTHENTICATING);
    210         }
    211         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
    212     }
    213 
    214     /********************************************************
    215      * HSM states
    216      *******************************************************/
    217 
    218     class DefaultState extends State {
    219         @Override
    220          public void enter() {
    221              if (DBG) Log.d(TAG, getName() + "\n");
    222          }
    223         @Override
    224         public boolean processMessage(Message message) {
    225             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
    226             switch (message.what) {
    227                 case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
    228                     mAuthenticationFailuresCount++;
    229                     mAuthFailureInSupplicantBroadcast = true;
    230                     break;
    231                 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
    232                     StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
    233                     SupplicantState state = stateChangeResult.state;
    234                     sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast);
    235                     mAuthFailureInSupplicantBroadcast = false;
    236                     transitionOnSupplicantStateChange(stateChangeResult);
    237                     break;
    238                 case WifiStateMachine.CMD_RESET_SUPPLICANT_STATE:
    239                     transitionTo(mUninitializedState);
    240                     break;
    241                 case WifiManager.CONNECT_NETWORK:
    242                     mNetworksDisabledDuringConnect = true;
    243                     mAssociationRejectCount = 0;
    244                     break;
    245                 case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
    246                     mAssociationRejectCount++;
    247                     break;
    248                 default:
    249                     Log.e(TAG, "Ignoring " + message);
    250                     break;
    251             }
    252             return HANDLED;
    253         }
    254     }
    255 
    256     /*
    257      * This indicates that the supplicant state as seen
    258      * by the framework is not initialized yet. We are
    259      * in this state right after establishing a control
    260      * channel connection before any supplicant events
    261      * or after we have lost the control channel
    262      * connection to the supplicant
    263      */
    264     class UninitializedState extends State {
    265         @Override
    266          public void enter() {
    267              if (DBG) Log.d(TAG, getName() + "\n");
    268          }
    269     }
    270 
    271     class InactiveState extends State {
    272         @Override
    273          public void enter() {
    274              if (DBG) Log.d(TAG, getName() + "\n");
    275          }
    276     }
    277 
    278     class DisconnectedState extends State {
    279         @Override
    280          public void enter() {
    281              if (DBG) Log.d(TAG, getName() + "\n");
    282              /* If a disconnect event happens after authentication failure
    283               * exceeds maximum retries, disable the network
    284               */
    285              Message message = getCurrentMessage();
    286              StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
    287 
    288              if (mAuthenticationFailuresCount >= MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
    289                  Log.d(TAG, "Failed to authenticate, disabling network " +
    290                          stateChangeResult.networkId);
    291                  handleNetworkConnectionFailure(stateChangeResult.networkId,
    292                          WifiConfiguration.DISABLED_AUTH_FAILURE);
    293                  mAuthenticationFailuresCount = 0;
    294              }
    295              else if (mAssociationRejectCount >= MAX_RETRIES_ON_ASSOCIATION_REJECT) {
    296                  Log.d(TAG, "Association getting rejected, disabling network " +
    297                          stateChangeResult.networkId);
    298                  handleNetworkConnectionFailure(stateChangeResult.networkId,
    299                          WifiConfiguration.DISABLED_ASSOCIATION_REJECT);
    300                  mAssociationRejectCount = 0;
    301             }
    302          }
    303     }
    304 
    305     class ScanState extends State {
    306         @Override
    307          public void enter() {
    308              if (DBG) Log.d(TAG, getName() + "\n");
    309          }
    310     }
    311 
    312     class HandshakeState extends State {
    313         /**
    314          * The max number of the WPA supplicant loop iterations before we
    315          * decide that the loop should be terminated:
    316          */
    317         private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4;
    318         private int mLoopDetectIndex;
    319         private int mLoopDetectCount;
    320 
    321         @Override
    322          public void enter() {
    323              if (DBG) Log.d(TAG, getName() + "\n");
    324              mLoopDetectIndex = 0;
    325              mLoopDetectCount = 0;
    326          }
    327         @Override
    328         public boolean processMessage(Message message) {
    329             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
    330             switch (message.what) {
    331                 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
    332                     StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
    333                     SupplicantState state = stateChangeResult.state;
    334                     if (SupplicantState.isHandshakeState(state)) {
    335                         if (mLoopDetectIndex > state.ordinal()) {
    336                             mLoopDetectCount++;
    337                         }
    338                         if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) {
    339                             Log.d(TAG, "Supplicant loop detected, disabling network " +
    340                                     stateChangeResult.networkId);
    341                             handleNetworkConnectionFailure(stateChangeResult.networkId,
    342                                     WifiConfiguration.DISABLED_AUTH_FAILURE);
    343                         }
    344                         mLoopDetectIndex = state.ordinal();
    345                         sendSupplicantStateChangedBroadcast(state,
    346                                 mAuthFailureInSupplicantBroadcast);
    347                     } else {
    348                         //Have the DefaultState handle the transition
    349                         return NOT_HANDLED;
    350                     }
    351                     break;
    352                 default:
    353                     return NOT_HANDLED;
    354             }
    355             return HANDLED;
    356         }
    357     }
    358 
    359     class CompletedState extends State {
    360         @Override
    361          public void enter() {
    362              if (DBG) Log.d(TAG, getName() + "\n");
    363              /* Reset authentication failure count */
    364              mAuthenticationFailuresCount = 0;
    365              mAssociationRejectCount = 0;
    366              if (mNetworksDisabledDuringConnect) {
    367                  mWifiConfigStore.enableAllNetworks();
    368                  mNetworksDisabledDuringConnect = false;
    369              }
    370         }
    371         @Override
    372         public boolean processMessage(Message message) {
    373             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
    374             switch(message.what) {
    375                 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
    376                     StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
    377                     SupplicantState state = stateChangeResult.state;
    378                     sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast);
    379                     /* Ignore any connecting state in completed state. Group re-keying
    380                      * events and other auth events that do not affect connectivity are
    381                      * ignored
    382                      */
    383                     if (SupplicantState.isConnecting(state)) {
    384                         break;
    385                     }
    386                     transitionOnSupplicantStateChange(stateChangeResult);
    387                     break;
    388                 case WifiStateMachine.CMD_RESET_SUPPLICANT_STATE:
    389                     sendSupplicantStateChangedBroadcast(SupplicantState.DISCONNECTED, false);
    390                     transitionTo(mUninitializedState);
    391                     break;
    392                 default:
    393                     return NOT_HANDLED;
    394             }
    395             return HANDLED;
    396         }
    397     }
    398 
    399     //TODO: remove after getting rid of the state in supplicant
    400     class DormantState extends State {
    401         @Override
    402         public void enter() {
    403             if (DBG) Log.d(TAG, getName() + "\n");
    404         }
    405     }
    406 
    407     @Override
    408     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    409         super.dump(fd, pw, args);
    410         pw.println("mAuthenticationFailuresCount " + mAuthenticationFailuresCount);
    411         pw.println("mAuthFailureInSupplicantBroadcast " + mAuthFailureInSupplicantBroadcast);
    412         pw.println("mNetworksDisabledDuringConnect " + mNetworksDisabledDuringConnect);
    413         pw.println();
    414     }
    415 }
    416