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