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 android.net.wifi;
     18 
     19 import com.android.internal.util.State;
     20 import com.android.internal.util.StateMachine;
     21 
     22 import android.net.wifi.StateChangeResult;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.os.Handler;
     26 import android.os.Message;
     27 import android.os.Parcelable;
     28 import android.util.Log;
     29 
     30 /**
     31  * Tracks the state changes in supplicant and provides functionality
     32  * that is based on these state changes:
     33  * - detect a failed WPA handshake that loops indefinitely
     34  * - authentication failure handling
     35  */
     36 class SupplicantStateTracker extends StateMachine {
     37 
     38     private static final String TAG = "SupplicantStateTracker";
     39     private static final boolean DBG = false;
     40 
     41     private WifiStateMachine mWifiStateMachine;
     42     private int mAuthenticationFailuresCount = 0;
     43     /* Indicates authentication failure in supplicant broadcast.
     44      * TODO: enhance auth failure reporting to include notification
     45      * for all type of failures: EAP, WPS & WPA networks */
     46     private boolean mAuthFailureInSupplicantBroadcast = false;
     47 
     48     /* Maximum retries on a authentication failure notification */
     49     private static final int MAX_RETRIES_ON_AUTHENTICATION_FAILURE = 2;
     50 
     51     /* Tracks if networks have been disabled during a connection */
     52     private boolean mNetworksDisabledDuringConnect = false;
     53 
     54     private Context mContext;
     55 
     56     private State mUninitializedState = new UninitializedState();
     57     private State mDefaultState = new DefaultState();
     58     private State mInactiveState = new InactiveState();
     59     private State mDisconnectState = new DisconnectedState();
     60     private State mScanState = new ScanState();
     61     private State mHandshakeState = new HandshakeState();
     62     private State mCompletedState = new CompletedState();
     63     private State mDormantState = new DormantState();
     64 
     65     public SupplicantStateTracker(Context context, WifiStateMachine wsm, Handler target) {
     66         super(TAG, target.getLooper());
     67 
     68         mContext = context;
     69         mWifiStateMachine = wsm;
     70         addState(mDefaultState);
     71             addState(mUninitializedState, mDefaultState);
     72             addState(mInactiveState, mDefaultState);
     73             addState(mDisconnectState, mDefaultState);
     74             addState(mScanState, mDefaultState);
     75             addState(mHandshakeState, mDefaultState);
     76             addState(mCompletedState, mDefaultState);
     77             addState(mDormantState, mDefaultState);
     78 
     79         setInitialState(mUninitializedState);
     80 
     81         //start the state machine
     82         start();
     83     }
     84 
     85     private void handleNetworkConnectionFailure(int netId) {
     86         /* If other networks disabled during connection, enable them */
     87         if (mNetworksDisabledDuringConnect) {
     88             WifiConfigStore.enableAllNetworks();
     89             mNetworksDisabledDuringConnect = false;
     90         }
     91         /* Disable failed network */
     92         WifiConfigStore.disableNetwork(netId, WifiConfiguration.DISABLED_AUTH_FAILURE);
     93     }
     94 
     95     private void transitionOnSupplicantStateChange(StateChangeResult stateChangeResult) {
     96         SupplicantState supState = (SupplicantState) stateChangeResult.state;
     97 
     98         if (DBG) Log.d(TAG, "Supplicant state: " + supState.toString() + "\n");
     99 
    100         switch (supState) {
    101            case DISCONNECTED:
    102                 transitionTo(mDisconnectState);
    103                 break;
    104             case INTERFACE_DISABLED:
    105                 //we should have received a disconnection already, do nothing
    106                 break;
    107             case SCANNING:
    108                 transitionTo(mScanState);
    109                 break;
    110             case AUTHENTICATING:
    111             case ASSOCIATING:
    112             case ASSOCIATED:
    113             case FOUR_WAY_HANDSHAKE:
    114             case GROUP_HANDSHAKE:
    115                 transitionTo(mHandshakeState);
    116                 break;
    117             case COMPLETED:
    118                 transitionTo(mCompletedState);
    119                 break;
    120             case DORMANT:
    121                 transitionTo(mDormantState);
    122                 break;
    123             case INACTIVE:
    124                 transitionTo(mInactiveState);
    125                 break;
    126             case UNINITIALIZED:
    127             case INVALID:
    128                 transitionTo(mUninitializedState);
    129                 break;
    130             default:
    131                 Log.e(TAG, "Unknown supplicant state " + supState);
    132                 break;
    133         }
    134     }
    135 
    136     private void sendSupplicantStateChangedBroadcast(SupplicantState state, boolean failedAuth) {
    137         Intent intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
    138         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
    139                 | Intent.FLAG_RECEIVER_REPLACE_PENDING);
    140         intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable) state);
    141         if (failedAuth) {
    142             intent.putExtra(
    143                 WifiManager.EXTRA_SUPPLICANT_ERROR,
    144                 WifiManager.ERROR_AUTHENTICATING);
    145         }
    146         mContext.sendStickyBroadcast(intent);
    147     }
    148 
    149     /********************************************************
    150      * HSM states
    151      *******************************************************/
    152 
    153     class DefaultState extends State {
    154         @Override
    155          public void enter() {
    156              if (DBG) Log.d(TAG, getName() + "\n");
    157          }
    158         @Override
    159         public boolean processMessage(Message message) {
    160             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
    161             switch (message.what) {
    162                 case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
    163                     mAuthenticationFailuresCount++;
    164                     mAuthFailureInSupplicantBroadcast = true;
    165                     break;
    166                 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
    167                     StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
    168                     SupplicantState state = stateChangeResult.state;
    169                     sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast);
    170                     mAuthFailureInSupplicantBroadcast = false;
    171                     transitionOnSupplicantStateChange(stateChangeResult);
    172                     break;
    173                 case WifiStateMachine.CMD_RESET_SUPPLICANT_STATE:
    174                     transitionTo(mUninitializedState);
    175                     break;
    176                 case WifiStateMachine.CMD_CONNECT_NETWORK:
    177                     mNetworksDisabledDuringConnect = true;
    178                     break;
    179                 default:
    180                     Log.e(TAG, "Ignoring " + message);
    181                     break;
    182             }
    183             return HANDLED;
    184         }
    185     }
    186 
    187     /*
    188      * This indicates that the supplicant state as seen
    189      * by the framework is not initialized yet. We are
    190      * in this state right after establishing a control
    191      * channel connection before any supplicant events
    192      * or after we have lost the control channel
    193      * connection to the supplicant
    194      */
    195     class UninitializedState extends State {
    196         @Override
    197          public void enter() {
    198              if (DBG) Log.d(TAG, getName() + "\n");
    199          }
    200     }
    201 
    202     class InactiveState extends State {
    203         @Override
    204          public void enter() {
    205              if (DBG) Log.d(TAG, getName() + "\n");
    206          }
    207     }
    208 
    209     class DisconnectedState extends State {
    210         @Override
    211          public void enter() {
    212              if (DBG) Log.d(TAG, getName() + "\n");
    213              /* If a disconnect event happens after authentication failure
    214               * exceeds maximum retries, disable the network
    215               */
    216              Message message = getCurrentMessage();
    217              StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
    218 
    219              if (mAuthenticationFailuresCount >= MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
    220                  Log.d(TAG, "Failed to authenticate, disabling network " +
    221                          stateChangeResult.networkId);
    222                  handleNetworkConnectionFailure(stateChangeResult.networkId);
    223                  mAuthenticationFailuresCount = 0;
    224              }
    225          }
    226     }
    227 
    228     class ScanState extends State {
    229         @Override
    230          public void enter() {
    231              if (DBG) Log.d(TAG, getName() + "\n");
    232          }
    233     }
    234 
    235     class HandshakeState extends State {
    236         /**
    237          * The max number of the WPA supplicant loop iterations before we
    238          * decide that the loop should be terminated:
    239          */
    240         private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4;
    241         private int mLoopDetectIndex;
    242         private int mLoopDetectCount;
    243 
    244         @Override
    245          public void enter() {
    246              if (DBG) Log.d(TAG, getName() + "\n");
    247              mLoopDetectIndex = 0;
    248              mLoopDetectCount = 0;
    249          }
    250         @Override
    251         public boolean processMessage(Message message) {
    252             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
    253             switch (message.what) {
    254                 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
    255                     StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
    256                     SupplicantState state = stateChangeResult.state;
    257                     if (SupplicantState.isHandshakeState(state)) {
    258                         if (mLoopDetectIndex > state.ordinal()) {
    259                             mLoopDetectCount++;
    260                         }
    261                         if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) {
    262                             Log.d(TAG, "Supplicant loop detected, disabling network " +
    263                                     stateChangeResult.networkId);
    264                             handleNetworkConnectionFailure(stateChangeResult.networkId);
    265                         }
    266                         mLoopDetectIndex = state.ordinal();
    267                         sendSupplicantStateChangedBroadcast(state,
    268                                 mAuthFailureInSupplicantBroadcast);
    269                     } else {
    270                         //Have the DefaultState handle the transition
    271                         return NOT_HANDLED;
    272                     }
    273                     break;
    274                 default:
    275                     return NOT_HANDLED;
    276             }
    277             return HANDLED;
    278         }
    279     }
    280 
    281     class CompletedState extends State {
    282         @Override
    283          public void enter() {
    284              if (DBG) Log.d(TAG, getName() + "\n");
    285              /* Reset authentication failure count */
    286              mAuthenticationFailuresCount = 0;
    287              if (mNetworksDisabledDuringConnect) {
    288                  WifiConfigStore.enableAllNetworks();
    289                  mNetworksDisabledDuringConnect = false;
    290              }
    291         }
    292         @Override
    293         public boolean processMessage(Message message) {
    294             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
    295             switch(message.what) {
    296                 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
    297                     StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
    298                     SupplicantState state = stateChangeResult.state;
    299                     sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast);
    300                     /* Ignore any connecting state in completed state. Group re-keying
    301                      * events and other auth events that do not affect connectivity are
    302                      * ignored
    303                      */
    304                     if (SupplicantState.isConnecting(state)) {
    305                         break;
    306                     }
    307                     transitionOnSupplicantStateChange(stateChangeResult);
    308                     break;
    309                 case WifiStateMachine.CMD_RESET_SUPPLICANT_STATE:
    310                     sendSupplicantStateChangedBroadcast(SupplicantState.DISCONNECTED, false);
    311                     transitionTo(mUninitializedState);
    312                     break;
    313                 default:
    314                     return NOT_HANDLED;
    315             }
    316             return HANDLED;
    317         }
    318     }
    319 
    320     //TODO: remove after getting rid of the state in supplicant
    321     class DormantState extends State {
    322         @Override
    323         public void enter() {
    324             if (DBG) Log.d(TAG, getName() + "\n");
    325         }
    326     }
    327 }
    328