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 enableVerboseLogging(int verbose)87 void enableVerboseLogging(int verbose) { 88 if (verbose > 0) { 89 DBG = true; 90 } else { 91 DBG = false; 92 } 93 } 94 getSupplicantStateName()95 public String getSupplicantStateName() { 96 return getCurrentState().getName(); 97 } 98 SupplicantStateTracker(Context c, WifiConfigManager wcs, FrameworkFacade facade, Handler t)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 handleNetworkConnectionFailure(int netId, int disableReason)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 transitionOnSupplicantStateChange(StateChangeResult stateChangeResult)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 sendSupplicantStateChangedBroadcast(SupplicantState state, boolean failedAuth)181 private void sendSupplicantStateChangedBroadcast(SupplicantState state, boolean failedAuth) { 182 sendSupplicantStateChangedBroadcast(state, failedAuth, WifiManager.ERROR_AUTH_FAILURE_NONE); 183 } 184 sendSupplicantStateChangedBroadcast(SupplicantState state, boolean failedAuth, int reasonCode)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 enter()235 public void enter() { 236 if (DBG) Log.d(TAG, getName() + "\n"); 237 } 238 @Override processMessage(Message message)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 ClientModeImpl.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 enter()280 public void enter() { 281 if (DBG) Log.d(TAG, getName() + "\n"); 282 } 283 } 284 285 class InactiveState extends State { 286 @Override enter()287 public void enter() { 288 if (DBG) Log.d(TAG, getName() + "\n"); 289 } 290 } 291 292 class DisconnectedState extends State { 293 @Override enter()294 public void enter() { 295 if (DBG) Log.d(TAG, getName() + "\n"); 296 } 297 } 298 299 class ScanState extends State { 300 @Override enter()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 processMessage(Message message)309 public boolean processMessage(Message message) { 310 if (message.what == ClientModeImpl.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 enter()329 public void enter() { 330 if (DBG) Log.d(TAG, getName() + "\n"); 331 mLoopDetectIndex = 0; 332 mLoopDetectCount = 0; 333 } 334 @Override processMessage(Message message)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 enter()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 processMessage(Message message)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 enter()404 public void enter() { 405 if (DBG) Log.d(TAG, getName() + "\n"); 406 } 407 } 408 409 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)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