Merge "Implement slow restransmission schedule in DHCP Renew/Rebind state."
diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java
index c88b653..b41484c 100644
--- a/src/android/net/dhcp/DhcpClient.java
+++ b/src/android/net/dhcp/DhcpClient.java
@@ -35,6 +35,7 @@
import static android.net.util.NetworkStackUtils.DHCP_IPV6_ONLY_PREFERRED_VERSION;
import static android.net.util.NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION;
import static android.net.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION;
+import static android.net.util.NetworkStackUtils.DHCP_SLOW_RETRANSMISSION_VERSION;
import static android.net.util.NetworkStackUtils.closeSocketQuietly;
import static android.net.util.SocketUtils.makePacketSocketAddress;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
@@ -349,6 +350,7 @@
private long mTransactionStartMillis;
private DhcpResults mDhcpLease;
private long mDhcpLeaseExpiry;
+ private long mT2;
private DhcpResults mOffer;
private Configuration mConfiguration;
private Inet4Address mLastAssignedIpv4Address;
@@ -580,6 +582,15 @@
true /* defaultEnabled */);
}
+ /**
+ * Check whether to adopt slow DHCPREQUEST retransmission approach in Renewing/Rebinding state
+ * suggested in RFC2131 section 4.4.5.
+ */
+ public boolean isSlowRetransmissionEnabled() {
+ return mDependencies.isFeatureEnabled(mContext, DHCP_SLOW_RETRANSMISSION_VERSION,
+ false /* defaultEnabled */);
+ }
+
private void recordMetricEnabledFeatures() {
if (isDhcpLeaseCacheEnabled()) mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_INITREBOOT);
if (isDhcpRapidCommitEnabled()) mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_RAPIDCOMMIT);
@@ -806,6 +817,7 @@
final long remainingDelay = mDhcpLeaseExpiry - now;
final long renewDelay = remainingDelay / 2;
final long rebindDelay = remainingDelay * 7 / 8;
+ mT2 = now + rebindDelay;
mRenewAlarm.schedule(now + renewDelay);
mRebindAlarm.schedule(now + rebindDelay);
mExpiryAlarm.schedule(now + remainingDelay);
@@ -880,6 +892,7 @@
private void clearDhcpState() {
mDhcpLease = null;
mDhcpLeaseExpiry = 0;
+ mT2 = 0;
mOffer = null;
}
@@ -1199,7 +1212,7 @@
return baseTimer + jitter;
}
- protected void scheduleKick() {
+ protected void scheduleFastKick() {
long now = SystemClock.elapsedRealtime();
long timeout = jitterTimer(mTimer);
long alarmTime = now + timeout;
@@ -1209,6 +1222,12 @@
mTimer = MAX_TIMEOUT_MS;
}
}
+
+ protected void scheduleKick() {
+ // Always adopt the fast kick schedule by default unless this method is overrided
+ // by subclasses.
+ scheduleFastKick();
+ }
}
class ObtainingConfigurationState extends LoggingState {
@@ -1823,6 +1842,21 @@
// in renew/rebind state or just restart reconfiguration from StoppedState.
protected abstract boolean shouldRestartOnNak();
+ // Schedule alarm for the next DHCPREQUEST tranmission. Per RFC2131 if the client
+ // receives no response to its DHCPREQUEST message, the client should wait one-half
+ // of the remaining time until T2 in RENEWING state, and one-half of the remaining
+ // lease time in REBINDING state, down to a minimum of 60 seconds before transmitting
+ // DHCPREQUEST.
+ private static final long MIN_DELAY_BEFORE_NEXT_REQUEST = 60_000L;
+ protected void scheduleSlowKick(final long target) {
+ final long now = SystemClock.elapsedRealtime();
+ long remainingDelay = (target - now) / 2;
+ if (remainingDelay < MIN_DELAY_BEFORE_NEXT_REQUEST) {
+ remainingDelay = MIN_DELAY_BEFORE_NEXT_REQUEST;
+ }
+ mKickAlarm.schedule(now + remainingDelay);
+ }
+
protected boolean sendPacket() {
return sendRequestPacket(
(Inet4Address) mDhcpLease.ipAddress.getAddress(), // ciaddr
@@ -1895,13 +1929,18 @@
protected boolean shouldRestartOnNak() {
return false;
}
+
+ @Override
+ protected void scheduleKick() {
+ if (isSlowRetransmissionEnabled()) {
+ scheduleSlowKick(mT2);
+ } else {
+ scheduleFastKick();
+ }
+ }
}
- class DhcpRebindingState extends DhcpReacquiringState {
- public DhcpRebindingState() {
- mLeaseMsg = "Rebound";
- }
-
+ class DhcpRebindingBaseState extends DhcpReacquiringState {
@Override
public void enter() {
super.enter();
@@ -1926,7 +1965,29 @@
}
}
- class DhcpRefreshingAddressState extends DhcpRebindingState {
+ class DhcpRebindingState extends DhcpRebindingBaseState {
+ DhcpRebindingState() {
+ mLeaseMsg = "Rebound";
+ }
+
+ @Override
+ protected void scheduleKick() {
+ if (isSlowRetransmissionEnabled()) {
+ scheduleSlowKick(mDhcpLeaseExpiry);
+ } else {
+ scheduleFastKick();
+ }
+ }
+ }
+
+ // The slow retransmission approach complied with RFC2131 should only be applied
+ // for Renewing and Rebinding state. For this state it's expected to refresh IPv4
+ // link address after roam as soon as possible, obviously it should not adopt the
+ // slow retransmission algorithm. Create a base DhcpRebindingBaseState state and
+ // have both of DhcpRebindingState and DhcpRefreshingAddressState extend from it,
+ // then override the scheduleKick method in DhcpRebindingState to comply with slow
+ // schedule and keep DhcpRefreshingAddressState as-is to use the fast schedule.
+ class DhcpRefreshingAddressState extends DhcpRebindingBaseState {
DhcpRefreshingAddressState() {
mLeaseMsg = "Refreshing address";
}
diff --git a/src/android/net/util/NetworkStackUtils.java b/src/android/net/util/NetworkStackUtils.java
index 6dc2a5b..a6ea343 100755
--- a/src/android/net/util/NetworkStackUtils.java
+++ b/src/android/net/util/NetworkStackUtils.java
@@ -211,6 +211,13 @@
"dhcp_ipv6_only_preferred_version";
/**
+ * Minimum module version at which to enable slow DHCP retransmission approach in renew/rebind
+ * state suggested in RFC2131 section 4.4.5.
+ */
+ public static final String DHCP_SLOW_RETRANSMISSION_VERSION =
+ "dhcp_slow_retransmission_version";
+
+ /**
* Minimum module version at which to enable dismissal CaptivePortalLogin app in validated
* network feature. CaptivePortalLogin app will also use validation facilities in
* {@link NetworkMonitor} to perform portal validation if feature is enabled.
diff --git a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
index 5364a00..ff99bc8 100644
--- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
@@ -1143,6 +1143,8 @@
assertNotEquals(0, lp.getDnsServers().size());
assertEquals(addresses.size(), lp.getAddresses().size());
assertTrue(lp.getAddresses().containsAll(addresses));
+ assertTrue(hasRouteTo(lp, IPV4_TEST_SUBNET_PREFIX)); // IPv4 directly-connected route
+ assertTrue(hasRouteTo(lp, IPV4_ANY_ADDRESS_PREFIX)); // IPv4 default route
return lp;
}
@@ -1588,7 +1590,7 @@
assertDhcpRequestForReacquire(packet);
packetList.add(packet);
}
- assertTrue(packetList.size() > 1);
+ assertEquals(1, packetList.size());
}
private LinkProperties prepareDhcpReacquireTest() throws Exception {
@@ -1597,6 +1599,7 @@
mNetworkAgentThread.start();
final long currentTime = System.currentTimeMillis();
+ setFeatureEnabled(NetworkStackUtils.DHCP_SLOW_RETRANSMISSION_VERSION, true);
performDhcpHandshake(true /* isSuccessLease */,
TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */,
false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU,
@@ -1632,21 +1635,20 @@
request.senderIp /* target IP */, SERVER_ADDR /* sender IP */);
HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
- // Verify the multiple unicast DHCPREQUESTs to be received in the renew state per current
- // packet retransmission schedule.
+ // Verify there should be only one unicast DHCPREQUESTs to be received per RFC2131.
assertReceivedDhcpRequestPacketCount();
return rebindAlarm;
}
- @Test @SignatureRequiredTest(reason = "Need to mock the DHCP renew/rebind/kick alarms")
+ @Test @SignatureRequiredTest(reason = "Need to mock the DHCP renew/rebind alarms")
public void testDhcpRenew() throws Exception {
final LinkProperties lp = prepareDhcpReacquireTest();
final InOrder inOrder = inOrder(mAlarm);
runDhcpRenewTest(mDependencies.mDhcpClient.getHandler(), lp, inOrder);
}
- @Test @SignatureRequiredTest(reason = "Need to mock the DHCP renew/rebind/kick alarms")
+ @Test @SignatureRequiredTest(reason = "Need to mock the DHCP renew/rebind alarms")
public void testDhcpRebind() throws Exception {
final LinkProperties lp = prepareDhcpReacquireTest();
final Handler handler = mDependencies.mDhcpClient.getHandler();
@@ -1655,9 +1657,8 @@
// Trigger rebind alarm and forece DHCP client enter RebindingState. DHCP client sends
// broadcast DHCPREQUEST to nearby servers, then check how many DHCPREQUEST packets are
- // retransmitted within PACKET_TIMEOUT_MS(5s), there should be more than one DHCPREQUEST
- // captured per current retransmission schedule(t=0, t=1, t=3, t=7, t=16 and allowing for
- // 10% jitter).
+ // retransmitted within PACKET_TIMEOUT_MS(5s), there should be only one DHCPREQUEST
+ // captured per RFC2131.
handler.post(() -> rebindAlarm.onAlarm());
assertReceivedDhcpRequestPacketCount();
}