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