SupplicantStateTracker.java revision 99f90f5cb7637ece0358003d3d3355036e6d68f7
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.UserHandle;
30import android.util.Log;
31import android.util.Slog;
32
33import com.android.internal.app.IBatteryStats;
34import com.android.internal.util.State;
35import com.android.internal.util.StateMachine;
36
37import java.io.FileDescriptor;
38import 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 */
46public 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    /* 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 mConnectionActiveState = new ConnectionActiveState();
75    private final State mHandshakeState = new HandshakeState();
76    private final State mCompletedState = new CompletedState();
77    private final State mDormantState = new DormantState();
78
79    void enableVerboseLogging(int verbose) {
80        if (verbose > 0) {
81            DBG = true;
82        } else {
83            DBG = false;
84        }
85    }
86
87    public String getSupplicantStateName() {
88        return getCurrentState().getName();
89    }
90
91    public SupplicantStateTracker(Context c, WifiConfigManager wcs,
92            FrameworkFacade facade, Handler t) {
93        super(TAG, t.getLooper());
94
95        mContext = c;
96        mWifiConfigManager = wcs;
97        mFacade = facade;
98        mBatteryStats = mFacade.getBatteryService();
99        // CHECKSTYLE:OFF IndentationCheck
100        addState(mDefaultState);
101            addState(mUninitializedState, mDefaultState);
102            addState(mInactiveState, mDefaultState);
103            addState(mDisconnectState, mDefaultState);
104            addState(mConnectionActiveState, mDefaultState);
105                addState(mScanState, mConnectionActiveState);
106                addState(mHandshakeState, mConnectionActiveState);
107                addState(mCompletedState, mConnectionActiveState);
108                addState(mDormantState, mConnectionActiveState);
109        // CHECKSTYLE:ON IndentationCheck
110
111        setInitialState(mUninitializedState);
112        setLogRecSize(50);
113        setLogOnlyTransitions(true);
114        //start the state machine
115        start();
116    }
117
118    private void handleNetworkConnectionFailure(int netId, int disableReason) {
119        if (DBG) {
120            Log.d(TAG, "handleNetworkConnectionFailure netId=" + Integer.toString(netId)
121                    + " reason " + Integer.toString(disableReason)
122                    + " mNetworksDisabledDuringConnect=" + mNetworksDisabledDuringConnect);
123        }
124
125        /* If other networks disabled during connection, enable them */
126        if (mNetworksDisabledDuringConnect) {
127            mNetworksDisabledDuringConnect = false; }
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         }
278    }
279
280    class ScanState extends State {
281        @Override
282         public void enter() {
283             if (DBG) Log.d(TAG, getName() + "\n");
284         }
285    }
286
287    /* Meta-state that processes supplicant disconnections and broadcasts this event. */
288    class ConnectionActiveState extends State {
289        @Override
290        public boolean processMessage(Message message) {
291            if (message.what == WifiStateMachine.CMD_RESET_SUPPLICANT_STATE) {
292                sendSupplicantStateChangedBroadcast(SupplicantState.DISCONNECTED, false);
293            }
294
295            /* Let parent states handle the state possible transition. */
296            return NOT_HANDLED;
297        }
298    }
299
300    class HandshakeState extends State {
301        /**
302         * The max number of the WPA supplicant loop iterations before we
303         * decide that the loop should be terminated:
304         */
305        private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4;
306        private int mLoopDetectIndex;
307        private int mLoopDetectCount;
308
309        @Override
310         public void enter() {
311             if (DBG) Log.d(TAG, getName() + "\n");
312             mLoopDetectIndex = 0;
313             mLoopDetectCount = 0;
314         }
315        @Override
316        public boolean processMessage(Message message) {
317            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
318            switch (message.what) {
319                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
320                    StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
321                    SupplicantState state = stateChangeResult.state;
322                    if (SupplicantState.isHandshakeState(state)) {
323                        if (mLoopDetectIndex > state.ordinal()) {
324                            mLoopDetectCount++;
325                        }
326                        if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) {
327                            Log.d(TAG, "Supplicant loop detected, disabling network " +
328                                    stateChangeResult.networkId);
329                            handleNetworkConnectionFailure(stateChangeResult.networkId,
330                                    WifiConfiguration.NetworkSelectionStatus
331                                            .DISABLED_AUTHENTICATION_FAILURE);
332                        }
333                        mLoopDetectIndex = state.ordinal();
334                        sendSupplicantStateChangedBroadcast(state,
335                                mAuthFailureInSupplicantBroadcast);
336                    } else {
337                        //Have the DefaultState handle the transition
338                        return NOT_HANDLED;
339                    }
340                    break;
341                default:
342                    return NOT_HANDLED;
343            }
344            return HANDLED;
345        }
346    }
347
348    class CompletedState extends State {
349        @Override
350         public void enter() {
351             if (DBG) Log.d(TAG, getName() + "\n");
352             /* Reset authentication failure count */
353             if (mNetworksDisabledDuringConnect) {
354                 mNetworksDisabledDuringConnect = false;
355             }
356        }
357        @Override
358        public boolean processMessage(Message message) {
359            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
360            switch(message.what) {
361                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
362                    StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
363                    SupplicantState state = stateChangeResult.state;
364                    sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast);
365                    /* Ignore any connecting state in completed state. Group re-keying
366                     * events and other auth events that do not affect connectivity are
367                     * ignored
368                     */
369                    if (SupplicantState.isConnecting(state)) {
370                        break;
371                    }
372                    transitionOnSupplicantStateChange(stateChangeResult);
373                    break;
374                default:
375                    return NOT_HANDLED;
376            }
377            return HANDLED;
378        }
379    }
380
381    //TODO: remove after getting rid of the state in supplicant
382    class DormantState extends State {
383        @Override
384        public void enter() {
385            if (DBG) Log.d(TAG, getName() + "\n");
386        }
387    }
388
389    @Override
390    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
391        super.dump(fd, pw, args);
392        pw.println("mAuthFailureInSupplicantBroadcast " + mAuthFailureInSupplicantBroadcast);
393        pw.println("mNetworksDisabledDuringConnect " + mNetworksDisabledDuringConnect);
394        pw.println();
395    }
396}
397