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