Merge "Add NeighborAdvertisement class to build/prase NA packet." am: d5a8a30a4d am: 3df5486586
Original change: https://android-review.googlesource.com/c/platform/packages/modules/NetworkStack/+/1535653
MUST ONLY BE SUBMITTED BY AUTOMERGER
Change-Id: I6f6283cf7fe1ad11173f33418aaa69697594fda7
diff --git a/proguard.flags b/proguard.flags
index af4262a..13425ce 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -8,6 +8,10 @@
static final int EVENT_*;
}
+-keepclassmembers public class * extends com.android.networkstack.util.Struct {
+ public <init>(...);
+}
+
# The lite proto runtime uses reflection to access fields based on the names in
# the schema, keep all the fields.
# This replicates the base proguard rule used by the build by default
diff --git a/src/com/android/networkstack/packets/NeighborAdvertisement.java b/src/com/android/networkstack/packets/NeighborAdvertisement.java
new file mode 100644
index 0000000..e6cdfc8
--- /dev/null
+++ b/src/com/android/networkstack/packets/NeighborAdvertisement.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.packets;
+
+import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_HEADER_MIN_LEN;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
+
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Ipv6Utils;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Icmpv6Header;
+import com.android.net.module.util.structs.Ipv6Header;
+import com.android.net.module.util.structs.LlaOption;
+import com.android.net.module.util.structs.NaHeader;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+
+/**
+ * Defines basic data and operations needed to build and parse Neighbor Advertisement packet.
+ *
+ * @hide
+ */
+public class NeighborAdvertisement {
+ @NonNull
+ public final EthernetHeader ethHdr;
+ @NonNull
+ public final Ipv6Header ipv6Hdr;
+ @NonNull
+ public final Icmpv6Header icmpv6Hdr;
+ @NonNull
+ public final NaHeader naHdr;
+ @NonNull
+ public final LlaOption tlla;
+
+ public NeighborAdvertisement(@NonNull final EthernetHeader ethHdr,
+ @NonNull final Ipv6Header ipv6Hdr, @NonNull final Icmpv6Header icmpv6Hdr,
+ @NonNull final NaHeader naHdr, @NonNull final LlaOption tlla) {
+ this.ethHdr = ethHdr;
+ this.ipv6Hdr = ipv6Hdr;
+ this.icmpv6Hdr = icmpv6Hdr;
+ this.naHdr = naHdr;
+ this.tlla = tlla;
+ }
+
+ /**
+ * Convert a Neighbor Advertisement instance to ByteBuffer.
+ */
+ public ByteBuffer toByteBuffer() {
+ final int etherHeaderLen = Struct.getSize(EthernetHeader.class);
+ final int ipv6HeaderLen = Struct.getSize(Ipv6Header.class);
+ final int icmpv6HeaderLen = Struct.getSize(Icmpv6Header.class);
+ final int naHeaderLen = Struct.getSize(NaHeader.class);
+ final int tllaOptionLen = Struct.getSize(LlaOption.class);
+ final ByteBuffer packet = ByteBuffer.allocate(etherHeaderLen + ipv6HeaderLen
+ + icmpv6HeaderLen + naHeaderLen + tllaOptionLen);
+
+ ethHdr.writeToByteBuffer(packet);
+ ipv6Hdr.writeToByteBuffer(packet);
+ icmpv6Hdr.writeToByteBuffer(packet);
+ naHdr.writeToByteBuffer(packet);
+ tlla.writeToByteBuffer(packet);
+ packet.flip();
+
+ return packet;
+ }
+
+ /**
+ * Build a Neighbor Advertisement packet from the required specified parameters.
+ */
+ public static ByteBuffer build(@NonNull final MacAddress srcMac,
+ @NonNull final MacAddress dstMac, @NonNull final Inet6Address srcIp,
+ @NonNull final Inet6Address dstIp, int flags, @NonNull final Inet6Address target) {
+ final ByteBuffer tlla = LlaOption.build((byte) ICMPV6_ND_OPTION_TLLA, srcMac);
+ return Ipv6Utils.buildNaPacket(srcMac, dstMac, srcIp, dstIp, flags, target, tlla);
+ }
+
+ /**
+ * Parse a Neighbor Advertisement packet from ByteBuffer.
+ */
+ public static NeighborAdvertisement parse(@NonNull final byte[] recvbuf, final int length)
+ throws ParseException {
+ if (length < ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMPV6_HEADER_MIN_LEN
+ || recvbuf.length < length) {
+ throw new ParseException("Invalid packet length: " + length);
+ }
+ final ByteBuffer packet = ByteBuffer.wrap(recvbuf, 0, length);
+
+ // Parse each header and option in Neighbor Advertisement packet in order.
+ final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, packet);
+ final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, packet);
+ final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, packet);
+ final NaHeader naHdr = Struct.parse(NaHeader.class, packet);
+ final LlaOption tlla = Struct.parse(LlaOption.class, packet);
+
+ return new NeighborAdvertisement(ethHdr, ipv6Hdr, icmpv6Hdr, naHdr, tlla);
+ }
+
+ /**
+ * Thrown when parsing Neighbor Advertisement packet failed.
+ */
+ public static class ParseException extends Exception {
+ ParseException(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/tests/unit/src/com/android/networkstack/packets/NeighborAdvertisementTest.java b/tests/unit/src/com/android/networkstack/packets/NeighborAdvertisementTest.java
new file mode 100644
index 0000000..3317b2b
--- /dev/null
+++ b/tests/unit/src/com/android/networkstack/packets/NeighborAdvertisementTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.packets;
+
+import static android.system.OsConstants.ETH_P_IPV6;
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.net.InetAddresses;
+import android.net.MacAddress;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class NeighborAdvertisementTest {
+ private static final Inet6Address TEST_SRC_ADDR =
+ (Inet6Address) InetAddresses.parseNumericAddress("fe80::dfd9:50a0:cc7b:7d6d");
+ private static final Inet6Address TEST_TARGET_ADDR =
+ (Inet6Address) InetAddresses.parseNumericAddress("2001:db8:1:0:c928:250d:b90c:3178");
+ private static final byte[] TEST_SOURCE_MAC_ADDR = new byte[] {
+ (byte) 0xea, (byte) 0xbe, (byte) 0x11, (byte) 0x25, (byte) 0xc1, (byte) 0x25
+ };
+ private static final byte[] TEST_DST_MAC_ADDR = new byte[] {
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ };
+ private static final byte[] TEST_GRATUITOUS_NA = new byte[] {
+ // dst mac address
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ // src mac address
+ (byte) 0xea, (byte) 0xbe, (byte) 0x11, (byte) 0x25, (byte) 0xc1, (byte) 0x25,
+ // ether type
+ (byte) 0x86, (byte) 0xdd,
+ // version, priority and flow label
+ (byte) 0x60, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ // length
+ (byte) 0x00, (byte) 0x20,
+ // next header
+ (byte) 0x3a,
+ // hop limit
+ (byte) 0xff,
+ // source address
+ (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0xdf, (byte) 0xd9, (byte) 0x50, (byte) 0xa0,
+ (byte) 0xcc, (byte) 0x7b, (byte) 0x7d, (byte) 0x6d,
+ // destination address
+ (byte) 0xff, (byte) 0x02, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // ICMP type, code, checksum
+ (byte) 0x88, (byte) 0x00, (byte) 0x3a, (byte) 0x3c,
+ // flags
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ // target address
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00,
+ (byte) 0xc9, (byte) 0x28, (byte) 0x25, (byte) 0x0d,
+ (byte) 0xb9, (byte) 0x0c, (byte) 0x31, (byte) 0x78,
+ // TLLA option
+ (byte) 0x02, (byte) 0x01,
+ // Link-Layer address
+ (byte) 0xea, (byte) 0xbe, (byte) 0x11, (byte) 0x25, (byte) 0xc1, (byte) 0x25,
+ };
+ private static final byte[] TEST_GRATUITOUS_NA_LESS_LENGTH = new byte[] {
+ // dst mac address
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ // src mac address
+ (byte) 0xea, (byte) 0xbe, (byte) 0x11, (byte) 0x25, (byte) 0xc1, (byte) 0x25,
+ // ether type
+ (byte) 0x86, (byte) 0xdd,
+ // version, priority and flow label
+ (byte) 0x60, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ // length
+ (byte) 0x00, (byte) 0x20,
+ // next header
+ (byte) 0x3a,
+ // hop limit
+ (byte) 0xff,
+ // source address
+ (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0xdf, (byte) 0xd9, (byte) 0x50, (byte) 0xa0,
+ (byte) 0xcc, (byte) 0x7b, (byte) 0x7d, (byte) 0x6d,
+ // destination address
+ (byte) 0xff, (byte) 0x02, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ };
+ private static final byte[] TEST_GRATUITOUS_NA_TRUNCATED = new byte[] {
+ // dst mac address
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ // src mac address
+ (byte) 0xea, (byte) 0xbe, (byte) 0x11, (byte) 0x25, (byte) 0xc1, (byte) 0x25,
+ // ether type
+ (byte) 0x86, (byte) 0xdd,
+ // version, priority and flow label
+ (byte) 0x60, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ // length
+ (byte) 0x00, (byte) 0x20,
+ // next header
+ (byte) 0x3a,
+ // hop limit
+ (byte) 0xff,
+ // source address
+ (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0xdf, (byte) 0xd9, (byte) 0x50, (byte) 0xa0,
+ (byte) 0xcc, (byte) 0x7b, (byte) 0x7d, (byte) 0x6d,
+ // destination address
+ (byte) 0xff, (byte) 0x02, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // ICMP type, code, checksum
+ (byte) 0x88, (byte) 0x00, (byte) 0x3a, (byte) 0x3c,
+ // flags
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ // target address
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00,
+ (byte) 0xc9, (byte) 0x28, (byte) 0x25, (byte) 0x0d,
+ (byte) 0xb9, (byte) 0x0c, (byte) 0x31, (byte) 0x78,
+ // TLLA option
+ (byte) 0x02, (byte) 0x01,
+ // Link-Layer address
+ (byte) 0xea, (byte) 0xbe, (byte) 0x11, (byte) 0x25,
+ };
+
+ @Test
+ public void testGratuitousNa_build() throws Exception {
+ final ByteBuffer na = NeighborAdvertisement.build(
+ MacAddress.fromBytes(TEST_SOURCE_MAC_ADDR),
+ MacAddress.fromBytes(TEST_DST_MAC_ADDR),
+ TEST_SRC_ADDR, IPV6_ADDR_ALL_ROUTERS_MULTICAST, 0 /* flags */, TEST_TARGET_ADDR);
+ assertArrayEquals(na.array(), TEST_GRATUITOUS_NA);
+ }
+
+ @Test
+ public void testGratuitousNa_parse() throws Exception {
+ final NeighborAdvertisement na = NeighborAdvertisement.parse(TEST_GRATUITOUS_NA,
+ TEST_GRATUITOUS_NA.length);
+
+ assertArrayEquals(TEST_SOURCE_MAC_ADDR, na.ethHdr.srcMac.toByteArray());
+ assertArrayEquals(TEST_DST_MAC_ADDR, na.ethHdr.dstMac.toByteArray());
+ assertEquals(ETH_P_IPV6, na.ethHdr.etherType);
+ assertEquals(IPPROTO_ICMPV6, na.ipv6Hdr.nextHeader);
+ assertEquals(0xff, na.ipv6Hdr.hopLimit);
+ assertEquals(IPV6_ADDR_ALL_ROUTERS_MULTICAST, na.ipv6Hdr.dstIp);
+ assertEquals(TEST_SRC_ADDR, na.ipv6Hdr.srcIp);
+ assertEquals(ICMPV6_NEIGHBOR_ADVERTISEMENT, na.icmpv6Hdr.type);
+ assertEquals(0, na.icmpv6Hdr.code);
+ assertEquals(0, na.naHdr.flags);
+ assertEquals(TEST_TARGET_ADDR, na.naHdr.target);
+ assertEquals(2, na.tlla.type);
+ assertEquals(1, na.tlla.length);
+ assertArrayEquals(TEST_SOURCE_MAC_ADDR, na.tlla.linkLayerAddress.toByteArray());
+
+ assertArrayEquals(TEST_GRATUITOUS_NA, na.toByteBuffer().array());
+ }
+
+ @Test
+ public void testGratuitousNa_invalidByteBufferParameters() throws Exception {
+ assertThrows(NeighborAdvertisement.ParseException.class,
+ () -> NeighborAdvertisement.parse(TEST_GRATUITOUS_NA, 0));
+ }
+
+ @Test
+ public void testGratuitousNa_lessPacketLength() throws Exception {
+ assertThrows(NeighborAdvertisement.ParseException.class,
+ () -> NeighborAdvertisement.parse(TEST_GRATUITOUS_NA_LESS_LENGTH,
+ TEST_GRATUITOUS_NA_LESS_LENGTH.length));
+ }
+
+ @Test
+ public void testGratuitousNa_truncatedPacket() throws Exception {
+ assertThrows(IllegalArgumentException.class,
+ () -> NeighborAdvertisement.parse(TEST_GRATUITOUS_NA_TRUNCATED,
+ TEST_GRATUITOUS_NA_TRUNCATED.length));
+ }
+}