[automerger skipped] Move frameworks/base depedencies into NetworkStack. am: d5664eab03 -s ours

am skip reason: Merged-In Ibb315561a8940d0a2af2b44237e39c7e7b1963aa with SHA-1 2ff6ee4131 is already in history

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/modules/NetworkStack/+/15032864

Change-Id: Id22e3e65827b7fb0f53a8b86f8f5613299c9ae22
diff --git a/common/moduleutils/src/android/net/ip/ConntrackMonitor.java b/common/moduleutils/src/android/net/ip/ConntrackMonitor.java
index 9189002..6c72984 100644
--- a/common/moduleutils/src/android/net/ip/ConntrackMonitor.java
+++ b/common/moduleutils/src/android/net/ip/ConntrackMonitor.java
@@ -51,6 +51,12 @@
     public static final int NF_NETLINK_CONNTRACK_UPDATE = 2;
     public static final int NF_NETLINK_CONNTRACK_DESTROY = 4;
 
+    // The socket receive buffer size in bytes. If too many conntrack messages are sent too
+    // quickly, the conntrack messages can overflow the socket receive buffer. This can happen
+    // if too many connections are disconnected by losing network and so on. Use a large-enough
+    // buffer to avoid the error ENOBUFS while listening to the conntrack messages.
+    private static final int SOCKET_RECV_BUFSIZE = 6 * 1024 * 1024;
+
     /**
      * A class for describing parsed netfilter conntrack events.
      */
@@ -176,7 +182,7 @@
     public ConntrackMonitor(@NonNull Handler h, @NonNull SharedLog log,
             @NonNull ConntrackEventConsumer cb) {
         super(h, log, TAG, OsConstants.NETLINK_NETFILTER, NF_NETLINK_CONNTRACK_NEW
-                | NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY);
+                | NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY, SOCKET_RECV_BUFSIZE);
         mConsumer = cb;
     }
 
diff --git a/common/moduleutils/src/android/net/ip/NetlinkMonitor.java b/common/moduleutils/src/android/net/ip/NetlinkMonitor.java
index 3d314f1..2025967 100644
--- a/common/moduleutils/src/android/net/ip/NetlinkMonitor.java
+++ b/common/moduleutils/src/android/net/ip/NetlinkMonitor.java
@@ -21,6 +21,8 @@
 import static android.system.OsConstants.AF_NETLINK;
 import static android.system.OsConstants.SOCK_DGRAM;
 import static android.system.OsConstants.SOCK_NONBLOCK;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_RCVBUF;
 
 import android.annotation.NonNull;
 import android.net.netlink.NetlinkErrorMessage;
@@ -56,9 +58,13 @@
     protected final String mTag;
     private final int mFamily;
     private final int mBindGroups;
+    private final int mSockRcvbufSize;
 
     private static final boolean DBG = false;
 
+    // Default socket receive buffer size. This means the specific buffer size is not set.
+    private static final int DEFAULT_SOCKET_RECV_BUFSIZE = -1;
+
     /**
      * Constructs a new {@code NetlinkMonitor} instance.
      *
@@ -68,14 +74,23 @@
      * @param tag The log tag to use for log messages.
      * @param family the Netlink socket family to, e.g., {@code NETLINK_ROUTE}.
      * @param bindGroups the netlink groups to bind to.
+     * @param sockRcvbufSize the specific socket receive buffer size in bytes. -1 means that don't
+     *        set the specific socket receive buffer size in #createFd and use the default value in
+     *        /proc/sys/net/core/rmem_default file. See SO_RCVBUF in man-pages/socket.
      */
     public NetlinkMonitor(@NonNull Handler h, @NonNull SharedLog log, @NonNull String tag,
-            int family, int bindGroups) {
+            int family, int bindGroups, int sockRcvbufSize) {
         super(h, NetlinkSocket.DEFAULT_RECV_BUFSIZE);
         mLog = log.forSubComponent(tag);
         mTag = tag;
         mFamily = family;
         mBindGroups = bindGroups;
+        mSockRcvbufSize = sockRcvbufSize;
+    }
+
+    public NetlinkMonitor(@NonNull Handler h, @NonNull SharedLog log, @NonNull String tag,
+            int family, int bindGroups) {
+        this(h, log, tag, family, bindGroups, DEFAULT_SOCKET_RECV_BUFSIZE);
     }
 
     @Override
@@ -84,6 +99,9 @@
 
         try {
             fd = Os.socket(AF_NETLINK, SOCK_DGRAM | SOCK_NONBLOCK, mFamily);
+            if (mSockRcvbufSize != DEFAULT_SOCKET_RECV_BUFSIZE) {
+                Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, mSockRcvbufSize);
+            }
             Os.bind(fd, makeNetlinkSocketAddress(0, mBindGroups));
             NetlinkSocket.connectToKernel(fd);
 
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index 34469b8..7a13392 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -1411,7 +1411,7 @@
         //     pass
         // if it's ICMPv6 RS to any:
         //   drop
-        // if it's ICMPv6 NA to ff02::1 or ff02::2:
+        // if it's ICMPv6 NA to anything in ff02::/120
         //   drop
         // if keepalive ack
         //   drop
@@ -1495,7 +1495,7 @@
      * <li>Drop all broadcast non-IP non-ARP packets.
      * <li>Pass all non-ICMPv6 IPv6 packets,
      * <li>Pass all non-IPv4 and non-IPv6 packets,
-     * <li>Drop IPv6 ICMPv6 NAs to ff02::1 or ff02::2.
+     * <li>Drop IPv6 ICMPv6 NAs to anything in ff02::/120.
      * <li>Drop IPv6 ICMPv6 RSs.
      * <li>Filter IPv4 packets (see generateIPv4FilterLocked())
      * <li>Filter IPv6 packets (see generateIPv6FilterLocked())
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index a57e99d..445f915 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -61,6 +61,7 @@
 import android.net.metrics.IpManagerEvent;
 import android.net.networkstack.aidl.dhcp.DhcpOption;
 import android.net.shared.InitialConfiguration;
+import android.net.shared.Layer2Information;
 import android.net.shared.ProvisioningConfiguration;
 import android.net.shared.ProvisioningConfiguration.ScanResultInfo;
 import android.net.shared.ProvisioningConfiguration.ScanResultInfo.InformationElement;
@@ -147,6 +148,7 @@
  * @hide
  */
 public class IpClient extends StateMachine {
+    private static final String TAG = IpClient.class.getSimpleName();
     private static final boolean DBG = false;
 
     // For message logging.
@@ -872,26 +874,28 @@
                 false /* defaultEnabled */);
     }
 
-    private void setInitialBssid(final ProvisioningConfiguration req) {
-        final ScanResultInfo scanResultInfo = req.mScanResultInfo;
-        mCurrentBssid = null;
+    @VisibleForTesting
+    static MacAddress getInitialBssid(final Layer2Information layer2Info,
+            final ScanResultInfo scanResultInfo, boolean isAtLeastS) {
+        MacAddress bssid = null;
         // http://b/185202634
         // ScanResultInfo is not populated in some situations.
         // On S and above, prefer getting the BSSID from the Layer2Info.
         // On R and below, get the BSSID from the ScanResultInfo and fall back to
         // getting it from the Layer2Info. This ensures no regressions if any R
         // devices pass in a null or meaningless BSSID in the Layer2Info.
-        if (!ShimUtils.isAtLeastS() && scanResultInfo != null) {
+        if (!isAtLeastS && scanResultInfo != null) {
             try {
-                mCurrentBssid = MacAddress.fromString(scanResultInfo.getBssid());
+                bssid = MacAddress.fromString(scanResultInfo.getBssid());
             } catch (IllegalArgumentException e) {
-                Log.wtf(mTag, "Invalid BSSID: " + scanResultInfo.getBssid()
+                Log.wtf(TAG, "Invalid BSSID: " + scanResultInfo.getBssid()
                         + " in provisioning configuration", e);
             }
         }
-        if (mCurrentBssid == null && req.mLayer2Info != null) {
-            mCurrentBssid = req.mLayer2Info.mBssid;
+        if (bssid == null && layer2Info != null) {
+            bssid = layer2Info.mBssid;
         }
+        return bssid;
     }
 
     private boolean shouldDisableAcceptRaOnProvisioningLoss() {
@@ -922,7 +926,8 @@
             return;
         }
 
-        setInitialBssid(req);
+        mCurrentBssid = getInitialBssid(req.mLayer2Info, req.mScanResultInfo,
+                ShimUtils.isAtLeastS());
         if (req.mLayer2Info != null) {
             mL2Key = req.mLayer2Info.mL2Key;
             mCluster = req.mLayer2Info.mCluster;
diff --git a/src/com/android/networkstack/packets/NeighborSolicitation.java b/src/com/android/networkstack/packets/NeighborSolicitation.java
new file mode 100644
index 0000000..e743209
--- /dev/null
+++ b/src/com/android/networkstack/packets/NeighborSolicitation.java
@@ -0,0 +1,134 @@
+/*
+ * 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_SLLA;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
+
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+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.NsHeader;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+
+/**
+ * Defines basic data and operations needed to build and parse Neighbor Solicitation packet.
+ *
+ * @hide
+ */
+public class NeighborSolicitation {
+    private static final int NS_HEADER_LEN = Struct.getSize(NsHeader.class);
+
+    @NonNull
+    public final EthernetHeader ethHdr;
+    @NonNull
+    public final Ipv6Header ipv6Hdr;
+    @NonNull
+    public final Icmpv6Header icmpv6Hdr;
+    @NonNull
+    public final NsHeader nsHdr;
+    @Nullable
+    public final LlaOption slla;
+
+    public NeighborSolicitation(@NonNull final EthernetHeader ethHdr,
+            @NonNull final Ipv6Header ipv6Hdr, @NonNull final Icmpv6Header icmpv6Hdr,
+            @NonNull final NsHeader nsHdr, @Nullable final LlaOption slla) {
+        this.ethHdr = ethHdr;
+        this.ipv6Hdr = ipv6Hdr;
+        this.icmpv6Hdr = icmpv6Hdr;
+        this.nsHdr = nsHdr;
+        this.slla = slla;
+    }
+
+    /**
+     * Convert a Neighbor Solicitation 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 nsHeaderLen = Struct.getSize(NsHeader.class);
+        final int sllaOptionLen = (slla == null) ? 0 : Struct.getSize(LlaOption.class);
+        final ByteBuffer packet = ByteBuffer.allocate(etherHeaderLen + ipv6HeaderLen
+                + icmpv6HeaderLen + nsHeaderLen + sllaOptionLen);
+
+        ethHdr.writeToByteBuffer(packet);
+        ipv6Hdr.writeToByteBuffer(packet);
+        icmpv6Hdr.writeToByteBuffer(packet);
+        nsHdr.writeToByteBuffer(packet);
+        if (slla != null) {
+            slla.writeToByteBuffer(packet);
+        }
+        packet.flip();
+
+        return packet;
+    }
+
+    /**
+     * Build a Neighbor Solicitation 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, @NonNull final Inet6Address target) {
+        final ByteBuffer slla = LlaOption.build((byte) ICMPV6_ND_OPTION_SLLA, srcMac);
+        return Ipv6Utils.buildNsPacket(srcMac, dstMac, srcIp, dstIp, target, slla);
+    }
+
+    /**
+     * Parse a Neighbor Solicitation packet from ByteBuffer.
+     */
+    public static NeighborSolicitation parse(@NonNull final byte[] recvbuf, final int length)
+            throws ParseException {
+        if (length < ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMPV6_HEADER_MIN_LEN + NS_HEADER_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 Solicitation 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 NsHeader nsHdr = Struct.parse(NsHeader.class, packet);
+        final LlaOption slla = (packet.remaining() == 0)
+                ? null
+                : Struct.parse(LlaOption.class, packet);
+
+        return new NeighborSolicitation(ethHdr, ipv6Hdr, icmpv6Hdr, nsHdr, slla);
+    }
+
+    /**
+     * Thrown when parsing Neighbor Solicitation packet failed.
+     */
+    public static class ParseException extends Exception {
+        ParseException(String message) {
+            super(message);
+        }
+    }
+}
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index 7a2a471..3842b50 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -72,6 +72,7 @@
     platform_apis: true,
     test_suites: ["device-tests"],
     min_sdk_version: "29",
+    target_sdk_version: "30",
 }
 
 // Network stack next integration tests.
@@ -111,6 +112,7 @@
     certificate: "networkstack",
     platform_apis: true,
     min_sdk_version: "29",
+    target_sdk_version: "30",
     test_suites: ["device-tests", "mts"],
     test_config: "AndroidTest_Coverage.xml",
     defaults: ["NetworkStackIntegrationTestsJniDefaults"],
diff --git a/tests/integration/AndroidManifest.xml b/tests/integration/AndroidManifest.xml
index 12f5d7d..bfd3735 100644
--- a/tests/integration/AndroidManifest.xml
+++ b/tests/integration/AndroidManifest.xml
@@ -16,7 +16,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.server.networkstack.integrationtests"
           android:sharedUserId="android.uid.networkstack">
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
 
     <!-- Note: do not add any privileged or signature permissions that are granted
          to the network stack app. Otherwise, the test APK will install, but when the device is
diff --git a/tests/integration/AndroidManifest_coverage.xml b/tests/integration/AndroidManifest_coverage.xml
index 660e42d..fc91e59 100644
--- a/tests/integration/AndroidManifest_coverage.xml
+++ b/tests/integration/AndroidManifest_coverage.xml
@@ -16,7 +16,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.server.networkstack.coverage"
           android:sharedUserId="android.uid.networkstack">
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
 
     <!-- Note: do not add any privileged or signature permissions that are granted
          to the network stack app. Otherwise, the test APK will install, but when the device is
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
index c121b3d..e40db77 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
@@ -3007,119 +3007,4 @@
         assertEquals(0, naList.size());
         assertEquals(1, arpList.size());
     }
-
-    private void doInitialBssidSetupTest(final Layer2Information layer2Info,
-            final ScanResultInfo scanResultInfo) throws Exception {
-        ProvisioningConfiguration.Builder prov = new ProvisioningConfiguration.Builder()
-                .withoutIpReachabilityMonitor()
-                .withLayer2Information(layer2Info)
-                .withScanResultInfo(scanResultInfo)
-                .withDisplayName("\"0001docomo\"")
-                .withoutIPv6();
-
-        setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* shouldReplyRapidCommitAck */,
-                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
-        startIpClientProvisioning(prov.build());
-
-        handleDhcpPackets(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
-                true /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, null /* serverSentUrl */);
-        verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
-        forceLayer2Roaming();
-    }
-
-    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
-    public void testSetInitialBssidFromLayer2Info() throws Exception {
-        final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
-                MacAddress.fromString(TEST_DEFAULT_BSSID));
-
-        doInitialBssidSetupTest(layer2Info, null /* scanResultInfo */);
-
-        // Initial BSSID comes from layer2Info, it's different with target roaming bssid,
-        // then verify that DHCPREQUEST packet is sent after roaming.
-        final DhcpPacket packet = getNextDhcpPacket();
-        assertTrue(packet instanceof DhcpRequestPacket);
-    }
-
-    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
-    public void testSetInitialBssidFromLayer2Info_NullBssid() throws Exception {
-        final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
-                null /* bssid */);
-        final ScanResultInfo scanResultInfo =
-                makeScanResultInfo(TEST_DEFAULT_SSID, TEST_DHCP_ROAM_BSSID);
-
-        doInitialBssidSetupTest(layer2Info, scanResultInfo);
-
-        // Initial BSSID comes from layer2Info, it's null, no DHCPREQUEST packet
-        // will be sent after roaming.
-        final DhcpPacket packet = getNextDhcpPacket(TEST_TIMEOUT_MS);
-        assertNull(packet);
-    }
-
-    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
-    public void testSetInitialBssidFromLayer2Info_SameRoamingBssid() throws Exception {
-        final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
-                MacAddress.fromString(TEST_DHCP_ROAM_BSSID));
-
-        doInitialBssidSetupTest(layer2Info, null /* scanResultInfo */);
-
-        // Initial BSSID comes from layer2Info, it's same with target roaming bssid,
-        // no DHCPREQUEST packet will be sent after roaming.
-        final DhcpPacket packet = getNextDhcpPacket(TEST_TIMEOUT_MS);
-        assertNull(packet);
-    }
-
-    @Test @IgnoreAfter(Build.VERSION_CODES.R)
-    public void testSetInitialBssidFromScanResultInfo() throws Exception {
-        final ScanResultInfo scanResultInfo =
-                makeScanResultInfo(TEST_DEFAULT_SSID, TEST_DEFAULT_BSSID);
-
-        doInitialBssidSetupTest(null /* layer2Info */, scanResultInfo);
-
-        // Initial BSSID comes from ScanResultInfo, it's different with target roaming bssid,
-        // then verify that DHCPREQUEST packet is sent after roaming.
-        final DhcpPacket packet = getNextDhcpPacket();
-        assertTrue(packet instanceof DhcpRequestPacket);
-    }
-
-    @Test @IgnoreAfter(Build.VERSION_CODES.R)
-    public void testSetInitialBssidFromScanResultInfo_SameRoamingBssid() throws Exception {
-        final ScanResultInfo scanResultInfo =
-                makeScanResultInfo(TEST_DEFAULT_SSID, TEST_DHCP_ROAM_BSSID);
-
-        doInitialBssidSetupTest(null /* layer2Info */, scanResultInfo);
-
-        // Initial BSSID comes from ScanResultInfo, it's same with target roaming bssid,
-        // no DHCPREQUEST packet will be sent after roaming.
-        final DhcpPacket packet = getNextDhcpPacket(TEST_TIMEOUT_MS);
-        assertNull(packet);
-    }
-
-    @Test @IgnoreAfter(Build.VERSION_CODES.R)
-    public void testSetInitialBssidFromScanResultInfo_BrokenInitialBssid() throws Exception {
-        final ScanResultInfo scanResultInfo =
-                makeScanResultInfo(TEST_DEFAULT_SSID, "00:11:22:33:44:");
-
-        doInitialBssidSetupTest(null /* layer2Info */, scanResultInfo);
-
-        // Initial BSSID comes from ScanResultInfo, it's broken MAC address format and fallback
-        // to null layer2Info, no DHCPREQUEST packet will be sent after roaming.
-        final DhcpPacket packet = getNextDhcpPacket(TEST_TIMEOUT_MS);
-        assertNull(packet);
-    }
-
-    @Test @IgnoreAfter(Build.VERSION_CODES.R)
-    public void testSetInitialBssidFromScanResultInfo_BrokenInitialBssidFallback()
-            throws Exception {
-        final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
-                MacAddress.fromString(TEST_DEFAULT_BSSID));
-        final ScanResultInfo scanResultInfo =
-                makeScanResultInfo(TEST_DEFAULT_SSID, "00:11:22:33:44:");
-
-        doInitialBssidSetupTest(layer2Info, scanResultInfo);
-
-        // Initial BSSID comes from ScanResultInfo, it's broken MAC address format and fallback
-        // to check layer2Info, then verify DHCPREQUEST packet will be sent after roaming.
-        final DhcpPacket packet = getNextDhcpPacket();
-        assertTrue(packet instanceof DhcpRequestPacket);
-    }
 }
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 73a844b..64da600 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -76,8 +76,8 @@
     static_libs: ["NetworkStackApiStableLib"],
     visibility: [
         "//packages/modules/NetworkStack/tests/integration",
-        "//frameworks/base/packages/Tethering/tests/integration",
-        "//packages/modules/Connectivity/Tethering/tests/integration",
+        "//packages/modules/Connectivity/tests:__subpackages__",
+        "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
     ]
 }
 
diff --git a/tests/unit/src/android/net/ip/IpClientTest.java b/tests/unit/src/android/net/ip/IpClientTest.java
index 7260332..c9e486b 100644
--- a/tests/unit/src/android/net/ip/IpClientTest.java
+++ b/tests/unit/src/android/net/ip/IpClientTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.any;
@@ -55,7 +56,9 @@
 import android.net.ipmemorystore.NetworkAttributes;
 import android.net.metrics.IpConnectivityLog;
 import android.net.shared.InitialConfiguration;
+import android.net.shared.Layer2Information;
 import android.net.shared.ProvisioningConfiguration;
+import android.net.shared.ProvisioningConfiguration.ScanResultInfo;
 import android.net.util.InterfaceParams;
 import android.os.Build;
 
@@ -83,9 +86,12 @@
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.nio.ByteBuffer;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Random;
 import java.util.Set;
 
 
@@ -107,6 +113,8 @@
     private static final int TEST_TIMEOUT_MS = 400;
     private static final String TEST_L2KEY = "some l2key";
     private static final String TEST_CLUSTER = "some cluster";
+    private static final String TEST_SSID = "test_ssid";
+    private static final String TEST_BSSID = "00:11:22:33:44:55";
 
     private static final String TEST_GLOBAL_ADDRESS = "1234:4321::548d:2db2:4fcf:ef75/64";
     private static final String[] TEST_LOCAL_ADDRESSES = {
@@ -709,6 +717,107 @@
         verifyShutdown(ipc);
     }
 
+    private ScanResultInfo makeScanResultInfo(final String ssid, final String bssid) {
+        final ByteBuffer payload = ByteBuffer.allocate(14 /* oui + type + data */);
+        final byte[] data = new byte[10];
+        new Random().nextBytes(data);
+        payload.put(new byte[] { 0x00, 0x1A, 0x11 });
+        payload.put((byte) 0x06);
+        payload.put(data);
+
+        final ScanResultInfo.InformationElement ie =
+                new ScanResultInfo.InformationElement(0xdd /* IE id */, payload);
+        return new ScanResultInfo(ssid, bssid, Collections.singletonList(ie));
+    }
+
+    @Test
+    public void testGetInitialBssidOnSOrAbove() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+                MacAddress.fromString(TEST_BSSID));
+        final MacAddress bssid = ipc.getInitialBssid(layer2Info, null /* ScanReqsultInfo */,
+                true /* isAtLeastS */);
+        assertEquals(bssid, MacAddress.fromString(TEST_BSSID));
+    }
+
+    @Test
+    public void testGetInitialBssidOnSOrAbove_NullBssid() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+                null /* bssid */);
+        final ScanResultInfo scanResultInfo = makeScanResultInfo(TEST_SSID, TEST_BSSID);
+        final MacAddress bssid = ipc.getInitialBssid(layer2Info, scanResultInfo,
+                true /* isAtLeastS */);
+        assertNull(bssid);
+    }
+
+    @Test
+    public void testGetInitialBssidOnSOrAbove_NullLayer2Info() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final ScanResultInfo scanResultInfo = makeScanResultInfo(TEST_SSID, TEST_BSSID);
+        final MacAddress bssid = ipc.getInitialBssid(null /* layer2Info */, scanResultInfo,
+                true /* isAtLeastS */);
+        assertNull(bssid);
+    }
+
+    @Test
+    public void testGetInitialBssidBeforeS() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+                MacAddress.fromString(TEST_BSSID));
+        final ScanResultInfo scanResultInfo = makeScanResultInfo(TEST_SSID, TEST_BSSID);
+        final MacAddress bssid = ipc.getInitialBssid(layer2Info, scanResultInfo,
+                false /* isAtLeastS */);
+        assertEquals(bssid, MacAddress.fromString(TEST_BSSID));
+    }
+
+    @Test
+    public void testGetInitialBssidBeforeS_NullLayer2Info() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final ScanResultInfo scanResultInfo = makeScanResultInfo(TEST_SSID, TEST_BSSID);
+        final MacAddress bssid = ipc.getInitialBssid(null /* layer2Info */, scanResultInfo,
+                false /* isAtLeastS */);
+        assertEquals(bssid, MacAddress.fromString(TEST_BSSID));
+    }
+
+    @Test
+    public void testGetInitialBssidBeforeS_BrokenInitialBssid() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final ScanResultInfo scanResultInfo = makeScanResultInfo(TEST_SSID, "00:11:22:33:44:");
+        final MacAddress bssid = ipc.getInitialBssid(null /* layer2Info */, scanResultInfo,
+                false /* isAtLeastS */);
+        assertNull(bssid);
+    }
+
+    @Test
+    public void testGetInitialBssidBeforeS_BrokenInitialBssidFallback() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+                MacAddress.fromString(TEST_BSSID));
+        final ScanResultInfo scanResultInfo = makeScanResultInfo(TEST_SSID, "00:11:22:33:44:");
+        final MacAddress bssid = ipc.getInitialBssid(layer2Info, scanResultInfo,
+                false /* isAtLeastS */);
+        assertEquals(bssid, MacAddress.fromString(TEST_BSSID));
+    }
+
+    @Test
+    public void testGetInitialBssidBeforeS_NullScanResultInfoFallback() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+                MacAddress.fromString(TEST_BSSID));
+        final MacAddress bssid = ipc.getInitialBssid(layer2Info, null /* scanResultInfo */,
+                false /* isAtLeastS */);
+        assertEquals(bssid, MacAddress.fromString(TEST_BSSID));
+    }
+
+    @Test
+    public void testGetInitialBssidBeforeS_NullScanResultInfoAndLayer2Info() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final MacAddress bssid = ipc.getInitialBssid(null /* layer2Info */,
+                null /* scanResultInfo */, false /* isAtLeastS */);
+        assertNull(bssid);
+    }
+
     interface Fn<A,B> {
         B call(A a) throws Exception;
     }
diff --git a/tests/unit/src/com/android/networkstack/packets/NeighborSolicitationTest.java b/tests/unit/src/com/android/networkstack/packets/NeighborSolicitationTest.java
new file mode 100644
index 0000000..c3ff239
--- /dev/null
+++ b/tests/unit/src/com/android/networkstack/packets/NeighborSolicitationTest.java
@@ -0,0 +1,270 @@
+/*
+ * 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_SOLICITATION;
+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 NeighborSolicitationTest {
+    private static final Inet6Address TEST_SRC_ADDR =
+            (Inet6Address) InetAddresses.parseNumericAddress("fe80::d419:d664:df38:2f65");
+    private static final Inet6Address TEST_DST_ADDR =
+            (Inet6Address) InetAddresses.parseNumericAddress("fe80::200:1a:1122:3344");
+    private static final Inet6Address TEST_TARGET_ADDR =
+            (Inet6Address) InetAddresses.parseNumericAddress("fe80::200:1a:1122:3344");
+    private static final byte[] TEST_SOURCE_MAC_ADDR = new byte[] {
+            (byte) 0x06, (byte) 0x5a, (byte) 0xac, (byte) 0x02, (byte) 0x61, (byte) 0x11,
+    };
+    private static final byte[] TEST_DST_MAC_ADDR = new byte[] {
+            (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+    };
+    private static final byte[] TEST_NEIGHBOR_SOLICITATION = new byte[] {
+        // dst mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+        // src mac address
+        (byte) 0x06, (byte) 0x5a, (byte) 0xac, (byte) 0x02, (byte) 0x61, (byte) 0x11,
+        // 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) 0xd4, (byte) 0x19, (byte) 0xd6, (byte) 0x64,
+        (byte) 0xdf, (byte) 0x38, (byte) 0x2f, (byte) 0x65,
+        // destination address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x1a,
+        (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+        // ICMP type, code, checksum
+        (byte) 0x87, (byte) 0x00, (byte) 0x22, (byte) 0x96,
+        // reserved
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // target address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x1a,
+        (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+        // slla option
+        (byte) 0x01, (byte) 0x01,
+        // link-layer address
+        (byte) 0x06, (byte) 0x5a, (byte) 0xac, (byte) 0x02,
+        (byte) 0x61, (byte) 0x11,
+    };
+    private static final byte[] TEST_NEIGHBOR_SOLICITATION_WITHOUT_SLLA = new byte[] {
+        // dst mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+        // src mac address
+        (byte) 0x06, (byte) 0x5a, (byte) 0xac, (byte) 0x02, (byte) 0x61, (byte) 0x11,
+        // 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) 0xd4, (byte) 0x19, (byte) 0xd6, (byte) 0x64,
+        (byte) 0xdf, (byte) 0x38, (byte) 0x2f, (byte) 0x65,
+        // destination address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x1a,
+        (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+        // ICMP type, code, checksum
+        (byte) 0x87, (byte) 0x00, (byte) 0x22, (byte) 0x96,
+        // reserved
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // target address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x1a,
+        (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+    };
+    private static final byte[] TEST_NEIGHBOR_SOLICITATION_LESS_LENGTH = new byte[] {
+        // dst mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+        // src mac address
+        (byte) 0x06, (byte) 0x5a, (byte) 0xac, (byte) 0x02, (byte) 0x61, (byte) 0x11,
+        // 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) 0xd4, (byte) 0x19, (byte) 0xd6, (byte) 0x64,
+        (byte) 0xdf, (byte) 0x38, (byte) 0x2f, (byte) 0x65,
+        // destination address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x1a,
+        (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+        // ICMP type, code, checksum
+        (byte) 0x87, (byte) 0x00, (byte) 0x22, (byte) 0x96,
+        // reserved
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+    };
+    private static final byte[] TEST_NEIGHBOR_SOLICITATION_TRUNCATED = new byte[] {
+        // dst mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+        // src mac address
+        (byte) 0x06, (byte) 0x5a, (byte) 0xac, (byte) 0x02, (byte) 0x61, (byte) 0x11,
+        // 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) 0xd4, (byte) 0x19, (byte) 0xd6, (byte) 0x64,
+        (byte) 0xdf, (byte) 0x38, (byte) 0x2f, (byte) 0x65,
+        // destination address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x1a,
+        (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+        // ICMP type, code, checksum
+        (byte) 0x87, (byte) 0x00, (byte) 0x22, (byte) 0x96,
+        // reserved
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // target address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x1a,
+        (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+        // slla option
+        (byte) 0x01, (byte) 0x01,
+        // link-layer address
+        (byte) 0x06, (byte) 0x5a, (byte) 0xac, (byte) 0x02,
+    };
+
+    @Test
+    public void testNeighborSolicitation_build() throws Exception {
+        final ByteBuffer ns = NeighborSolicitation.build(
+                MacAddress.fromBytes(TEST_SOURCE_MAC_ADDR),
+                MacAddress.fromBytes(TEST_DST_MAC_ADDR),
+                TEST_SRC_ADDR, TEST_DST_ADDR, TEST_TARGET_ADDR);
+        assertArrayEquals(ns.array(), TEST_NEIGHBOR_SOLICITATION);
+    }
+
+    private void assertNeighborSolicitation(final NeighborSolicitation ns, boolean hasSllaOption) {
+        assertArrayEquals(TEST_SOURCE_MAC_ADDR, ns.ethHdr.srcMac.toByteArray());
+        assertArrayEquals(TEST_DST_MAC_ADDR, ns.ethHdr.dstMac.toByteArray());
+        assertEquals(ETH_P_IPV6, ns.ethHdr.etherType);
+        assertEquals(IPPROTO_ICMPV6, ns.ipv6Hdr.nextHeader);
+        assertEquals(0xff, ns.ipv6Hdr.hopLimit);
+        assertEquals(TEST_DST_ADDR, ns.ipv6Hdr.dstIp);
+        assertEquals(TEST_SRC_ADDR, ns.ipv6Hdr.srcIp);
+        assertEquals(ICMPV6_NEIGHBOR_SOLICITATION, ns.icmpv6Hdr.type);
+        assertEquals(0, ns.icmpv6Hdr.code);
+        assertEquals(TEST_TARGET_ADDR, ns.nsHdr.target);
+        if (hasSllaOption) {
+            assertEquals(MacAddress.fromBytes(TEST_SOURCE_MAC_ADDR), ns.slla.linkLayerAddress);
+        }
+    }
+
+    @Test
+    public void testNeighborSolicitation_parse() throws Exception {
+        final NeighborSolicitation ns = NeighborSolicitation.parse(TEST_NEIGHBOR_SOLICITATION,
+                TEST_NEIGHBOR_SOLICITATION.length);
+
+        assertNeighborSolicitation(ns, true /* hasSllaOption */);
+        assertArrayEquals(TEST_NEIGHBOR_SOLICITATION, ns.toByteBuffer().array());
+    }
+
+    @Test
+    public void testNeighborSolicitation_parseWithoutSllaOption() throws Exception {
+        final NeighborSolicitation ns =
+                NeighborSolicitation.parse(TEST_NEIGHBOR_SOLICITATION_WITHOUT_SLLA,
+                        TEST_NEIGHBOR_SOLICITATION_WITHOUT_SLLA.length);
+
+        assertNeighborSolicitation(ns, false /* hasSllaOption */);
+        assertArrayEquals(TEST_NEIGHBOR_SOLICITATION_WITHOUT_SLLA, ns.toByteBuffer().array());
+    }
+
+    @Test
+    public void testNeighborSolicitation_invalidPacketLength() throws Exception {
+        assertThrows(NeighborSolicitation.ParseException.class,
+                () -> NeighborSolicitation.parse(TEST_NEIGHBOR_SOLICITATION, 0));
+    }
+
+    @Test
+    public void testNeighborSolicitation_invalidByteBufferLength() throws Exception {
+        assertThrows(NeighborSolicitation.ParseException.class,
+                () -> NeighborSolicitation.parse(TEST_NEIGHBOR_SOLICITATION_TRUNCATED,
+                                                  TEST_NEIGHBOR_SOLICITATION.length));
+    }
+
+    @Test
+    public void testNeighborSolicitation_lessPacketLength() throws Exception {
+        assertThrows(NeighborSolicitation.ParseException.class,
+                () -> NeighborSolicitation.parse(TEST_NEIGHBOR_SOLICITATION_LESS_LENGTH,
+                                                  TEST_NEIGHBOR_SOLICITATION_LESS_LENGTH.length));
+    }
+
+    @Test
+    public void testNeighborSolicitation_truncatedPacket() throws Exception {
+        assertThrows(IllegalArgumentException.class,
+                () -> NeighborSolicitation.parse(TEST_NEIGHBOR_SOLICITATION_TRUNCATED,
+                                                  TEST_NEIGHBOR_SOLICITATION_TRUNCATED.length));
+    }
+}