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