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