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