SupplicantStateTracker.java revision 3bc487aa49deecbc358ee819e0dd4b2534412281
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    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
78    void enableVerboseLogging(int verbose) {
79        if (verbose > 0) {
80            DBG = true;
81        } else {
82            DBG = false;
83        }
84    }
85
86    public String getSupplicantStateName() {
87        return getCurrentState().getName();
88    }
89
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
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            mNetworksDisabledDuringConnect = false; }
122        /* update network status */
123        mWifiConfigManager.updateNetworkSelectionStatus(netId, disableReason);
124    }
125
126    private void transitionOnSupplicantStateChange(StateChangeResult stateChangeResult) {
127        SupplicantState supState = (SupplicantState) stateChangeResult.state;
128
129        if (DBG) Log.d(TAG, "Supplicant state: " + supState.toString() + "\n");
130
131        switch (supState) {
132           case DISCONNECTED:
133                transitionTo(mDisconnectState);
134                break;
135            case INTERFACE_DISABLED:
136                //we should have received a disconnection already, do nothing
137                break;
138            case SCANNING:
139                transitionTo(mScanState);
140                break;
141            case AUTHENTICATING:
142            case ASSOCIATING:
143            case ASSOCIATED:
144            case FOUR_WAY_HANDSHAKE:
145            case GROUP_HANDSHAKE:
146                transitionTo(mHandshakeState);
147                break;
148            case COMPLETED:
149                transitionTo(mCompletedState);
150                break;
151            case DORMANT:
152                transitionTo(mDormantState);
153                break;
154            case INACTIVE:
155                transitionTo(mInactiveState);
156                break;
157            case UNINITIALIZED:
158            case INVALID:
159                transitionTo(mUninitializedState);
160                break;
161            default:
162                Log.e(TAG, "Unknown supplicant state " + supState);
163                break;
164        }
165    }
166
167    private void sendSupplicantStateChangedBroadcast(SupplicantState state, boolean failedAuth) {
168        int supplState;
169        switch (state) {
170            case DISCONNECTED: supplState = BatteryStats.WIFI_SUPPL_STATE_DISCONNECTED; break;
171            case INTERFACE_DISABLED:
172                supplState = BatteryStats.WIFI_SUPPL_STATE_INTERFACE_DISABLED; break;
173            case INACTIVE: supplState = BatteryStats.WIFI_SUPPL_STATE_INACTIVE; break;
174            case SCANNING: supplState = BatteryStats.WIFI_SUPPL_STATE_SCANNING; break;
175            case AUTHENTICATING: supplState = BatteryStats.WIFI_SUPPL_STATE_AUTHENTICATING; break;
176            case ASSOCIATING: supplState = BatteryStats.WIFI_SUPPL_STATE_ASSOCIATING; break;
177            case ASSOCIATED: supplState = BatteryStats.WIFI_SUPPL_STATE_ASSOCIATED; break;
178            case FOUR_WAY_HANDSHAKE:
179                supplState = BatteryStats.WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE; break;
180            case GROUP_HANDSHAKE: supplState = BatteryStats.WIFI_SUPPL_STATE_GROUP_HANDSHAKE; break;
181            case COMPLETED: supplState = BatteryStats.WIFI_SUPPL_STATE_COMPLETED; break;
182            case DORMANT: supplState = BatteryStats.WIFI_SUPPL_STATE_DORMANT; break;
183            case UNINITIALIZED: supplState = BatteryStats.WIFI_SUPPL_STATE_UNINITIALIZED; break;
184            case INVALID: supplState = BatteryStats.WIFI_SUPPL_STATE_INVALID; break;
185            default:
186                Slog.w(TAG, "Unknown supplicant state " + state);
187                supplState = BatteryStats.WIFI_SUPPL_STATE_INVALID;
188                break;
189        }
190        try {
191            mBatteryStats.noteWifiSupplicantStateChanged(supplState, failedAuth);
192        } catch (RemoteException e) {
193            // Won't happen.
194        }
195        Intent intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
196        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
197                | Intent.FLAG_RECEIVER_REPLACE_PENDING);
198        intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable) state);
199        if (failedAuth) {
200            intent.putExtra(
201                WifiManager.EXTRA_SUPPLICANT_ERROR,
202                WifiManager.ERROR_AUTHENTICATING);
203        }
204        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
205    }
206
207    /********************************************************
208     * HSM states
209     *******************************************************/
210
211    class DefaultState extends State {
212        @Override
213         public void enter() {
214             if (DBG) Log.d(TAG, getName() + "\n");
215         }
216        @Override
217        public boolean processMessage(Message message) {
218            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
219            switch (message.what) {
220                case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
221                    mAuthFailureInSupplicantBroadcast = true;
222                    break;
223                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
224                    StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
225                    SupplicantState state = stateChangeResult.state;
226                    sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast);
227                    mAuthFailureInSupplicantBroadcast = false;
228                    transitionOnSupplicantStateChange(stateChangeResult);
229                    break;
230                case WifiStateMachine.CMD_RESET_SUPPLICANT_STATE:
231                    transitionTo(mUninitializedState);
232                    break;
233                case WifiManager.CONNECT_NETWORK:
234                    mNetworksDisabledDuringConnect = true;
235                    break;
236                case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
237                default:
238                    Log.e(TAG, "Ignoring " + message);
239                    break;
240            }
241            return HANDLED;
242        }
243    }
244
245    /*
246     * This indicates that the supplicant state as seen
247     * by the framework is not initialized yet. We are
248     * in this state right after establishing a control
249     * channel connection before any supplicant events
250     * or after we have lost the control channel
251     * connection to the supplicant
252     */
253    class UninitializedState extends State {
254        @Override
255         public void enter() {
256             if (DBG) Log.d(TAG, getName() + "\n");
257         }
258    }
259
260    class InactiveState extends State {
261        @Override
262         public void enter() {
263             if (DBG) Log.d(TAG, getName() + "\n");
264         }
265    }
266
267    class DisconnectedState extends State {
268        @Override
269         public void enter() {
270             if (DBG) Log.d(TAG, getName() + "\n");
271         }
272    }
273
274    class ScanState extends State {
275        @Override
276         public void enter() {
277             if (DBG) Log.d(TAG, getName() + "\n");
278         }
279    }
280
281    class HandshakeState extends State {
282        /**
283         * The max number of the WPA supplicant loop iterations before we
284         * decide that the loop should be terminated:
285         */
286        private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4;
287        private int mLoopDetectIndex;
288        private int mLoopDetectCount;
289
290        @Override
291         public void enter() {
292             if (DBG) Log.d(TAG, getName() + "\n");
293             mLoopDetectIndex = 0;
294             mLoopDetectCount = 0;
295         }
296        @Override
297        public boolean processMessage(Message message) {
298            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
299            switch (message.what) {
300                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
301                    StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
302                    SupplicantState state = stateChangeResult.state;
303                    if (SupplicantState.isHandshakeState(state)) {
304                        if (mLoopDetectIndex > state.ordinal()) {
305                            mLoopDetectCount++;
306                        }
307                        if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) {
308                            Log.d(TAG, "Supplicant loop detected, disabling network " +
309                                    stateChangeResult.networkId);
310                            handleNetworkConnectionFailure(stateChangeResult.networkId,
311                                    WifiConfiguration.NetworkSelectionStatus
312                                            .DISABLED_AUTHENTICATION_FAILURE);
313                        }
314                        mLoopDetectIndex = state.ordinal();
315                        sendSupplicantStateChangedBroadcast(state,
316                                mAuthFailureInSupplicantBroadcast);
317                    } else {
318                        //Have the DefaultState handle the transition
319                        return NOT_HANDLED;
320                    }
321                    break;
322                default:
323                    return NOT_HANDLED;
324            }
325            return HANDLED;
326        }
327    }
328
329    class CompletedState extends State {
330        @Override
331         public void enter() {
332             if (DBG) Log.d(TAG, getName() + "\n");
333             /* Reset authentication failure count */
334             if (mNetworksDisabledDuringConnect) {
335                 mNetworksDisabledDuringConnect = false;
336             }
337        }
338        @Override
339        public boolean processMessage(Message message) {
340            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
341            switch(message.what) {
342                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
343                    StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
344                    SupplicantState state = stateChangeResult.state;
345                    sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast);
346                    /* Ignore any connecting state in completed state. Group re-keying
347                     * events and other auth events that do not affect connectivity are
348                     * ignored
349                     */
350                    if (SupplicantState.isConnecting(state)) {
351                        break;
352                    }
353                    transitionOnSupplicantStateChange(stateChangeResult);
354                    break;
355                case WifiStateMachine.CMD_RESET_SUPPLICANT_STATE:
356                    sendSupplicantStateChangedBroadcast(SupplicantState.DISCONNECTED, false);
357                    transitionTo(mUninitializedState);
358                    break;
359                default:
360                    return NOT_HANDLED;
361            }
362            return HANDLED;
363        }
364    }
365
366    //TODO: remove after getting rid of the state in supplicant
367    class DormantState extends State {
368        @Override
369        public void enter() {
370            if (DBG) Log.d(TAG, getName() + "\n");
371        }
372    }
373
374    @Override
375    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
376        super.dump(fd, pw, args);
377        pw.println("mAuthFailureInSupplicantBroadcast " + mAuthFailureInSupplicantBroadcast);
378        pw.println("mNetworksDisabledDuringConnect " + mNetworksDisabledDuringConnect);
379        pw.println();
380    }
381}
382