IpManager: add a provisioning timeout option
Relatedly: remove the provisioning timeout from DhcpClient.
Bug: 17733693
Bug: 24837343
Change-Id: I6d5b835b4ca70ba6fd06df359fc2128a0df46252
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 2b6c916..d0083ab 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -105,26 +105,25 @@
/* Commands from controller to start/stop DHCP */
public static final int CMD_START_DHCP = PUBLIC_BASE + 1;
public static final int CMD_STOP_DHCP = PUBLIC_BASE + 2;
- public static final int CMD_RENEW_DHCP = PUBLIC_BASE + 3;
/* Notification from DHCP state machine prior to DHCP discovery/renewal */
- public static final int CMD_PRE_DHCP_ACTION = PUBLIC_BASE + 4;
+ public static final int CMD_PRE_DHCP_ACTION = PUBLIC_BASE + 3;
/* Notification from DHCP state machine post DHCP discovery/renewal. Indicates
* success/failure */
- public static final int CMD_POST_DHCP_ACTION = PUBLIC_BASE + 5;
+ public static final int CMD_POST_DHCP_ACTION = PUBLIC_BASE + 4;
/* Notification from DHCP state machine before quitting */
- public static final int CMD_ON_QUIT = PUBLIC_BASE + 6;
+ public static final int CMD_ON_QUIT = PUBLIC_BASE + 5;
/* Command from controller to indicate DHCP discovery/renewal can continue
* after pre DHCP action is complete */
- public static final int CMD_PRE_DHCP_ACTION_COMPLETE = PUBLIC_BASE + 7;
+ public static final int CMD_PRE_DHCP_ACTION_COMPLETE = PUBLIC_BASE + 6;
/* Command and event notification to/from IpManager requesting the setting
* (or clearing) of an IPv4 LinkAddress.
*/
- public static final int CMD_CLEAR_LINKADDRESS = PUBLIC_BASE + 8;
- public static final int CMD_CONFIGURE_LINKADDRESS = PUBLIC_BASE + 9;
- public static final int EVENT_LINKADDRESS_CONFIGURED = PUBLIC_BASE + 10;
+ public static final int CMD_CLEAR_LINKADDRESS = PUBLIC_BASE + 7;
+ public static final int CMD_CONFIGURE_LINKADDRESS = PUBLIC_BASE + 8;
+ public static final int EVENT_LINKADDRESS_CONFIGURED = PUBLIC_BASE + 9;
/* Message.arg1 arguments to CMD_POST_DHCP notification */
public static final int DHCP_SUCCESS = 1;
@@ -135,7 +134,7 @@
private static final int CMD_KICK = PRIVATE_BASE + 1;
private static final int CMD_RECEIVED_PACKET = PRIVATE_BASE + 2;
private static final int CMD_TIMEOUT = PRIVATE_BASE + 3;
- private static final int CMD_ONESHOT_TIMEOUT = PRIVATE_BASE + 4;
+ private static final int CMD_RENEW_DHCP = PRIVATE_BASE + 4;
// For message logging.
private static final Class[] sMessageClasses = { DhcpClient.class };
@@ -177,7 +176,6 @@
private final WakeupMessage mKickAlarm;
private final WakeupMessage mTimeoutAlarm;
private final WakeupMessage mRenewAlarm;
- private final WakeupMessage mOneshotTimeoutAlarm;
private final String mIfaceName;
private boolean mRegisteredForPreDhcpNotification;
@@ -243,10 +241,6 @@
mTimeoutAlarm = makeWakeupMessage("TIMEOUT", CMD_TIMEOUT);
// Used to schedule DHCP renews.
mRenewAlarm = makeWakeupMessage("RENEW", CMD_RENEW_DHCP);
- // Used to tell the caller when its request (CMD_START_DHCP or CMD_RENEW_DHCP) timed out.
- // TODO: when the legacy DHCP client is gone, make the client fully asynchronous and
- // remove this.
- mOneshotTimeoutAlarm = makeWakeupMessage("ONESHOT_TIMEOUT", CMD_ONESHOT_TIMEOUT);
}
public void registerForPreDhcpNotification() {
@@ -506,29 +500,12 @@
}
}
- // The one-shot timeout is used to implement the timeout for CMD_START_DHCP. We can't use a
- // state timeout to do this because obtaining an IP address involves passing through more than
- // one state (specifically, it passes at least once through DhcpInitState and once through
- // DhcpRequestingState). The one-shot timeout is created when CMD_START_DHCP is received, and is
- // cancelled when exiting DhcpState (either due to a CMD_STOP_DHCP, or because of an error), or
- // when we get an IP address (when entering DhcpBoundState). If it fires, we send ourselves
- // CMD_ONESHOT_TIMEOUT and notify the caller that DHCP failed, but we take no other action. For
- // example, if we're in DhcpInitState and sending DISCOVERs, we continue to do so.
- //
- // The one-shot timeout is not used for CMD_RENEW_DHCP because that is implemented using only
- // one state, so we can just use the state timeout.
- private void scheduleOneshotTimeout() {
- final long alarmTime = SystemClock.elapsedRealtime() + DHCP_TIMEOUT_MS;
- mOneshotTimeoutAlarm.schedule(alarmTime);
- }
-
class StoppedState extends LoggingState {
@Override
public boolean processMessage(Message message) {
super.processMessage(message);
switch (message.what) {
case CMD_START_DHCP:
- scheduleOneshotTimeout();
if (mRegisteredForPreDhcpNotification) {
transitionTo(mWaitBeforeStartState);
} else {
@@ -571,7 +548,6 @@
@Override
public void exit() {
- mOneshotTimeoutAlarm.cancel();
if (mReceiveThread != null) {
mReceiveThread.halt(); // Also closes sockets.
mReceiveThread = null;
@@ -586,10 +562,6 @@
case CMD_STOP_DHCP:
transitionTo(mStoppedState);
return HANDLED;
- case CMD_ONESHOT_TIMEOUT:
- if (DBG) Log.d(TAG, "Timed out");
- notifyFailure();
- return HANDLED;
default:
return NOT_HANDLED;
}
@@ -822,7 +794,6 @@
@Override
public void enter() {
super.enter();
- mOneshotTimeoutAlarm.cancel();
notifySuccess();
// TODO: DhcpStateMachine only supported renewing at 50% of the lease time,
// and did not support rebinding. Now that the legacy DHCP client is gone, fix this.
@@ -888,7 +859,7 @@
@Override
protected void timeout() {
transitionTo(mDhcpInitState);
- sendMessage(CMD_ONESHOT_TIMEOUT);
+ notifyFailure();
}
}
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index fd98f46..8ab029c 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -17,6 +17,7 @@
package android.net.ip;
import com.android.internal.util.MessageUtils;
+import com.android.internal.util.WakeupMessage;
import android.content.Context;
import android.net.apf.ApfCapabilities;
@@ -53,6 +54,7 @@
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Objects;
+import java.util.StringJoiner;
import static android.net.metrics.IpConnectivityEvent.IPCE_IPMGR_PROVISIONING_OK;
import static android.net.metrics.IpConnectivityEvent.IPCE_IPMGR_PROVISIONING_FAIL;
@@ -254,6 +256,7 @@
* final ProvisioningConfiguration config =
* mIpManager.buildProvisioningConfiguration()
* .withPreDhcpAction()
+ * .withProvisioningTimeoutMs(36 * 1000)
* .build();
* mIpManager.startProvisioning(config);
* ...
@@ -264,6 +267,15 @@
* must specify the configuration again.
*/
public static class ProvisioningConfiguration {
+ // TODO: Delete this default timeout once those callers that care are
+ // fixed to pass in their preferred timeout.
+ //
+ // We pick 36 seconds so we can send DHCP requests at
+ //
+ // t=0, t=2, t=6, t=14, t=30
+ //
+ // allowing for 10% jitter.
+ private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
public static class Builder {
private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
@@ -288,6 +300,11 @@
return this;
}
+ public Builder withProvisioningTimeoutMs(int timeoutMs) {
+ mConfig.mProvisioningTimeoutMs = timeoutMs;
+ return this;
+ }
+
public ProvisioningConfiguration build() {
return new ProvisioningConfiguration(mConfig);
}
@@ -297,6 +314,7 @@
/* package */ boolean mRequestedPreDhcpAction;
/* package */ StaticIpConfiguration mStaticIpConfig;
/* package */ ApfCapabilities mApfCapabilities;
+ /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
public ProvisioningConfiguration() {}
@@ -305,6 +323,18 @@
mRequestedPreDhcpAction = other.mRequestedPreDhcpAction;
mStaticIpConfig = other.mStaticIpConfig;
mApfCapabilities = other.mApfCapabilities;
+ mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
+ }
+
+ @Override
+ public String toString() {
+ return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
+ .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
+ .add("mRequestedPreDhcpAction: " + mRequestedPreDhcpAction)
+ .add("mStaticIpConfig: " + mStaticIpConfig)
+ .add("mApfCapabilities: " + mApfCapabilities)
+ .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
+ .toString();
}
}
@@ -319,6 +349,7 @@
private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 6;
private static final int CMD_UPDATE_HTTP_PROXY = 7;
private static final int CMD_SET_MULTICAST_FILTER = 8;
+ private static final int EVENT_PROVISIONING_TIMEOUT = 9;
private static final int MAX_LOG_RECORDS = 500;
@@ -341,6 +372,7 @@
protected final Callback mCallback;
private final INetworkManagementService mNwService;
private final NetlinkTracker mNetlinkTracker;
+ private final WakeupMessage mProvisioningTimeoutAlarm;
private final LocalLog mLocalLog;
private NetworkInterface mNetworkInterface;
@@ -415,6 +447,9 @@
resetLinkProperties();
+ mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
+ mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
+
// Super simple StateMachine.
addState(mStoppedState);
addState(mStartedState);
@@ -653,7 +688,6 @@
}
private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) {
- if (mApfFilter != null) mApfFilter.setLinkProperties(newLp);
switch (delta) {
case GAINED_PROVISIONING:
if (VDBG) { Log.d(mTag, "onProvisioningSuccess()"); }
@@ -674,7 +708,13 @@
}
}
+ // Updates all IpManager-related state concerned with LinkProperties.
+ // Returns a ProvisioningChange for possibly notifying other interested
+ // parties that are not fronted by IpManager.
private ProvisioningChange setLinkProperties(LinkProperties newLp) {
+ if (mApfFilter != null) {
+ mApfFilter.setLinkProperties(newLp);
+ }
if (mIpReachabilityMonitor != null) {
mIpReachabilityMonitor.updateLinkProperties(newLp);
}
@@ -682,13 +722,10 @@
ProvisioningChange delta = compareProvisioning(mLinkProperties, newLp);
mLinkProperties = new LinkProperties(newLp);
- if (DBG) {
- switch (delta) {
- case GAINED_PROVISIONING:
- case LOST_PROVISIONING:
- Log.d(mTag, "provisioning: " + delta);
- break;
- }
+ if (delta == ProvisioningChange.GAINED_PROVISIONING) {
+ // TODO: Add a proper ProvisionedState and cancel the alarm in
+ // its enter() method.
+ mProvisioningTimeoutAlarm.cancel();
}
return delta;
@@ -802,33 +839,39 @@
Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
}
mCallback.onNewDhcpResults(dhcpResults);
-
dispatchCallback(delta, newLp);
}
private void handleIPv4Failure() {
+ // TODO: Investigate deleting this clearIPv4Address() call.
+ //
+ // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
+ // that could trigger a call to this function. If we missed handling
+ // that message in StartedState for some reason we would still clear
+ // any addresses upon entry to StoppedState.
clearIPv4Address();
mDhcpResults = null;
+ if (VDBG) { Log.d(mTag, "onNewDhcpResults(null)"); }
+ mCallback.onNewDhcpResults(null);
+
+ handleProvisioningFailure();
+ }
+
+ private void handleProvisioningFailure() {
final LinkProperties newLp = assembleLinkProperties();
ProvisioningChange delta = setLinkProperties(newLp);
// If we've gotten here and we're still not provisioned treat that as
// a total loss of provisioning.
//
// Either (a) static IP configuration failed or (b) DHCPv4 failed AND
- // there was no usable IPv6 obtained before the DHCPv4 timeout.
+ // there was no usable IPv6 obtained before a non-zero provisioning
+ // timeout expired.
//
// Regardless: GAME OVER.
- //
- // TODO: Make the DHCP client not time out and just continue in
- // exponential backoff. Callers such as Wi-Fi which need a timeout
- // should implement it themselves.
if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) {
delta = ProvisioningChange.LOST_PROVISIONING;
}
- if (VDBG) { Log.d(mTag, "onNewDhcpResults(null)"); }
- mCallback.onNewDhcpResults(null);
-
dispatchCallback(delta, newLp);
if (delta == ProvisioningChange.LOST_PROVISIONING) {
transitionTo(mStoppingState);
@@ -972,11 +1015,19 @@
mInterfaceName);
mDhcpClient.registerForPreDhcpNotification();
mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
+
+ if (mConfiguration.mProvisioningTimeoutMs > 0) {
+ final long alarmTime = SystemClock.elapsedRealtime() +
+ mConfiguration.mProvisioningTimeoutMs;
+ mProvisioningTimeoutAlarm.schedule(alarmTime);
+ }
}
}
@Override
public void exit() {
+ mProvisioningTimeoutAlarm.cancel();
+
if (mIpReachabilityMonitor != null) {
mIpReachabilityMonitor.stop();
mIpReachabilityMonitor = null;
@@ -999,7 +1050,7 @@
public boolean processMessage(Message msg) {
switch (msg.what) {
case CMD_STOP:
- transitionTo(mStoppedState);
+ transitionTo(mStoppingState);
break;
case CMD_START:
@@ -1027,7 +1078,7 @@
case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
- transitionTo(mStoppedState);
+ transitionTo(mStoppingState);
}
break;
@@ -1053,6 +1104,10 @@
break;
}
+ case EVENT_PROVISIONING_TIMEOUT:
+ handleProvisioningFailure();
+ break;
+
case DhcpClient.CMD_PRE_DHCP_ACTION:
if (VDBG) { Log.d(mTag, "onPreDhcpAction()"); }
if (mConfiguration.mRequestedPreDhcpAction) {