• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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 android.net;
18 
19 import com.android.internal.util.Protocol;
20 import com.android.internal.util.State;
21 import com.android.internal.util.StateMachine;
22 
23 import android.app.AlarmManager;
24 import android.app.PendingIntent;
25 import android.content.BroadcastReceiver;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.net.DhcpInfoInternal;
30 import android.net.NetworkUtils;
31 import android.os.Message;
32 import android.os.PowerManager;
33 import android.os.SystemClock;
34 import android.util.Log;
35 
36 /**
37  * StateMachine that interacts with the native DHCP client and can talk to
38  * a controller that also needs to be a StateMachine
39  *
40  * The Dhcp state machine provides the following features:
41  * - Wakeup and renewal using the native DHCP client  (which will not renew
42  *   on its own when the device is in suspend state and this can lead to device
43  *   holding IP address beyond expiry)
44  * - A notification right before DHCP request or renewal is started. This
45  *   can be used for any additional setup before DHCP. For example, wifi sets
46  *   BT-Wifi coex settings right before DHCP is initiated
47  *
48  * @hide
49  */
50 public class DhcpStateMachine extends StateMachine {
51 
52     private static final String TAG = "DhcpStateMachine";
53     private static final boolean DBG = false;
54 
55 
56     /* A StateMachine that controls the DhcpStateMachine */
57     private StateMachine mController;
58 
59     private Context mContext;
60     private BroadcastReceiver mBroadcastReceiver;
61     private AlarmManager mAlarmManager;
62     private PendingIntent mDhcpRenewalIntent;
63     private PowerManager.WakeLock mDhcpRenewWakeLock;
64     private static final String WAKELOCK_TAG = "DHCP";
65 
66     //Remember DHCP configuration from first request
67     private DhcpInfoInternal mDhcpInfo;
68 
69     private static final int DHCP_RENEW = 0;
70     private static final String ACTION_DHCP_RENEW = "android.net.wifi.DHCP_RENEW";
71 
72     //Used for sanity check on setting up renewal
73     private static final int MIN_RENEWAL_TIME_SECS = 5 * 60;  // 5 minutes
74 
75     private enum DhcpAction {
76         START,
77         RENEW
78     };
79 
80     private String mInterfaceName;
81     private boolean mRegisteredForPreDhcpNotification = false;
82 
83     private static final int BASE = Protocol.BASE_DHCP;
84 
85     /* Commands from controller to start/stop DHCP */
86     public static final int CMD_START_DHCP                  = BASE + 1;
87     public static final int CMD_STOP_DHCP                   = BASE + 2;
88     public static final int CMD_RENEW_DHCP                  = BASE + 3;
89 
90     /* Notification from DHCP state machine prior to DHCP discovery/renewal */
91     public static final int CMD_PRE_DHCP_ACTION             = BASE + 4;
92     /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates
93      * success/failure */
94     public static final int CMD_POST_DHCP_ACTION            = BASE + 5;
95 
96     /* Command from controller to indicate DHCP discovery/renewal can continue
97      * after pre DHCP action is complete */
98     public static final int CMD_PRE_DHCP_ACTION_COMPLETE    = BASE + 6;
99 
100     /* Message.arg1 arguments to CMD_POST_DHCP notification */
101     public static final int DHCP_SUCCESS = 1;
102     public static final int DHCP_FAILURE = 2;
103 
104     private State mDefaultState = new DefaultState();
105     private State mStoppedState = new StoppedState();
106     private State mWaitBeforeStartState = new WaitBeforeStartState();
107     private State mRunningState = new RunningState();
108     private State mWaitBeforeRenewalState = new WaitBeforeRenewalState();
109 
DhcpStateMachine(Context context, StateMachine controller, String intf)110     private DhcpStateMachine(Context context, StateMachine controller, String intf) {
111         super(TAG);
112 
113         mContext = context;
114         mController = controller;
115         mInterfaceName = intf;
116 
117         mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
118         Intent dhcpRenewalIntent = new Intent(ACTION_DHCP_RENEW, null);
119         mDhcpRenewalIntent = PendingIntent.getBroadcast(mContext, DHCP_RENEW, dhcpRenewalIntent, 0);
120 
121         PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
122         mDhcpRenewWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
123         mDhcpRenewWakeLock.setReferenceCounted(false);
124 
125         mBroadcastReceiver = new BroadcastReceiver() {
126             @Override
127             public void onReceive(Context context, Intent intent) {
128                 //DHCP renew
129                 if (DBG) Log.d(TAG, "Sending a DHCP renewal " + this);
130                 //Lock released after 40s in worst case scenario
131                 mDhcpRenewWakeLock.acquire(40000);
132                 sendMessage(CMD_RENEW_DHCP);
133             }
134         };
135         mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_DHCP_RENEW));
136 
137         addState(mDefaultState);
138             addState(mStoppedState, mDefaultState);
139             addState(mWaitBeforeStartState, mDefaultState);
140             addState(mRunningState, mDefaultState);
141             addState(mWaitBeforeRenewalState, mDefaultState);
142 
143         setInitialState(mStoppedState);
144     }
145 
makeDhcpStateMachine(Context context, StateMachine controller, String intf)146     public static DhcpStateMachine makeDhcpStateMachine(Context context, StateMachine controller,
147             String intf) {
148         DhcpStateMachine dsm = new DhcpStateMachine(context, controller, intf);
149         dsm.start();
150         return dsm;
151     }
152 
153     /**
154      * This sends a notification right before DHCP request/renewal so that the
155      * controller can do certain actions before DHCP packets are sent out.
156      * When the controller is ready, it sends a CMD_PRE_DHCP_ACTION_COMPLETE message
157      * to indicate DHCP can continue
158      *
159      * This is used by Wifi at this time for the purpose of doing BT-Wifi coex
160      * handling during Dhcp
161      */
registerForPreDhcpNotification()162     public void registerForPreDhcpNotification() {
163         mRegisteredForPreDhcpNotification = true;
164     }
165 
166     class DefaultState extends State {
167         @Override
processMessage(Message message)168         public boolean processMessage(Message message) {
169             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
170             switch (message.what) {
171                 case CMD_RENEW_DHCP:
172                     Log.e(TAG, "Error! Failed to handle a DHCP renewal on " + mInterfaceName);
173                     mDhcpRenewWakeLock.release();
174                     break;
175                 case SM_QUIT_CMD:
176                     mContext.unregisterReceiver(mBroadcastReceiver);
177                     //let parent kill the state machine
178                     return NOT_HANDLED;
179                 default:
180                     Log.e(TAG, "Error! unhandled message  " + message);
181                     break;
182             }
183             return HANDLED;
184         }
185     }
186 
187 
188     class StoppedState extends State {
189         @Override
enter()190         public void enter() {
191             if (DBG) Log.d(TAG, getName() + "\n");
192         }
193 
194         @Override
processMessage(Message message)195         public boolean processMessage(Message message) {
196             boolean retValue = HANDLED;
197             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
198             switch (message.what) {
199                 case CMD_START_DHCP:
200                     if (mRegisteredForPreDhcpNotification) {
201                         /* Notify controller before starting DHCP */
202                         mController.sendMessage(CMD_PRE_DHCP_ACTION);
203                         transitionTo(mWaitBeforeStartState);
204                     } else {
205                         if (runDhcp(DhcpAction.START)) {
206                             transitionTo(mRunningState);
207                         }
208                     }
209                     break;
210                 case CMD_STOP_DHCP:
211                     //ignore
212                     break;
213                 default:
214                     retValue = NOT_HANDLED;
215                     break;
216             }
217             return retValue;
218         }
219     }
220 
221     class WaitBeforeStartState extends State {
222         @Override
enter()223         public void enter() {
224             if (DBG) Log.d(TAG, getName() + "\n");
225         }
226 
227         @Override
processMessage(Message message)228         public boolean processMessage(Message message) {
229             boolean retValue = HANDLED;
230             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
231             switch (message.what) {
232                 case CMD_PRE_DHCP_ACTION_COMPLETE:
233                     if (runDhcp(DhcpAction.START)) {
234                         transitionTo(mRunningState);
235                     } else {
236                         transitionTo(mStoppedState);
237                     }
238                     break;
239                 case CMD_STOP_DHCP:
240                     transitionTo(mStoppedState);
241                     break;
242                 case CMD_START_DHCP:
243                     //ignore
244                     break;
245                 default:
246                     retValue = NOT_HANDLED;
247                     break;
248             }
249             return retValue;
250         }
251     }
252 
253     class RunningState extends State {
254         @Override
enter()255         public void enter() {
256             if (DBG) Log.d(TAG, getName() + "\n");
257         }
258 
259         @Override
processMessage(Message message)260         public boolean processMessage(Message message) {
261             boolean retValue = HANDLED;
262             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
263             switch (message.what) {
264                 case CMD_STOP_DHCP:
265                     mAlarmManager.cancel(mDhcpRenewalIntent);
266                     if (!NetworkUtils.stopDhcp(mInterfaceName)) {
267                         Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
268                     }
269                     transitionTo(mStoppedState);
270                     break;
271                 case CMD_RENEW_DHCP:
272                     if (mRegisteredForPreDhcpNotification) {
273                         /* Notify controller before starting DHCP */
274                         mController.sendMessage(CMD_PRE_DHCP_ACTION);
275                         transitionTo(mWaitBeforeRenewalState);
276                         //mDhcpRenewWakeLock is released in WaitBeforeRenewalState
277                     } else {
278                         if (!runDhcp(DhcpAction.RENEW)) {
279                             transitionTo(mStoppedState);
280                         }
281                         mDhcpRenewWakeLock.release();
282                     }
283                     break;
284                 case CMD_START_DHCP:
285                     //ignore
286                     break;
287                 default:
288                     retValue = NOT_HANDLED;
289             }
290             return retValue;
291         }
292     }
293 
294     class WaitBeforeRenewalState extends State {
295         @Override
enter()296         public void enter() {
297             if (DBG) Log.d(TAG, getName() + "\n");
298         }
299 
300         @Override
processMessage(Message message)301         public boolean processMessage(Message message) {
302             boolean retValue = HANDLED;
303             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
304             switch (message.what) {
305                 case CMD_STOP_DHCP:
306                     mAlarmManager.cancel(mDhcpRenewalIntent);
307                     if (!NetworkUtils.stopDhcp(mInterfaceName)) {
308                         Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
309                     }
310                     transitionTo(mStoppedState);
311                     break;
312                 case CMD_PRE_DHCP_ACTION_COMPLETE:
313                     if (runDhcp(DhcpAction.RENEW)) {
314                        transitionTo(mRunningState);
315                     } else {
316                        transitionTo(mStoppedState);
317                     }
318                     break;
319                 case CMD_START_DHCP:
320                     //ignore
321                     break;
322                 default:
323                     retValue = NOT_HANDLED;
324                     break;
325             }
326             return retValue;
327         }
328         @Override
exit()329         public void exit() {
330             mDhcpRenewWakeLock.release();
331         }
332     }
333 
runDhcp(DhcpAction dhcpAction)334     private boolean runDhcp(DhcpAction dhcpAction) {
335         boolean success = false;
336         DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal();
337 
338         if (dhcpAction == DhcpAction.START) {
339             if (DBG) Log.d(TAG, "DHCP request on " + mInterfaceName);
340             success = NetworkUtils.runDhcp(mInterfaceName, dhcpInfoInternal);
341             mDhcpInfo = dhcpInfoInternal;
342         } else if (dhcpAction == DhcpAction.RENEW) {
343             if (DBG) Log.d(TAG, "DHCP renewal on " + mInterfaceName);
344             success = NetworkUtils.runDhcpRenew(mInterfaceName, dhcpInfoInternal);
345             dhcpInfoInternal.updateFromDhcpRequest(mDhcpInfo);
346         }
347 
348         if (success) {
349             if (DBG) Log.d(TAG, "DHCP succeeded on " + mInterfaceName);
350            long leaseDuration = dhcpInfoInternal.leaseDuration; //int to long conversion
351 
352            //Sanity check for renewal
353            //TODO: would be good to notify the user that his network configuration is
354            //bad and that the device cannot renew below MIN_RENEWAL_TIME_SECS
355            if (leaseDuration < MIN_RENEWAL_TIME_SECS) {
356                leaseDuration = MIN_RENEWAL_TIME_SECS;
357            }
358            //Do it a bit earlier than half the lease duration time
359            //to beat the native DHCP client and avoid extra packets
360            //48% for one hour lease time = 29 minutes
361            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
362                    SystemClock.elapsedRealtime() +
363                    leaseDuration * 480, //in milliseconds
364                    mDhcpRenewalIntent);
365 
366             mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpInfoInternal)
367                 .sendToTarget();
368         } else {
369             Log.e(TAG, "DHCP failed on " + mInterfaceName + ": " +
370                     NetworkUtils.getDhcpError());
371             NetworkUtils.stopDhcp(mInterfaceName);
372             mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0)
373                 .sendToTarget();
374         }
375         return success;
376     }
377 }
378