SupplicantStateTracker.java revision 99f90f5cb7637ece0358003d3d3355036e6d68f7
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.UserHandle; 30import android.util.Log; 31import android.util.Slog; 32 33import com.android.internal.app.IBatteryStats; 34import com.android.internal.util.State; 35import com.android.internal.util.StateMachine; 36 37import java.io.FileDescriptor; 38import 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 */ 46public 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 /* 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 mConnectionActiveState = new ConnectionActiveState(); 75 private final State mHandshakeState = new HandshakeState(); 76 private final State mCompletedState = new CompletedState(); 77 private final State mDormantState = new DormantState(); 78 79 void enableVerboseLogging(int verbose) { 80 if (verbose > 0) { 81 DBG = true; 82 } else { 83 DBG = false; 84 } 85 } 86 87 public String getSupplicantStateName() { 88 return getCurrentState().getName(); 89 } 90 91 public SupplicantStateTracker(Context c, WifiConfigManager wcs, 92 FrameworkFacade facade, Handler t) { 93 super(TAG, t.getLooper()); 94 95 mContext = c; 96 mWifiConfigManager = wcs; 97 mFacade = facade; 98 mBatteryStats = mFacade.getBatteryService(); 99 // CHECKSTYLE:OFF IndentationCheck 100 addState(mDefaultState); 101 addState(mUninitializedState, mDefaultState); 102 addState(mInactiveState, mDefaultState); 103 addState(mDisconnectState, mDefaultState); 104 addState(mConnectionActiveState, mDefaultState); 105 addState(mScanState, mConnectionActiveState); 106 addState(mHandshakeState, mConnectionActiveState); 107 addState(mCompletedState, mConnectionActiveState); 108 addState(mDormantState, mConnectionActiveState); 109 // CHECKSTYLE:ON IndentationCheck 110 111 setInitialState(mUninitializedState); 112 setLogRecSize(50); 113 setLogOnlyTransitions(true); 114 //start the state machine 115 start(); 116 } 117 118 private void handleNetworkConnectionFailure(int netId, int disableReason) { 119 if (DBG) { 120 Log.d(TAG, "handleNetworkConnectionFailure netId=" + Integer.toString(netId) 121 + " reason " + Integer.toString(disableReason) 122 + " mNetworksDisabledDuringConnect=" + mNetworksDisabledDuringConnect); 123 } 124 125 /* If other networks disabled during connection, enable them */ 126 if (mNetworksDisabledDuringConnect) { 127 mNetworksDisabledDuringConnect = false; } 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 } 278 } 279 280 class ScanState extends State { 281 @Override 282 public void enter() { 283 if (DBG) Log.d(TAG, getName() + "\n"); 284 } 285 } 286 287 /* Meta-state that processes supplicant disconnections and broadcasts this event. */ 288 class ConnectionActiveState extends State { 289 @Override 290 public boolean processMessage(Message message) { 291 if (message.what == WifiStateMachine.CMD_RESET_SUPPLICANT_STATE) { 292 sendSupplicantStateChangedBroadcast(SupplicantState.DISCONNECTED, false); 293 } 294 295 /* Let parent states handle the state possible transition. */ 296 return NOT_HANDLED; 297 } 298 } 299 300 class HandshakeState extends State { 301 /** 302 * The max number of the WPA supplicant loop iterations before we 303 * decide that the loop should be terminated: 304 */ 305 private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4; 306 private int mLoopDetectIndex; 307 private int mLoopDetectCount; 308 309 @Override 310 public void enter() { 311 if (DBG) Log.d(TAG, getName() + "\n"); 312 mLoopDetectIndex = 0; 313 mLoopDetectCount = 0; 314 } 315 @Override 316 public boolean processMessage(Message message) { 317 if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); 318 switch (message.what) { 319 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: 320 StateChangeResult stateChangeResult = (StateChangeResult) message.obj; 321 SupplicantState state = stateChangeResult.state; 322 if (SupplicantState.isHandshakeState(state)) { 323 if (mLoopDetectIndex > state.ordinal()) { 324 mLoopDetectCount++; 325 } 326 if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) { 327 Log.d(TAG, "Supplicant loop detected, disabling network " + 328 stateChangeResult.networkId); 329 handleNetworkConnectionFailure(stateChangeResult.networkId, 330 WifiConfiguration.NetworkSelectionStatus 331 .DISABLED_AUTHENTICATION_FAILURE); 332 } 333 mLoopDetectIndex = state.ordinal(); 334 sendSupplicantStateChangedBroadcast(state, 335 mAuthFailureInSupplicantBroadcast); 336 } else { 337 //Have the DefaultState handle the transition 338 return NOT_HANDLED; 339 } 340 break; 341 default: 342 return NOT_HANDLED; 343 } 344 return HANDLED; 345 } 346 } 347 348 class CompletedState extends State { 349 @Override 350 public void enter() { 351 if (DBG) Log.d(TAG, getName() + "\n"); 352 /* Reset authentication failure count */ 353 if (mNetworksDisabledDuringConnect) { 354 mNetworksDisabledDuringConnect = false; 355 } 356 } 357 @Override 358 public boolean processMessage(Message message) { 359 if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); 360 switch(message.what) { 361 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: 362 StateChangeResult stateChangeResult = (StateChangeResult) message.obj; 363 SupplicantState state = stateChangeResult.state; 364 sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast); 365 /* Ignore any connecting state in completed state. Group re-keying 366 * events and other auth events that do not affect connectivity are 367 * ignored 368 */ 369 if (SupplicantState.isConnecting(state)) { 370 break; 371 } 372 transitionOnSupplicantStateChange(stateChangeResult); 373 break; 374 default: 375 return NOT_HANDLED; 376 } 377 return HANDLED; 378 } 379 } 380 381 //TODO: remove after getting rid of the state in supplicant 382 class DormantState extends State { 383 @Override 384 public void enter() { 385 if (DBG) Log.d(TAG, getName() + "\n"); 386 } 387 } 388 389 @Override 390 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 391 super.dump(fd, pw, args); 392 pw.println("mAuthFailureInSupplicantBroadcast " + mAuthFailureInSupplicantBroadcast); 393 pw.println("mNetworksDisabledDuringConnect " + mNetworksDisabledDuringConnect); 394 pw.println(); 395 } 396} 397