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 17package android.net; 18 19import com.android.internal.util.Protocol; 20import com.android.internal.util.State; 21import com.android.internal.util.StateMachine; 22 23import android.app.AlarmManager; 24import android.app.PendingIntent; 25import android.content.BroadcastReceiver; 26import android.content.Context; 27import android.content.Intent; 28import android.content.IntentFilter; 29import android.net.DhcpInfoInternal; 30import android.net.NetworkUtils; 31import android.os.Message; 32import android.os.PowerManager; 33import android.os.SystemClock; 34import 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 */ 50public 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 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 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 */ 162 public void registerForPreDhcpNotification() { 163 mRegisteredForPreDhcpNotification = true; 164 } 165 166 class DefaultState extends State { 167 @Override 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 190 public void enter() { 191 if (DBG) Log.d(TAG, getName() + "\n"); 192 } 193 194 @Override 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 223 public void enter() { 224 if (DBG) Log.d(TAG, getName() + "\n"); 225 } 226 227 @Override 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 255 public void enter() { 256 if (DBG) Log.d(TAG, getName() + "\n"); 257 } 258 259 @Override 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 296 public void enter() { 297 if (DBG) Log.d(TAG, getName() + "\n"); 298 } 299 300 @Override 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 329 public void exit() { 330 mDhcpRenewWakeLock.release(); 331 } 332 } 333 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