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