Only renew/rebind valid prefixes at ReacquireState.
For prefixes that DHCP server cannot extend, client should stop trying
to send renew/rebind message to extend the lifetime for those prefixes,
for example, prefix with preferred/valid lifetime of 0 or prefix with
t1/t2 equals to valid lifetime, which indicates that server won't extend
the lifetime for these prefixes.
Bug: 260934173
Test: atest NetworkStackIntegraionTests
Change-Id: I8cefd3d6e8cd6dd75ef4785bbf280a8beb5083ad
diff --git a/src/android/net/dhcp6/Dhcp6Client.java b/src/android/net/dhcp6/Dhcp6Client.java
index eba5767..291a97a 100644
--- a/src/android/net/dhcp6/Dhcp6Client.java
+++ b/src/android/net/dhcp6/Dhcp6Client.java
@@ -67,6 +67,7 @@
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.util.Collections;
+import java.util.List;
import java.util.Random;
import java.util.function.IntSupplier;
@@ -384,8 +385,9 @@
}
private void scheduleLeaseTimers() {
- // TODO: validate t1, t2, valid and preferred lifetimes before the timers are scheduled to
- // prevent packet storms due to low timeouts.
+ // TODO: validate t1, t2, valid and preferred lifetimes before the timers are scheduled
+ // to prevent packet storms due to low timeouts. Preferred/valid lifetime of 0 should be
+ // excluded before scheduling the lease timer.
int renewTimeout = mReply.t1;
int rebindTimeout = mReply.t2;
final long preferredTimeout = mReply.getMinimalPreferredLifetime();
@@ -785,7 +787,9 @@
@Override
protected boolean sendPacket(int transId, long elapsedTimeMs) {
- return sendRenewPacket(transId, elapsedTimeMs, mReply.build());
+ final List<IaPrefixOption> toBeRenewed = mReply.getRenewableIaPrefixes();
+ if (toBeRenewed.isEmpty()) return false;
+ return sendRenewPacket(transId, elapsedTimeMs, mReply.build(toBeRenewed));
}
}
@@ -801,7 +805,9 @@
@Override
protected boolean sendPacket(int transId, long elapsedTimeMs) {
- return sendRebindPacket(transId, elapsedTimeMs, mReply.build());
+ final List<IaPrefixOption> toBeRebound = mReply.getRenewableIaPrefixes();
+ if (toBeRebound.isEmpty()) return false;
+ return sendRebindPacket(transId, elapsedTimeMs, mReply.build(toBeRebound));
}
}
diff --git a/src/android/net/dhcp6/Dhcp6Packet.java b/src/android/net/dhcp6/Dhcp6Packet.java
index 7a977e5..e1dad15 100644
--- a/src/android/net/dhcp6/Dhcp6Packet.java
+++ b/src/android/net/dhcp6/Dhcp6Packet.java
@@ -278,12 +278,19 @@
* Build an IA_PD option from given specific parameters, including IA_PREFIX options.
*/
public ByteBuffer build() {
+ return build(ipos);
+ }
+
+ /**
+ * Build an IA_PD option from given specific parameters, including IA_PREFIX options.
+ */
+ public ByteBuffer build(@NonNull final List<IaPrefixOption> input) {
final ByteBuffer iapd = ByteBuffer.allocate(IaPdOption.LENGTH
- + Struct.getSize(IaPrefixOption.class) * ipos.size());
+ + Struct.getSize(IaPrefixOption.class) * input.size());
iapd.putInt(iaid);
iapd.putInt(t1);
iapd.putInt(t2);
- for (IaPrefixOption ipo : ipos) {
+ for (IaPrefixOption ipo : input) {
ipo.writeToByteBuffer(iapd);
}
iapd.flip();
@@ -332,6 +339,22 @@
(IaPrefixOption lhs, IaPrefixOption rhs) -> Long.compare(lhs.valid, rhs.valid));
return ipo.valid;
}
+
+ /**
+ * Return IA prefix option list to be renewed/rebound.
+ *
+ * Per RFC8415#section-18.2.4, client must not include any prefixes that it didn't obtain
+ * from server or that are no longer valid (that have a valid lifetime of 0). Section-18.3.4
+ * also mentions that server can inform client that it will not extend the prefix by setting
+ * T1 and T2 to values equal to the valid lifetime, so in this case client has no point in
+ * renewing as well.
+ */
+ public List<IaPrefixOption> getRenewableIaPrefixes() {
+ final List<IaPrefixOption> toBeRenewed = getValidIaPrefixes();
+ toBeRenewed.removeIf(ipo -> ipo.preferred == 0 && ipo.valid == 0);
+ toBeRenewed.removeIf(ipo -> t1 == ipo.valid && t2 == ipo.valid);
+ return toBeRenewed;
+ }
}
/**
diff --git a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
index 090b83d..40da4b7 100644
--- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
@@ -5220,6 +5220,92 @@
verify(mCb, never()).onLinkPropertiesChange(any());
}
+ @SignatureRequiredTest(reason = "Need to mock the DHCP6 renew/rebind alarms")
+ @Test
+ public void testDhcp6Pd_renewInvalidPrefixes_zeroPreferredAndValidLifetime() throws Exception {
+ prepareDhcp6PdRenewTest();
+
+ final InOrder inOrder = inOrder(mAlarm);
+ final Handler handler = mDependencies.mDhcp6Client.getHandler();
+ final OnAlarmListener renewAlarm = expectAlarmSet(inOrder, "RENEW", 3600, handler);
+
+ handler.post(() -> renewAlarm.onAlarm());
+ HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+ Dhcp6Packet packet = getNextDhcp6Packet();
+ assertTrue(packet instanceof Dhcp6RenewPacket);
+
+ // Reply with the requested prefix with preferred/valid lifetime of 0.
+ final IpPrefix prefix = new IpPrefix("2001:db8:1::/64");
+ final IpPrefix prefix1 = new IpPrefix("2001:db8:2::/64");
+ final IaPrefixOption ipo = buildIaPrefixOption(prefix, 0 /* preferred */,
+ 0 /* valid */);
+ final IaPrefixOption ipo1 = buildIaPrefixOption(prefix1, 5000 /* preferred */,
+ 6000 /* valid */);
+ final PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 3600 /* t1 */,
+ 4500 /* t2 */, Arrays.asList(ipo, ipo1));
+ final ByteBuffer iapd = pd.build();
+ mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac,
+ (Inet6Address) mClientIpAddress, false /* rapidCommit */));
+ verify(mCb, never()).onProvisioningFailure(any());
+ verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(argThat(
+ x -> x.isIpv6Provisioned()
+ && hasIpv6AddressPrefixedWith(x, prefix)
+ && hasIpv6AddressPrefixedWith(x, prefix1)
+ && hasRouteTo(x, "2001:db8:1::/64", RTN_UNREACHABLE)
+ && hasRouteTo(x, "2001:db8:2::/64", RTN_UNREACHABLE)
+ // IPv6 link-local, four global delegated IPv6 addresses
+ && x.getLinkAddresses().size() == 5
+ ));
+
+ handler.post(() -> renewAlarm.onAlarm());
+ HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+ packet = getNextDhcp6Packet();
+ assertTrue(packet instanceof Dhcp6RenewPacket);
+ final List<IaPrefixOption> renewIpos = packet.getPrefixDelegation().ipos;
+ assertEquals(1, renewIpos.size()); // don't renew prefix 2001:db8:1::/64
+ assertEquals(prefix1, renewIpos.get(0).getIpPrefix());
+ }
+
+ @SignatureRequiredTest(reason = "Need to mock the DHCP6 renew/rebind alarms")
+ @Test
+ public void testDhcp6Pd_renewInvalidPrefixes_theSameT1T2ValidLifetime() throws Exception {
+ prepareDhcp6PdRenewTest();
+
+ final InOrder inOrder = inOrder(mAlarm);
+ final Handler handler = mDependencies.mDhcp6Client.getHandler();
+ final OnAlarmListener renewAlarm = expectAlarmSet(inOrder, "RENEW", 3600, handler);
+
+ handler.post(() -> renewAlarm.onAlarm());
+ HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+ Dhcp6Packet packet = getNextDhcp6Packet();
+ assertTrue(packet instanceof Dhcp6RenewPacket);
+
+ clearInvocations(mCb);
+
+ // Reply with the requested prefix with preferred/valid lifetime of 0.
+ final IpPrefix prefix = new IpPrefix("2001:db8:1::/64");
+ final IaPrefixOption ipo = buildIaPrefixOption(prefix, 3600 /* preferred */,
+ 3600 /* valid */);
+ final PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 3600 /* t1 */,
+ 3600 /* t2 */, Collections.singletonList(ipo));
+ final ByteBuffer iapd = pd.build();
+ mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac,
+ (Inet6Address) mClientIpAddress, false /* rapidCommit */));
+ // The prefix doesn't change only the lifetime is updated, therefore, LinkProperties update
+ // isn't expected.
+ verify(mCb, never()).onProvisioningFailure(any());
+ verify(mCb, never()).onLinkPropertiesChange(any());
+
+ handler.post(() -> renewAlarm.onAlarm());
+ HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+ packet = getNextDhcp6Packet(TEST_TIMEOUT_MS);
+ assertNull(packet);
+ }
+
@Test
@SignatureRequiredTest(reason = "InterfaceParams.getByName requires CAP_NET_ADMIN")
public void testSendRtmDelAddressMethod() throws Exception {