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