Add Status Code option if needed when building an IA_PD option.
Make PrefixDelegation constructor with statusCode param visible for
testing, which is useful to inject the Status code in the response from
Server, e.g. inject an NoPrefixAvail status code in resposne to check
the client's behavior. And take the Status Code optino in IA_PD unless
it's not Success, otherwise, the absent of Status Code option in IA_PD
indicates a success resposne.
Bug: 317161875
Test: atest NetworkStackIntegrationTests
Change-Id: Ib67135c662e5cdf7ea644742503d4977f5e97fa8
diff --git a/src/android/net/dhcp6/Dhcp6Packet.java b/src/android/net/dhcp6/Dhcp6Packet.java
index f8b812b..640ae2c 100644
--- a/src/android/net/dhcp6/Dhcp6Packet.java
+++ b/src/android/net/dhcp6/Dhcp6Packet.java
@@ -94,6 +94,7 @@
* DHCPv6 Optional Type: Status Code.
*/
public static final byte DHCP6_STATUS_CODE = 13;
+ private static final byte MIN_STATUS_CODE_OPT_LEN = 6;
protected short mStatusCode;
public static final short STATUS_SUCCESS = 0;
@@ -209,6 +210,7 @@
public final List<IaPrefixOption> ipos;
public final short statusCode;
+ @VisibleForTesting
public PrefixDelegation(int iaid, int t1, int t2,
@NonNull final List<IaPrefixOption> ipos, short statusCode) {
Objects.requireNonNull(ipos);
@@ -293,16 +295,26 @@
/**
* Build an IA_PD option from given specific parameters, including IA_PREFIX options.
+ *
+ * Per RFC8415 section 21.13 if the Status Code option does not appear in a message in
+ * which the option could appear, the status of the message is assumed to be Success. So
+ * only put the Status Code option in IA_PD when the status code is not Success.
*/
public ByteBuffer build(@NonNull final List<IaPrefixOption> input) {
final ByteBuffer iapd = ByteBuffer.allocate(IaPdOption.LENGTH
- + Struct.getSize(IaPrefixOption.class) * input.size());
+ + Struct.getSize(IaPrefixOption.class) * input.size()
+ + (statusCode != STATUS_SUCCESS ? MIN_STATUS_CODE_OPT_LEN : 0));
iapd.putInt(iaid);
iapd.putInt(t1);
iapd.putInt(t2);
for (IaPrefixOption ipo : input) {
ipo.writeToByteBuffer(iapd);
}
+ if (statusCode != STATUS_SUCCESS) {
+ iapd.putShort(DHCP6_STATUS_CODE);
+ iapd.putShort((short) 2);
+ iapd.putShort(statusCode);
+ }
iapd.flip();
return iapd;
}
diff --git a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
index 253892b..9cd2535 100644
--- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
@@ -779,6 +779,8 @@
enableRealAlarm("DhcpClient." + mIfaceName + ".KICK");
// Enable alarm for IPv6 autoconf via SLAAC in IpClient.
enableRealAlarm("IpClient." + mIfaceName + ".EVENT_IPV6_AUTOCONF_TIMEOUT");
+ // Enable packet retransmit alarm in Dhcp6Client.
+ enableRealAlarm("Dhcp6Client." + mIfaceName + ".KICK");
}
mIIpClient = makeIIpClient(mIfaceName, mCb);
@@ -5340,6 +5342,41 @@
assertFalse(hasRouteTo(lp, prefix3.toString(), RTN_UNREACHABLE));
}
+ private void runDhcp6PacketWithNoPrefixAvailStatusCodeTest(boolean shouldReplyWithAdvertise)
+ throws Exception {
+ prepareDhcp6PdTest();
+ Dhcp6Packet packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS);
+ assertTrue(packet instanceof Dhcp6SolicitPacket);
+
+ final PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 0 /* t1 */, 0 /* t2 */,
+ new ArrayList<IaPrefixOption>() /* ipos */, Dhcp6Packet.STATUS_NO_PREFIX_AVAI);
+ final ByteBuffer iapd = pd.build();
+ if (shouldReplyWithAdvertise) {
+ mPacketReader.sendResponse(buildDhcp6Advertise(packet, iapd.array(), mClientMac,
+ (Inet6Address) mClientIpAddress));
+ } else {
+ mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac,
+ (Inet6Address) mClientIpAddress, true /* rapidCommit */));
+ }
+
+ // Check if client will ignore Advertise or Reply for Rapid Commit Solicit and
+ // retransmit Solicit.
+ packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS);
+ assertTrue(packet instanceof Dhcp6SolicitPacket);
+ }
+
+ @Test
+ public void testDhcp6AdvertiseWithNoPrefixAvailStatusCode() throws Exception {
+ // Advertise
+ runDhcp6PacketWithNoPrefixAvailStatusCodeTest(true /* shouldReplyWithAdvertise */);
+ }
+
+ @Test
+ public void testDhcp6ReplyForRapidCommitSolicitWithNoPrefixAvailStatusCode() throws Exception {
+ // Reply
+ runDhcp6PacketWithNoPrefixAvailStatusCodeTest(false /* shouldReplyWithAdvertise */);
+ }
+
@Test
@SignatureRequiredTest(reason = "InterfaceParams.getByName requires CAP_NET_ADMIN")
public void testSendRtmDelAddressMethod() throws Exception {