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