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