SupplicantStateTracker.java revision 3bc487aa49deecbc358ee819e0dd4b2534412281
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 17package com.android.server.wifi; 18 19import android.content.Context; 20import android.content.Intent; 21import android.net.wifi.SupplicantState; 22import android.net.wifi.WifiConfiguration; 23import android.net.wifi.WifiManager; 24import android.os.BatteryStats; 25import android.os.Handler; 26import android.os.Message; 27import android.os.Parcelable; 28import android.os.RemoteException; 29import android.os.ServiceManager; 30import android.os.UserHandle; 31import android.util.Log; 32import android.util.Slog; 33 34import com.android.internal.app.IBatteryStats; 35import com.android.internal.util.State; 36import com.android.internal.util.StateMachine; 37 38import java.io.FileDescriptor; 39import 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 */ 47public 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 mNetworksDisabledDuringConnect = false; } 122 /* update network status */ 123 mWifiConfigManager.updateNetworkSelectionStatus(netId, disableReason); 124 } 125 126 private void transitionOnSupplicantStateChange(StateChangeResult stateChangeResult) { 127 SupplicantState supState = (SupplicantState) stateChangeResult.state; 128 129 if (DBG) Log.d(TAG, "Supplicant state: " + supState.toString() + "\n"); 130 131 switch (supState) { 132 case DISCONNECTED: 133 transitionTo(mDisconnectState); 134 break; 135 case INTERFACE_DISABLED: 136 //we should have received a disconnection already, do nothing 137 break; 138 case SCANNING: 139 transitionTo(mScanState); 140 break; 141 case AUTHENTICATING: 142 case ASSOCIATING: 143 case ASSOCIATED: 144 case FOUR_WAY_HANDSHAKE: 145 case GROUP_HANDSHAKE: 146 transitionTo(mHandshakeState); 147 break; 148 case COMPLETED: 149 transitionTo(mCompletedState); 150 break; 151 case DORMANT: 152 transitionTo(mDormantState); 153 break; 154 case INACTIVE: 155 transitionTo(mInactiveState); 156 break; 157 case UNINITIALIZED: 158 case INVALID: 159 transitionTo(mUninitializedState); 160 break; 161 default: 162 Log.e(TAG, "Unknown supplicant state " + supState); 163 break; 164 } 165 } 166 167 private void sendSupplicantStateChangedBroadcast(SupplicantState state, boolean failedAuth) { 168 int supplState; 169 switch (state) { 170 case DISCONNECTED: supplState = BatteryStats.WIFI_SUPPL_STATE_DISCONNECTED; break; 171 case INTERFACE_DISABLED: 172 supplState = BatteryStats.WIFI_SUPPL_STATE_INTERFACE_DISABLED; break; 173 case INACTIVE: supplState = BatteryStats.WIFI_SUPPL_STATE_INACTIVE; break; 174 case SCANNING: supplState = BatteryStats.WIFI_SUPPL_STATE_SCANNING; break; 175 case AUTHENTICATING: supplState = BatteryStats.WIFI_SUPPL_STATE_AUTHENTICATING; break; 176 case ASSOCIATING: supplState = BatteryStats.WIFI_SUPPL_STATE_ASSOCIATING; break; 177 case ASSOCIATED: supplState = BatteryStats.WIFI_SUPPL_STATE_ASSOCIATED; break; 178 case FOUR_WAY_HANDSHAKE: 179 supplState = BatteryStats.WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE; break; 180 case GROUP_HANDSHAKE: supplState = BatteryStats.WIFI_SUPPL_STATE_GROUP_HANDSHAKE; break; 181 case COMPLETED: supplState = BatteryStats.WIFI_SUPPL_STATE_COMPLETED; break; 182 case DORMANT: supplState = BatteryStats.WIFI_SUPPL_STATE_DORMANT; break; 183 case UNINITIALIZED: supplState = BatteryStats.WIFI_SUPPL_STATE_UNINITIALIZED; break; 184 case INVALID: supplState = BatteryStats.WIFI_SUPPL_STATE_INVALID; break; 185 default: 186 Slog.w(TAG, "Unknown supplicant state " + state); 187 supplState = BatteryStats.WIFI_SUPPL_STATE_INVALID; 188 break; 189 } 190 try { 191 mBatteryStats.noteWifiSupplicantStateChanged(supplState, failedAuth); 192 } catch (RemoteException e) { 193 // Won't happen. 194 } 195 Intent intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); 196 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT 197 | Intent.FLAG_RECEIVER_REPLACE_PENDING); 198 intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable) state); 199 if (failedAuth) { 200 intent.putExtra( 201 WifiManager.EXTRA_SUPPLICANT_ERROR, 202 WifiManager.ERROR_AUTHENTICATING); 203 } 204 mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); 205 } 206 207 /******************************************************** 208 * HSM states 209 *******************************************************/ 210 211 class DefaultState extends State { 212 @Override 213 public void enter() { 214 if (DBG) Log.d(TAG, getName() + "\n"); 215 } 216 @Override 217 public boolean processMessage(Message message) { 218 if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); 219 switch (message.what) { 220 case WifiMonitor.AUTHENTICATION_FAILURE_EVENT: 221 mAuthFailureInSupplicantBroadcast = true; 222 break; 223 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: 224 StateChangeResult stateChangeResult = (StateChangeResult) message.obj; 225 SupplicantState state = stateChangeResult.state; 226 sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast); 227 mAuthFailureInSupplicantBroadcast = false; 228 transitionOnSupplicantStateChange(stateChangeResult); 229 break; 230 case WifiStateMachine.CMD_RESET_SUPPLICANT_STATE: 231 transitionTo(mUninitializedState); 232 break; 233 case WifiManager.CONNECT_NETWORK: 234 mNetworksDisabledDuringConnect = true; 235 break; 236 case WifiMonitor.ASSOCIATION_REJECTION_EVENT: 237 default: 238 Log.e(TAG, "Ignoring " + message); 239 break; 240 } 241 return HANDLED; 242 } 243 } 244 245 /* 246 * This indicates that the supplicant state as seen 247 * by the framework is not initialized yet. We are 248 * in this state right after establishing a control 249 * channel connection before any supplicant events 250 * or after we have lost the control channel 251 * connection to the supplicant 252 */ 253 class UninitializedState extends State { 254 @Override 255 public void enter() { 256 if (DBG) Log.d(TAG, getName() + "\n"); 257 } 258 } 259 260 class InactiveState extends State { 261 @Override 262 public void enter() { 263 if (DBG) Log.d(TAG, getName() + "\n"); 264 } 265 } 266 267 class DisconnectedState extends State { 268 @Override 269 public void enter() { 270 if (DBG) Log.d(TAG, getName() + "\n"); 271 } 272 } 273 274 class ScanState extends State { 275 @Override 276 public void enter() { 277 if (DBG) Log.d(TAG, getName() + "\n"); 278 } 279 } 280 281 class HandshakeState extends State { 282 /** 283 * The max number of the WPA supplicant loop iterations before we 284 * decide that the loop should be terminated: 285 */ 286 private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4; 287 private int mLoopDetectIndex; 288 private int mLoopDetectCount; 289 290 @Override 291 public void enter() { 292 if (DBG) Log.d(TAG, getName() + "\n"); 293 mLoopDetectIndex = 0; 294 mLoopDetectCount = 0; 295 } 296 @Override 297 public boolean processMessage(Message message) { 298 if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); 299 switch (message.what) { 300 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: 301 StateChangeResult stateChangeResult = (StateChangeResult) message.obj; 302 SupplicantState state = stateChangeResult.state; 303 if (SupplicantState.isHandshakeState(state)) { 304 if (mLoopDetectIndex > state.ordinal()) { 305 mLoopDetectCount++; 306 } 307 if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) { 308 Log.d(TAG, "Supplicant loop detected, disabling network " + 309 stateChangeResult.networkId); 310 handleNetworkConnectionFailure(stateChangeResult.networkId, 311 WifiConfiguration.NetworkSelectionStatus 312 .DISABLED_AUTHENTICATION_FAILURE); 313 } 314 mLoopDetectIndex = state.ordinal(); 315 sendSupplicantStateChangedBroadcast(state, 316 mAuthFailureInSupplicantBroadcast); 317 } else { 318 //Have the DefaultState handle the transition 319 return NOT_HANDLED; 320 } 321 break; 322 default: 323 return NOT_HANDLED; 324 } 325 return HANDLED; 326 } 327 } 328 329 class CompletedState extends State { 330 @Override 331 public void enter() { 332 if (DBG) Log.d(TAG, getName() + "\n"); 333 /* Reset authentication failure count */ 334 if (mNetworksDisabledDuringConnect) { 335 mNetworksDisabledDuringConnect = false; 336 } 337 } 338 @Override 339 public boolean processMessage(Message message) { 340 if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); 341 switch(message.what) { 342 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: 343 StateChangeResult stateChangeResult = (StateChangeResult) message.obj; 344 SupplicantState state = stateChangeResult.state; 345 sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast); 346 /* Ignore any connecting state in completed state. Group re-keying 347 * events and other auth events that do not affect connectivity are 348 * ignored 349 */ 350 if (SupplicantState.isConnecting(state)) { 351 break; 352 } 353 transitionOnSupplicantStateChange(stateChangeResult); 354 break; 355 case WifiStateMachine.CMD_RESET_SUPPLICANT_STATE: 356 sendSupplicantStateChangedBroadcast(SupplicantState.DISCONNECTED, false); 357 transitionTo(mUninitializedState); 358 break; 359 default: 360 return NOT_HANDLED; 361 } 362 return HANDLED; 363 } 364 } 365 366 //TODO: remove after getting rid of the state in supplicant 367 class DormantState extends State { 368 @Override 369 public void enter() { 370 if (DBG) Log.d(TAG, getName() + "\n"); 371 } 372 } 373 374 @Override 375 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 376 super.dump(fd, pw, args); 377 pw.println("mAuthFailureInSupplicantBroadcast " + mAuthFailureInSupplicantBroadcast); 378 pw.println("mNetworksDisabledDuringConnect " + mNetworksDisabledDuringConnect); 379 pw.println(); 380 } 381} 382