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.UserHandle;
30 import android.util.Log;
31 import android.util.Slog;
32 
33 import com.android.internal.app.IBatteryStats;
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  */
46 public class SupplicantStateTracker extends StateMachine {
47 
48     private static final String TAG = "SupplicantStateTracker";
49     private static boolean DBG = false;
50     private final WifiConfigManager mWifiConfigManager;
51     private FrameworkFacade mFacade;
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     /* Authentication failure reason
59      * see {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_NONE},
60      *     {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_TIMEOUT},
61      *     {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_WRONG_PSWD},
62      *     {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_EAP_FAILURE}
63      */
64     private int mAuthFailureReason;
65 
66     /* Maximum retries on a authentication failure notification */
67     private static final int MAX_RETRIES_ON_AUTHENTICATION_FAILURE = 2;
68 
69     /* Maximum retries on assoc rejection events */
70     private static final int MAX_RETRIES_ON_ASSOCIATION_REJECT = 16;
71 
72     /* Tracks if networks have been disabled during a connection */
73     private boolean mNetworksDisabledDuringConnect = false;
74 
75     private final Context mContext;
76 
77     private final State mUninitializedState = new UninitializedState();
78     private final State mDefaultState = new DefaultState();
79     private final State mInactiveState = new InactiveState();
80     private final State mDisconnectState = new DisconnectedState();
81     private final State mScanState = new ScanState();
82     private final State mConnectionActiveState = new ConnectionActiveState();
83     private final State mHandshakeState = new HandshakeState();
84     private final State mCompletedState = new CompletedState();
85     private final State mDormantState = new DormantState();
86 
enableVerboseLogging(int verbose)87     void enableVerboseLogging(int verbose) {
88         if (verbose > 0) {
89             DBG = true;
90         } else {
91             DBG = false;
92         }
93     }
94 
getSupplicantStateName()95     public String getSupplicantStateName() {
96         return getCurrentState().getName();
97     }
98 
SupplicantStateTracker(Context c, WifiConfigManager wcs, FrameworkFacade facade, Handler t)99     public SupplicantStateTracker(Context c, WifiConfigManager wcs,
100             FrameworkFacade facade, Handler t) {
101         super(TAG, t.getLooper());
102 
103         mContext = c;
104         mWifiConfigManager = wcs;
105         mFacade = facade;
106         mBatteryStats = mFacade.getBatteryService();
107         // CHECKSTYLE:OFF IndentationCheck
108         addState(mDefaultState);
109             addState(mUninitializedState, mDefaultState);
110             addState(mInactiveState, mDefaultState);
111             addState(mDisconnectState, mDefaultState);
112             addState(mConnectionActiveState, mDefaultState);
113                 addState(mScanState, mConnectionActiveState);
114                 addState(mHandshakeState, mConnectionActiveState);
115                 addState(mCompletedState, mConnectionActiveState);
116                 addState(mDormantState, mConnectionActiveState);
117         // CHECKSTYLE:ON IndentationCheck
118 
119         setInitialState(mUninitializedState);
120         setLogRecSize(50);
121         setLogOnlyTransitions(true);
122         //start the state machine
123         start();
124     }
125 
handleNetworkConnectionFailure(int netId, int disableReason)126     private void handleNetworkConnectionFailure(int netId, int disableReason) {
127         if (DBG) {
128             Log.d(TAG, "handleNetworkConnectionFailure netId=" + Integer.toString(netId)
129                     + " reason " + Integer.toString(disableReason)
130                     + " mNetworksDisabledDuringConnect=" + mNetworksDisabledDuringConnect);
131         }
132 
133         /* If other networks disabled during connection, enable them */
134         if (mNetworksDisabledDuringConnect) {
135             mNetworksDisabledDuringConnect = false; }
136         /* update network status */
137         mWifiConfigManager.updateNetworkSelectionStatus(netId, disableReason);
138     }
139 
transitionOnSupplicantStateChange(StateChangeResult stateChangeResult)140     private void transitionOnSupplicantStateChange(StateChangeResult stateChangeResult) {
141         SupplicantState supState = (SupplicantState) stateChangeResult.state;
142 
143         if (DBG) Log.d(TAG, "Supplicant state: " + supState.toString() + "\n");
144 
145         switch (supState) {
146            case DISCONNECTED:
147                 transitionTo(mDisconnectState);
148                 break;
149             case INTERFACE_DISABLED:
150                 //we should have received a disconnection already, do nothing
151                 break;
152             case SCANNING:
153                 transitionTo(mScanState);
154                 break;
155             case AUTHENTICATING:
156             case ASSOCIATING:
157             case ASSOCIATED:
158             case FOUR_WAY_HANDSHAKE:
159             case GROUP_HANDSHAKE:
160                 transitionTo(mHandshakeState);
161                 break;
162             case COMPLETED:
163                 transitionTo(mCompletedState);
164                 break;
165             case DORMANT:
166                 transitionTo(mDormantState);
167                 break;
168             case INACTIVE:
169                 transitionTo(mInactiveState);
170                 break;
171             case UNINITIALIZED:
172             case INVALID:
173                 transitionTo(mUninitializedState);
174                 break;
175             default:
176                 Log.e(TAG, "Unknown supplicant state " + supState);
177                 break;
178         }
179     }
180 
sendSupplicantStateChangedBroadcast(SupplicantState state, boolean failedAuth)181     private void sendSupplicantStateChangedBroadcast(SupplicantState state, boolean failedAuth) {
182         sendSupplicantStateChangedBroadcast(state, failedAuth, WifiManager.ERROR_AUTH_FAILURE_NONE);
183     }
184 
sendSupplicantStateChangedBroadcast(SupplicantState state, boolean failedAuth, int reasonCode)185     private void sendSupplicantStateChangedBroadcast(SupplicantState state, boolean failedAuth,
186             int reasonCode) {
187         int supplState;
188         switch (state) {
189             case DISCONNECTED: supplState = BatteryStats.WIFI_SUPPL_STATE_DISCONNECTED; break;
190             case INTERFACE_DISABLED:
191                 supplState = BatteryStats.WIFI_SUPPL_STATE_INTERFACE_DISABLED; break;
192             case INACTIVE: supplState = BatteryStats.WIFI_SUPPL_STATE_INACTIVE; break;
193             case SCANNING: supplState = BatteryStats.WIFI_SUPPL_STATE_SCANNING; break;
194             case AUTHENTICATING: supplState = BatteryStats.WIFI_SUPPL_STATE_AUTHENTICATING; break;
195             case ASSOCIATING: supplState = BatteryStats.WIFI_SUPPL_STATE_ASSOCIATING; break;
196             case ASSOCIATED: supplState = BatteryStats.WIFI_SUPPL_STATE_ASSOCIATED; break;
197             case FOUR_WAY_HANDSHAKE:
198                 supplState = BatteryStats.WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE; break;
199             case GROUP_HANDSHAKE: supplState = BatteryStats.WIFI_SUPPL_STATE_GROUP_HANDSHAKE; break;
200             case COMPLETED: supplState = BatteryStats.WIFI_SUPPL_STATE_COMPLETED; break;
201             case DORMANT: supplState = BatteryStats.WIFI_SUPPL_STATE_DORMANT; break;
202             case UNINITIALIZED: supplState = BatteryStats.WIFI_SUPPL_STATE_UNINITIALIZED; break;
203             case INVALID: supplState = BatteryStats.WIFI_SUPPL_STATE_INVALID; break;
204             default:
205                 Slog.w(TAG, "Unknown supplicant state " + state);
206                 supplState = BatteryStats.WIFI_SUPPL_STATE_INVALID;
207                 break;
208         }
209         try {
210             mBatteryStats.noteWifiSupplicantStateChanged(supplState, failedAuth);
211         } catch (RemoteException e) {
212             // Won't happen.
213         }
214         Intent intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
215         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
216                 | Intent.FLAG_RECEIVER_REPLACE_PENDING);
217         intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable) state);
218         if (failedAuth) {
219             intent.putExtra(
220                     WifiManager.EXTRA_SUPPLICANT_ERROR,
221                     WifiManager.ERROR_AUTHENTICATING);
222             intent.putExtra(
223                     WifiManager.EXTRA_SUPPLICANT_ERROR_REASON,
224                     reasonCode);
225         }
226         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
227     }
228 
229     /********************************************************
230      * HSM states
231      *******************************************************/
232 
233     class DefaultState extends State {
234         @Override
enter()235          public void enter() {
236              if (DBG) Log.d(TAG, getName() + "\n");
237          }
238         @Override
processMessage(Message message)239         public boolean processMessage(Message message) {
240             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
241             switch (message.what) {
242                 case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
243                     mAuthFailureInSupplicantBroadcast = true;
244                     mAuthFailureReason = message.arg1;
245                     break;
246                 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
247                     StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
248                     SupplicantState state = stateChangeResult.state;
249                     sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast,
250                             mAuthFailureReason);
251                     mAuthFailureInSupplicantBroadcast = false;
252                     mAuthFailureReason = WifiManager.ERROR_AUTH_FAILURE_NONE;
253                     transitionOnSupplicantStateChange(stateChangeResult);
254                     break;
255                 case ClientModeImpl.CMD_RESET_SUPPLICANT_STATE:
256                     transitionTo(mUninitializedState);
257                     break;
258                 case WifiManager.CONNECT_NETWORK:
259                     mNetworksDisabledDuringConnect = true;
260                     break;
261                 case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
262                 default:
263                     Log.e(TAG, "Ignoring " + message);
264                     break;
265             }
266             return HANDLED;
267         }
268     }
269 
270     /*
271      * This indicates that the supplicant state as seen
272      * by the framework is not initialized yet. We are
273      * in this state right after establishing a control
274      * channel connection before any supplicant events
275      * or after we have lost the control channel
276      * connection to the supplicant
277      */
278     class UninitializedState extends State {
279         @Override
enter()280          public void enter() {
281              if (DBG) Log.d(TAG, getName() + "\n");
282          }
283     }
284 
285     class InactiveState extends State {
286         @Override
enter()287          public void enter() {
288              if (DBG) Log.d(TAG, getName() + "\n");
289          }
290     }
291 
292     class DisconnectedState extends State {
293         @Override
enter()294          public void enter() {
295              if (DBG) Log.d(TAG, getName() + "\n");
296          }
297     }
298 
299     class ScanState extends State {
300         @Override
enter()301          public void enter() {
302              if (DBG) Log.d(TAG, getName() + "\n");
303          }
304     }
305 
306     /* Meta-state that processes supplicant disconnections and broadcasts this event. */
307     class ConnectionActiveState extends State {
308         @Override
processMessage(Message message)309         public boolean processMessage(Message message) {
310             if (message.what == ClientModeImpl.CMD_RESET_SUPPLICANT_STATE) {
311                 sendSupplicantStateChangedBroadcast(SupplicantState.DISCONNECTED, false);
312             }
313 
314             /* Let parent states handle the state possible transition. */
315             return NOT_HANDLED;
316         }
317     }
318 
319     class HandshakeState extends State {
320         /**
321          * The max number of the WPA supplicant loop iterations before we
322          * decide that the loop should be terminated:
323          */
324         private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4;
325         private int mLoopDetectIndex;
326         private int mLoopDetectCount;
327 
328         @Override
enter()329          public void enter() {
330              if (DBG) Log.d(TAG, getName() + "\n");
331              mLoopDetectIndex = 0;
332              mLoopDetectCount = 0;
333          }
334         @Override
processMessage(Message message)335         public boolean processMessage(Message message) {
336             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
337             switch (message.what) {
338                 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
339                     StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
340                     SupplicantState state = stateChangeResult.state;
341                     if (SupplicantState.isHandshakeState(state)) {
342                         if (mLoopDetectIndex > state.ordinal()) {
343                             mLoopDetectCount++;
344                         }
345                         if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) {
346                             Log.d(TAG, "Supplicant loop detected, disabling network " +
347                                     stateChangeResult.networkId);
348                             handleNetworkConnectionFailure(stateChangeResult.networkId,
349                                     WifiConfiguration.NetworkSelectionStatus
350                                             .DISABLED_AUTHENTICATION_FAILURE);
351                         }
352                         mLoopDetectIndex = state.ordinal();
353                         sendSupplicantStateChangedBroadcast(state,
354                                 mAuthFailureInSupplicantBroadcast, mAuthFailureReason);
355                     } else {
356                         //Have the DefaultState handle the transition
357                         return NOT_HANDLED;
358                     }
359                     break;
360                 default:
361                     return NOT_HANDLED;
362             }
363             return HANDLED;
364         }
365     }
366 
367     class CompletedState extends State {
368         @Override
enter()369          public void enter() {
370              if (DBG) Log.d(TAG, getName() + "\n");
371              /* Reset authentication failure count */
372              if (mNetworksDisabledDuringConnect) {
373                  mNetworksDisabledDuringConnect = false;
374              }
375         }
376         @Override
processMessage(Message message)377         public boolean processMessage(Message message) {
378             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
379             switch(message.what) {
380                 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
381                     StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
382                     SupplicantState state = stateChangeResult.state;
383                     sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast,
384                             mAuthFailureReason);
385                     /* Ignore any connecting state in completed state. Group re-keying
386                      * events and other auth events that do not affect connectivity are
387                      * ignored
388                      */
389                     if (SupplicantState.isConnecting(state)) {
390                         break;
391                     }
392                     transitionOnSupplicantStateChange(stateChangeResult);
393                     break;
394                 default:
395                     return NOT_HANDLED;
396             }
397             return HANDLED;
398         }
399     }
400 
401     //TODO: remove after getting rid of the state in supplicant
402     class DormantState extends State {
403         @Override
enter()404         public void enter() {
405             if (DBG) Log.d(TAG, getName() + "\n");
406         }
407     }
408 
409     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)410     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
411         super.dump(fd, pw, args);
412         pw.println("mAuthFailureInSupplicantBroadcast " + mAuthFailureInSupplicantBroadcast);
413         pw.println("mAuthFailureReason " + mAuthFailureReason);
414         pw.println("mNetworksDisabledDuringConnect " + mNetworksDisabledDuringConnect);
415         pw.println();
416     }
417 }
418