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