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 static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY; 20 21 import android.annotation.NonNull; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.net.wifi.SupplicantState; 25 import android.net.wifi.WifiConfiguration; 26 import android.net.wifi.WifiManager; 27 import android.os.BatteryStatsManager; 28 import android.os.Looper; 29 import android.os.Message; 30 import android.os.Parcelable; 31 import android.os.UserHandle; 32 import android.util.Log; 33 34 import com.android.internal.util.State; 35 import com.android.internal.util.StateMachine; 36 37 import java.io.FileDescriptor; 38 import java.io.PrintWriter; 39 40 /** 41 * Tracks the state changes in supplicant and provides functionality 42 * that is based on these state changes: 43 * - detect a failed WPA handshake that loops indefinitely 44 * - authentication failure handling 45 * TODO(b/159944009): Need to rework this class to handle make before break transition on STA + 46 * STA devices (Apps will not see SUPPLICANT_STATE_CHANGED_ACTION when switching wifi networks) 47 */ 48 public class SupplicantStateTracker extends StateMachine { 49 50 private static final String TAG = "SupplicantStateTracker"; 51 52 private final Context mContext; 53 private final WifiConfigManager mWifiConfigManager; 54 private final WifiMonitor mWifiMonitor; 55 private final BatteryStatsManager mBatteryStatsManager; 56 private final String mInterfaceName; 57 private final ClientModeManager mClientModeManager; 58 private final ClientModeManagerBroadcastQueue mBroadcastQueue; 59 60 private boolean mVerboseLoggingEnabled = false; 61 /* Indicates authentication failure in supplicant broadcast. 62 * TODO: enhance auth failure reporting to include notification 63 * for all type of failures: EAP, WPS & WPA networks */ 64 private boolean mAuthFailureInSupplicantBroadcast = false; 65 66 /* Authentication failure reason 67 * see {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_NONE}, 68 * {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_TIMEOUT}, 69 * {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_WRONG_PSWD}, 70 * {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_EAP_FAILURE} 71 */ 72 private int mAuthFailureReason; 73 74 private final State mUninitializedState = new UninitializedState(); 75 private final State mDefaultState = new DefaultState(); 76 private final State mInactiveState = new InactiveState(); 77 private final State mDisconnectState = new DisconnectedState(); 78 private final State mScanState = new ScanState(); 79 private final State mHandshakeState = new HandshakeState(); 80 private final State mCompletedState = new CompletedState(); 81 private final State mDormantState = new DormantState(); 82 83 /** enable/disable verbose logging. */ enableVerboseLogging(boolean verbose)84 public void enableVerboseLogging(boolean verbose) { 85 mVerboseLoggingEnabled = verbose; 86 } 87 getTag()88 private String getTag() { 89 return TAG + "[" + (mInterfaceName == null ? "unknown" : mInterfaceName) + "]"; 90 } 91 SupplicantStateTracker( @onNull Context context, @NonNull WifiConfigManager wifiConfigManager, @NonNull BatteryStatsManager batteryStatsManager, @NonNull Looper looper, @NonNull WifiMonitor wifiMonitor, @NonNull String interfaceName, @NonNull ClientModeManager clientModeManager, @NonNull ClientModeManagerBroadcastQueue broadcastQueue)92 public SupplicantStateTracker( 93 @NonNull Context context, 94 @NonNull WifiConfigManager wifiConfigManager, 95 @NonNull BatteryStatsManager batteryStatsManager, 96 @NonNull Looper looper, 97 @NonNull WifiMonitor wifiMonitor, 98 @NonNull String interfaceName, 99 @NonNull ClientModeManager clientModeManager, 100 @NonNull ClientModeManagerBroadcastQueue broadcastQueue) { 101 super(TAG, looper); 102 mContext = context; 103 mWifiConfigManager = wifiConfigManager; 104 mBatteryStatsManager = batteryStatsManager; 105 mWifiMonitor = wifiMonitor; 106 mInterfaceName = interfaceName; 107 mClientModeManager = clientModeManager; 108 mBroadcastQueue = broadcastQueue; 109 110 registerForWifiMonitorEvents(); 111 112 addState(mDefaultState); { 113 // disconnected states 114 addState(mUninitializedState, mDefaultState); 115 addState(mInactiveState, mDefaultState); 116 addState(mDisconnectState, mDefaultState); 117 // connected states 118 addState(mScanState, mDefaultState); 119 addState(mHandshakeState, mDefaultState); 120 addState(mCompletedState, mDefaultState); 121 addState(mDormantState, mDefaultState); 122 } 123 124 setInitialState(mUninitializedState); 125 setLogRecSize(50); 126 setLogOnlyTransitions(true); 127 // start the state machine 128 start(); 129 } 130 131 private static final int[] WIFI_MONITOR_EVENTS = { 132 WifiMonitor.ASSOCIATION_REJECTION_EVENT, 133 WifiMonitor.AUTHENTICATION_FAILURE_EVENT, 134 WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 135 }; 136 registerForWifiMonitorEvents()137 private void registerForWifiMonitorEvents() { 138 for (int event : WIFI_MONITOR_EVENTS) { 139 mWifiMonitor.registerHandler(mInterfaceName, event, getHandler()); 140 } 141 } 142 deregisterForWifiMonitorEvents()143 private void deregisterForWifiMonitorEvents() { 144 for (int event : WIFI_MONITOR_EVENTS) { 145 mWifiMonitor.deregisterHandler(mInterfaceName, event, getHandler()); 146 } 147 } 148 149 /** 150 * Called when the owner ClientModeImpl is stopped. No more actions shall be performed on this 151 * instance after it is stopped. 152 */ stop()153 public void stop() { 154 deregisterForWifiMonitorEvents(); 155 156 quitNow(); 157 } 158 handleNetworkConnectionFailure(int netId, int disableReason)159 private void handleNetworkConnectionFailure(int netId, int disableReason) { 160 if (mVerboseLoggingEnabled) { 161 Log.d(getTag(), "handleNetworkConnectionFailure netId=" + netId 162 + " reason " + disableReason); 163 } 164 165 /* update network status */ 166 mWifiConfigManager.updateNetworkSelectionStatus(netId, disableReason); 167 } 168 transitionOnSupplicantStateChange(StateChangeResult stateChangeResult)169 private void transitionOnSupplicantStateChange(StateChangeResult stateChangeResult) { 170 SupplicantState supState = stateChangeResult.state; 171 172 if (mVerboseLoggingEnabled) { 173 Log.d(getTag(), "Supplicant state: " + supState.toString() + "\n"); 174 } 175 176 switch (supState) { 177 case DISCONNECTED: 178 transitionTo(mDisconnectState); 179 break; 180 case INTERFACE_DISABLED: 181 //we should have received a disconnection already, do nothing 182 break; 183 case SCANNING: 184 transitionTo(mScanState); 185 break; 186 case AUTHENTICATING: 187 case ASSOCIATING: 188 case ASSOCIATED: 189 case FOUR_WAY_HANDSHAKE: 190 case GROUP_HANDSHAKE: 191 transitionTo(mHandshakeState); 192 break; 193 case COMPLETED: 194 transitionTo(mCompletedState); 195 break; 196 case DORMANT: 197 transitionTo(mDormantState); 198 break; 199 case INACTIVE: 200 transitionTo(mInactiveState); 201 break; 202 case UNINITIALIZED: 203 case INVALID: 204 transitionTo(mUninitializedState); 205 break; 206 default: 207 Log.e(getTag(), "Unknown supplicant state " + supState); 208 break; 209 } 210 } 211 supplicantStateToBatteryStatsSupplicantState(SupplicantState state)212 private static int supplicantStateToBatteryStatsSupplicantState(SupplicantState state) { 213 switch (state) { 214 case DISCONNECTED: 215 return BatteryStatsManager.WIFI_SUPPL_STATE_DISCONNECTED; 216 case INTERFACE_DISABLED: 217 return BatteryStatsManager.WIFI_SUPPL_STATE_INTERFACE_DISABLED; 218 case INACTIVE: 219 return BatteryStatsManager.WIFI_SUPPL_STATE_INACTIVE; 220 case SCANNING: 221 return BatteryStatsManager.WIFI_SUPPL_STATE_SCANNING; 222 case AUTHENTICATING: 223 return BatteryStatsManager.WIFI_SUPPL_STATE_AUTHENTICATING; 224 case ASSOCIATING: 225 return BatteryStatsManager.WIFI_SUPPL_STATE_ASSOCIATING; 226 case ASSOCIATED: 227 return BatteryStatsManager.WIFI_SUPPL_STATE_ASSOCIATED; 228 case FOUR_WAY_HANDSHAKE: 229 return BatteryStatsManager.WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE; 230 case GROUP_HANDSHAKE: 231 return BatteryStatsManager.WIFI_SUPPL_STATE_GROUP_HANDSHAKE; 232 case COMPLETED: 233 return BatteryStatsManager.WIFI_SUPPL_STATE_COMPLETED; 234 case DORMANT: 235 return BatteryStatsManager.WIFI_SUPPL_STATE_DORMANT; 236 case UNINITIALIZED: 237 return BatteryStatsManager.WIFI_SUPPL_STATE_UNINITIALIZED; 238 case INVALID: 239 return BatteryStatsManager.WIFI_SUPPL_STATE_INVALID; 240 default: 241 Log.w(TAG, "Unknown supplicant state " + state); 242 return BatteryStatsManager.WIFI_SUPPL_STATE_INVALID; 243 } 244 } 245 sendSupplicantStateChangedBroadcast( SupplicantState state, boolean failedAuth, int reasonCode)246 private void sendSupplicantStateChangedBroadcast( 247 SupplicantState state, boolean failedAuth, int reasonCode) { 248 int supplState = supplicantStateToBatteryStatsSupplicantState(state); 249 if (mClientModeManager.getRole() == ROLE_CLIENT_PRIMARY) { 250 mBatteryStatsManager.reportWifiSupplicantStateChanged(supplState, failedAuth); 251 } 252 String summary = "broadcast=SUPPLICANT_STATE_CHANGED_ACTION" 253 + " state=" + state 254 + " failedAuth=" + failedAuth 255 + " reasonCode=" + reasonCode; 256 if (mVerboseLoggingEnabled) Log.d(TAG, "Queuing " + summary); 257 mBroadcastQueue.queueOrSendBroadcast( 258 mClientModeManager, 259 () -> { 260 if (mVerboseLoggingEnabled) Log.d(TAG, "Sending " + summary); 261 sendSupplicantStateChangedBroadcast(mContext, state, failedAuth, reasonCode); 262 }); 263 } 264 sendSupplicantStateChangedBroadcast( Context context, SupplicantState state, boolean failedAuth, int reasonCode)265 private static void sendSupplicantStateChangedBroadcast( 266 Context context, SupplicantState state, boolean failedAuth, int reasonCode) { 267 Intent intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); 268 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT 269 | Intent.FLAG_RECEIVER_REPLACE_PENDING); 270 intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable) state); 271 if (failedAuth) { 272 intent.putExtra( 273 WifiManager.EXTRA_SUPPLICANT_ERROR, 274 WifiManager.ERROR_AUTHENTICATING); 275 intent.putExtra( 276 WifiManager.EXTRA_SUPPLICANT_ERROR_REASON, 277 reasonCode); 278 } 279 context.sendStickyBroadcastAsUser(intent, UserHandle.ALL); 280 } 281 282 /******************************************************** 283 * HSM states 284 *******************************************************/ 285 286 class DefaultState extends State { 287 @Override enter()288 public void enter() { 289 if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + "\n"); 290 } 291 @Override processMessage(Message message)292 public boolean processMessage(Message message) { 293 if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + message.toString() + "\n"); 294 switch (message.what) { 295 case WifiMonitor.AUTHENTICATION_FAILURE_EVENT: 296 mAuthFailureInSupplicantBroadcast = true; 297 mAuthFailureReason = message.arg1; 298 break; 299 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: 300 StateChangeResult stateChangeResult = (StateChangeResult) message.obj; 301 SupplicantState state = stateChangeResult.state; 302 sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast, 303 mAuthFailureReason); 304 mAuthFailureInSupplicantBroadcast = false; 305 mAuthFailureReason = WifiManager.ERROR_AUTH_FAILURE_NONE; 306 transitionOnSupplicantStateChange(stateChangeResult); 307 break; 308 case WifiMonitor.ASSOCIATION_REJECTION_EVENT: 309 default: 310 Log.e(getTag(), "Ignoring " + message); 311 break; 312 } 313 return HANDLED; 314 } 315 } 316 317 /* 318 * This indicates that the supplicant state as seen 319 * by the framework is not initialized yet. We are 320 * in this state right after establishing a control 321 * channel connection before any supplicant events 322 * or after we have lost the control channel 323 * connection to the supplicant 324 */ 325 class UninitializedState extends State { 326 @Override enter()327 public void enter() { 328 if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + "\n"); 329 } 330 } 331 332 class InactiveState extends State { 333 @Override enter()334 public void enter() { 335 if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + "\n"); 336 } 337 } 338 339 class DisconnectedState extends State { 340 @Override enter()341 public void enter() { 342 if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + "\n"); 343 } 344 } 345 346 class ScanState extends State { 347 @Override enter()348 public void enter() { 349 if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + "\n"); 350 } 351 } 352 353 class HandshakeState extends State { 354 /** 355 * The max number of the WPA supplicant loop iterations before we 356 * decide that the loop should be terminated: 357 */ 358 private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4; 359 private int mLoopDetectIndex; 360 private int mLoopDetectCount; 361 362 @Override enter()363 public void enter() { 364 if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + "\n"); 365 mLoopDetectIndex = 0; 366 mLoopDetectCount = 0; 367 } 368 @Override processMessage(Message message)369 public boolean processMessage(Message message) { 370 if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + message.toString() + "\n"); 371 switch (message.what) { 372 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: 373 StateChangeResult stateChangeResult = (StateChangeResult) message.obj; 374 SupplicantState state = stateChangeResult.state; 375 if (SupplicantState.isHandshakeState(state)) { 376 if (mLoopDetectIndex > state.ordinal()) { 377 mLoopDetectCount++; 378 } 379 if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) { 380 Log.d(getTag(), "Supplicant loop detected, disabling network " 381 + stateChangeResult.networkId); 382 handleNetworkConnectionFailure(stateChangeResult.networkId, 383 WifiConfiguration.NetworkSelectionStatus 384 .DISABLED_AUTHENTICATION_FAILURE); 385 } 386 mLoopDetectIndex = state.ordinal(); 387 sendSupplicantStateChangedBroadcast(state, 388 mAuthFailureInSupplicantBroadcast, mAuthFailureReason); 389 } else { 390 //Have the DefaultState handle the transition 391 return NOT_HANDLED; 392 } 393 break; 394 default: 395 return NOT_HANDLED; 396 } 397 return HANDLED; 398 } 399 } 400 401 class CompletedState extends State { 402 @Override enter()403 public void enter() { 404 if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + "\n"); 405 } 406 @Override processMessage(Message message)407 public boolean processMessage(Message message) { 408 if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + message.toString() + "\n"); 409 switch (message.what) { 410 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: 411 StateChangeResult stateChangeResult = (StateChangeResult) message.obj; 412 SupplicantState state = stateChangeResult.state; 413 sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast, 414 mAuthFailureReason); 415 /* Ignore any connecting state in completed state. Group re-keying 416 * events and other auth events that do not affect connectivity are 417 * ignored 418 */ 419 if (SupplicantState.isConnecting(state)) { 420 break; 421 } 422 transitionOnSupplicantStateChange(stateChangeResult); 423 break; 424 default: 425 return NOT_HANDLED; 426 } 427 return HANDLED; 428 } 429 } 430 431 //TODO: remove after getting rid of the state in supplicant 432 class DormantState extends State { 433 @Override enter()434 public void enter() { 435 if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + "\n"); 436 } 437 } 438 439 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)440 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 441 super.dump(fd, pw, args); 442 pw.println("mAuthFailureInSupplicantBroadcast " + mAuthFailureInSupplicantBroadcast); 443 pw.println("mAuthFailureReason " + mAuthFailureReason); 444 pw.println(); 445 } 446 } 447