Snap for 6001391 from 8ee5b28804b96b7f7eba7a47893e4c8a2169a5c8 to qt-aml-resolv-release

Change-Id: I43031b34e34373548c130d6b48876a641b9c463f
diff --git a/Android.bp b/Android.bp
index f17412d..762ace9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -19,20 +19,26 @@
 // (InProcessNetworkStack). The following structure is used to create the build rules:
 //
 //                          NetworkStackAndroidLibraryDefaults <-- common defaults for android libs
-//                                     /           \
-//    +NetworkStackApiStableShims --> /             \ <-- +NetworkStackApiCurrentShims
-//    +NetworkStackApiStableLevel    /               \    +NetworkStackApiCurrentLevel
-//                                  /                 \
-//           NetworkStackApiStableLib         NetworkStackApiCurrentLib <-- android libs w/ all code
-//                     |                                     |             (also used in unit tests)
-//                     | <--   +NetworkStackAppDefaults  --> |
-//                     |          (APK build params)         |
-//                     |                                     |
-//                     | <-- +NetworkStackApiStableLevel     | <-- +NetworkStackApiCurrentLevel
-//                     |                                     |
-//                     |                                     |
-//           NetworkStackApiStable          NetworkStack, InProcessNetworkStack, <-- output APKs
-//                                                    TestNetworkStack
+//                                            /    \
+//           +NetworkStackApiStableShims --> /      \ <-- +NetworkStackApiCurrentShims
+//           +NetworkStackApiStableLevel    /        \    +NetworkStackApiCurrentLevel
+//           +jarjar apistub.api[latest].* /          \   +module src/
+//            to apistub.*                /            \
+//                                       /              \
+//         NetworkStackApiStableDependencies             \
+//                                     /                  \               android libs w/ all code
+//                   +module src/ --> /                    \              (also used in unit tests)
+//                                   /                      \                        |
+//               NetworkStackApiStableLib               NetworkStackApiCurrentLib <--*
+//                          |                                     |
+//                          | <--   +NetworkStackAppDefaults  --> |
+//                          |          (APK build params)         |
+//                          |                                     |
+//                          | <-- +NetworkStackApiStableLevel     | <-- +NetworkStackApiCurrentLevel
+//                          |                                     |
+//                          |                                     |
+//                NetworkStackApiStable          NetworkStack, InProcessNetworkStack, <-- APKs
+//                                                         TestNetworkStack
 
 // Common defaults to define SDK level
 java_defaults {
@@ -43,22 +49,29 @@
 
 java_defaults {
     name: "NetworkStackApiStableLevel",
-    sdk_version: "system_current", // TODO: change to system_29
+    sdk_version: "system_29",
     min_sdk_version: "28",
 }
 
-// Java libraries for the API shims
+// Filegroups for the API shims
 filegroup {
     name: "NetworkStackApiCurrentShims",
     srcs: [
-        "apishim/current/**/*.java"
+        "apishim/common/**/*.java",
+        "apishim/29/**/*.java",
+        "apishim/current/**/*.java",
+        ":net-module-utils-srcs",
     ],
 }
 
+// API stable shims only include the compat package, but it is jarjared to replace the non-compat
+// package
 filegroup {
     name: "NetworkStackApiStableShims",
     srcs: [
-        "apishim/29/**/*.java"
+        "apishim/common/**/*.java",
+        "apishim/29/**/*.java",
+        ":net-module-utils-srcs",
     ],
 }
 
@@ -67,7 +80,6 @@
 java_defaults {
     name: "NetworkStackAndroidLibraryDefaults",
     srcs: [
-        "src/**/*.java",
         ":framework-networkstack-shared-srcs",
         ":services-networkstack-shared-srcs",
         ":statslog-networkstack-java-gen",
@@ -80,25 +92,38 @@
         "networkstackprotosnano",
         "captiveportal-lib",
     ],
-    manifest: "AndroidManifestBase.xml",
     plugins: ["java_api_finder"],
 }
 
 // The versions of the android library containing network stack code compiled for each SDK variant
+// API current uses the sources of the API current shims directly.
+// This allows API current code to be treated identically to code in src/ (it will be moved
+// there eventually), and to use the compat shim as fallback on older devices.
 android_library {
     name: "NetworkStackApiCurrentLib",
     defaults: ["NetworkStackApiCurrentLevel", "NetworkStackAndroidLibraryDefaults"],
-    srcs: [
-        ":NetworkStackApiCurrentShims",
-    ],
+    srcs: [":NetworkStackApiCurrentShims", "src/**/*.java"],
+    manifest: "AndroidManifestBase.xml",
+}
+
+// For API stable, first build the dependencies using jarjar compat rules, then build the sources
+// linking with the dependencies.
+java_library {
+    name: "NetworkStackApiStableDependencies",
+    defaults: ["NetworkStackApiStableLevel", "NetworkStackAndroidLibraryDefaults"],
+    srcs: [":NetworkStackApiStableShims"],
+    jarjar_rules: "apishim/jarjar-rules-compat.txt",
 }
 
 android_library {
     name: "NetworkStackApiStableLib",
-    defaults: ["NetworkStackApiStableLevel", "NetworkStackAndroidLibraryDefaults"],
-    srcs: [
-        ":NetworkStackApiStableShims",
+    defaults: ["NetworkStackApiStableLevel"],
+    srcs: ["src/**/*.java"],
+    // API stable uses a jarjared version of the shims
+    static_libs: [
+        "NetworkStackApiStableDependencies",
     ],
+    manifest: "AndroidManifestBase.xml",
 }
 
 // Common defaults for compiling the actual APK, based on the NetworkStackApiXBase android libraries
@@ -126,7 +151,7 @@
     certificate: "platform",
     manifest: "AndroidManifest_InProcess.xml",
     // InProcessNetworkStack is a replacement for NetworkStack
-    overrides: ["NetworkStack"],
+    overrides: ["NetworkStack", "NetworkStackNext"],
     // The permission configuration *must* be included to ensure security of the device
     // The InProcessNetworkStack goes together with the PlatformCaptivePortalLogin, which replaces
     // the default CaptivePortalLogin.
@@ -135,7 +160,7 @@
 
 // Updatable network stack packaged as an application
 android_app {
-    name: "NetworkStack",
+    name: "NetworkStackNext",
     defaults: ["NetworkStackAppDefaults", "NetworkStackApiCurrentLevel"],
     static_libs: ["NetworkStackApiCurrentLib"],
     certificate: "networkstack",
@@ -146,7 +171,7 @@
 
 // Updatable network stack for finalized API
 android_app {
-    name: "NetworkStackApiStable",
+    name: "NetworkStack",
     defaults: ["NetworkStackAppDefaults", "NetworkStackApiStableLevel"],
     static_libs: ["NetworkStackApiStableLib"],
     certificate: "networkstack",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 249e510..9628b3f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -19,7 +19,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.android.networkstack"
   android:sharedUserId="android.uid.networkstack"
-  android:versionCode="290000000"
+  android:versionCode="299900000"
   android:versionName="2019-09"
 >
 
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 43a1bd0..6c79f20 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -45,6 +45,8 @@
 #$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
 
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/CaptivePortalLogin)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/NetworkStack)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/NetworkStackApiStable)
 
 # ******************************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
diff --git a/TEST_MAPPING b/TEST_MAPPING
index e5c8117..48370c0 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -2,6 +2,9 @@
   "presubmit": [
     {
       "name": "NetworkStackTests"
+    },
+    {
+      "name": "NetworkStackNextTests"
     }
   ],
   "postsubmit": [
diff --git a/apishim/29/com/android/networkstack/apishim/SocketUtilsShimImpl.java b/apishim/29/com/android/networkstack/apishim/api29/SocketUtilsShimImpl.java
similarity index 70%
rename from apishim/29/com/android/networkstack/apishim/SocketUtilsShimImpl.java
rename to apishim/29/com/android/networkstack/apishim/api29/SocketUtilsShimImpl.java
index 0e41e19..be12662 100644
--- a/apishim/29/com/android/networkstack/apishim/SocketUtilsShimImpl.java
+++ b/apishim/29/com/android/networkstack/apishim/api29/SocketUtilsShimImpl.java
@@ -14,18 +14,32 @@
  * limitations under the License.
  */
 
-package com.android.networkstack.apishim;
+package com.android.networkstack.apishim.api29;
 
 import android.net.util.SocketUtils;
 
 import androidx.annotation.NonNull;
 
+import com.android.networkstack.apishim.SocketUtilsShim;
+
 import java.net.SocketAddress;
 
 /**
  * Implementation of SocketUtilsShim for API 29.
  */
 public class SocketUtilsShimImpl implements SocketUtilsShim {
+    protected SocketUtilsShimImpl() {}
+
+    /**
+     * Get a new instance of {@link SocketUtilsShim}.
+     *
+     * Use com.android.networkstack.apishim.SocketUtilsShim#newInstance()
+     * (non-API29 version) instead, to use the correct shims depending on build SDK.
+     */
+    public static SocketUtilsShim newInstance() {
+        return new SocketUtilsShimImpl();
+    }
+
     @NonNull
     @Override
     public SocketAddress makePacketSocketAddress(
diff --git a/apishim/current/com/android/networkstack/apishim/SocketUtilsShimImpl.java b/apishim/30/com/android/networkstack/apishim/SocketUtilsShimImpl.java
similarity index 68%
rename from apishim/current/com/android/networkstack/apishim/SocketUtilsShimImpl.java
rename to apishim/30/com/android/networkstack/apishim/SocketUtilsShimImpl.java
index 92f8438..9516e7c 100644
--- a/apishim/current/com/android/networkstack/apishim/SocketUtilsShimImpl.java
+++ b/apishim/30/com/android/networkstack/apishim/SocketUtilsShimImpl.java
@@ -17,6 +17,7 @@
 package com.android.networkstack.apishim;
 
 import android.net.util.SocketUtils;
+import android.os.Build;
 
 import androidx.annotation.NonNull;
 
@@ -25,7 +26,20 @@
 /**
  * Implementation of {@link SocketUtilsShim} for API 30.
  */
-public class SocketUtilsShimImpl implements SocketUtilsShim {
+public class SocketUtilsShimImpl
+        extends com.android.networkstack.apishim.api29.SocketUtilsShimImpl {
+    protected SocketUtilsShimImpl() {}
+
+    /**
+     * Get a new instance of {@link SocketUtilsShim}.
+     */
+    public static SocketUtilsShim newInstance() {
+        if (!ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)) {
+            return com.android.networkstack.apishim.api29.SocketUtilsShimImpl.newInstance();
+        }
+        return new SocketUtilsShimImpl();
+    }
+
     @NonNull
     @Override
     public SocketAddress makePacketSocketAddress(
diff --git a/apishim/common/com/android/networkstack/apishim/ShimUtils.java b/apishim/common/com/android/networkstack/apishim/ShimUtils.java
new file mode 100644
index 0000000..e9b5305
--- /dev/null
+++ b/apishim/common/com/android/networkstack/apishim/ShimUtils.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 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.apishim;
+
+import android.os.Build;
+
+/**
+ * Utility class for API shims.
+ */
+public final class ShimUtils {
+    /**
+     * Check whether the device release or development API level is strictly higher than the passed
+     * in level.
+     *
+     * On a development build (codename != REL), the device will have the same API level as the
+     * last stable release, even though some additional APIs may be available. In this method the
+     * device API level is considered to be higher if the device supports a stable SDK with a higher
+     * version number, or if the device supports a development version of a SDK that has a higher
+     * version number.
+     *
+     * @return True if the device supports an SDK that has or will have a higher version number,
+     *         even if still in development.
+     */
+    public static boolean isReleaseOrDevelopmentApiAbove(int apiLevel) {
+        // In-development API n+1 will have SDK_INT == n and CODENAME != REL.
+        // Stable API n has SDK_INT == n and CODENAME == REL.
+        final int devApiLevel = Build.VERSION.SDK_INT
+                + ("REL".equals(Build.VERSION.CODENAME) ? 0 : 1);
+        return devApiLevel > apiLevel;
+    }
+}
diff --git a/src/com/android/networkstack/apishim/SocketUtilsShim.java b/apishim/common/com/android/networkstack/apishim/SocketUtilsShim.java
similarity index 72%
rename from src/com/android/networkstack/apishim/SocketUtilsShim.java
rename to apishim/common/com/android/networkstack/apishim/SocketUtilsShim.java
index 34b5f40..33cb5f8 100644
--- a/src/com/android/networkstack/apishim/SocketUtilsShim.java
+++ b/apishim/common/com/android/networkstack/apishim/SocketUtilsShim.java
@@ -30,18 +30,6 @@
  */
 public interface SocketUtilsShim {
     /**
-     * Create a new instance of SocketUtilsShim.
-     */
-    @NonNull
-    static SocketUtilsShim newInstance() {
-        // TODO: when the R API is finalized, rename the API 29 shim to SocketUtilsCompat, and
-        // return it here instead of SocketUtilsShimImpl for devices with Build.VERSION <= 29.
-        // For now, the switch between implementations is done at build time (swapping the java file
-        // with another), since production modules should not be built with a non-finalized API.
-        return new SocketUtilsShimImpl();
-    }
-
-    /**
      * @see android.net.util.SocketUtils#makePacketSocketAddress(int, int, byte[])
      */
     @NonNull
diff --git a/apishim/jarjar-rules-compat.txt b/apishim/jarjar-rules-compat.txt
new file mode 100644
index 0000000..3e54c12
--- /dev/null
+++ b/apishim/jarjar-rules-compat.txt
@@ -0,0 +1,7 @@
+# jarjar rules to use on API stable builds.
+# Use the latest stable apishim package as the main apishim package, to replace and avoid building
+# the unstable, non-compatibility shims.
+# Once API 30 is stable, apishim/30/com.android.networkstack.apishim should be moved to the
+# com.android.networkstack.apishim.api30 package, a new apishim/31/com.android.networkstack.apishim
+# package should be created, and this rule should reference api30.
+rule com.android.networkstack.apishim.api29.** com.android.networkstack.apishim.@1
\ No newline at end of file
diff --git a/common/moduleutils/Android.bp b/common/moduleutils/Android.bp
new file mode 100644
index 0000000..6117149
--- /dev/null
+++ b/common/moduleutils/Android.bp
@@ -0,0 +1,21 @@
+//
+// Copyright (C) 2019 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.
+//
+
+// Shared utility sources to be used by multiple network modules
+filegroup {
+    name: "net-module-utils-srcs",
+    srcs: ["src/**/*.java"],
+}
\ No newline at end of file
diff --git a/src/android/net/util/SharedLog.java b/common/moduleutils/src/android/net/util/SharedLog.java
similarity index 100%
rename from src/android/net/util/SharedLog.java
rename to common/moduleutils/src/android/net/util/SharedLog.java
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp
index c82f751..12c5355 100644
--- a/common/networkstackclient/Android.bp
+++ b/common/networkstackclient/Android.bp
@@ -87,6 +87,6 @@
     ],
     static_libs: [
         "ipmemorystore-aidl-interfaces-V3-java",
-        "networkstack-aidl-interfaces-java",
+        "networkstack-aidl-interfaces-unstable-java",
     ],
 }
diff --git a/src/android/net/NetworkStackIpMemoryStore.java b/src/android/net/NetworkStackIpMemoryStore.java
index 41715b2..850faaf 100644
--- a/src/android/net/NetworkStackIpMemoryStore.java
+++ b/src/android/net/NetworkStackIpMemoryStore.java
@@ -16,9 +16,10 @@
 
 package android.net;
 
-import android.annotation.NonNull;
 import android.content.Context;
 
+import androidx.annotation.NonNull;
+
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index 2f74ad6..165e37b 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -33,7 +33,6 @@
 import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
 import static com.android.server.util.NetworkStackConstants.IPV6_ADDR_LEN;
 
-import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -59,6 +58,8 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.HexDump;
@@ -104,6 +105,7 @@
         public boolean multicastFilter;
         public boolean ieee802_3Filter;
         public int[] ethTypeBlackList;
+        public int minRdnssLifetimeSec;
     }
 
     // Enums describing the outcome of receiving an RA packet.
@@ -357,6 +359,9 @@
     private final boolean mDrop802_3Frames;
     private final int[] mEthTypeBlackList;
 
+    // Ignore non-zero RDNSS lifetimes below this value.
+    private final int mMinRdnssLifetimeSec;
+
     // Detects doze mode state transitions.
     private final BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
         @Override
@@ -387,6 +392,7 @@
         mInterfaceParams = ifParams;
         mMulticastFilter = config.multicastFilter;
         mDrop802_3Frames = config.ieee802_3Filter;
+        mMinRdnssLifetimeSec = config.minRdnssLifetimeSec;
         mContext = context;
 
         if (mApfCapabilities.hasDataAccess()) {
@@ -513,9 +519,9 @@
         public final Type type;
         /** Offset into the packet at which this section begins. */
         public final int start;
-        /** Length of this section. */
+        /** Length of this section in bytes. */
         public final int length;
-        /** If this is a lifetime, the ICMP option that the defined it. 0 for router lifetime. */
+        /** If this is a lifetime, the ICMP option that defined it. 0 for router lifetime. */
         public final int option;
         /** If this is a lifetime, the lifetime value. */
         public final long lifetime;
@@ -747,6 +753,19 @@
             return lifetime;
         }
 
+        // http://b/66928272 http://b/65056012
+        // DnsServerRepository ignores RDNSS servers with lifetimes that are too low. Ignore these
+        // lifetimes for the purpose of filter lifetime calculations.
+        private boolean shouldIgnoreLifetime(int optionType, long lifetime) {
+            return optionType == ICMP6_RDNSS_OPTION_TYPE
+                    && lifetime != 0 && lifetime < mMinRdnssLifetimeSec;
+        }
+
+        private boolean isRelevantLifetime(PacketSection section) {
+            return section.type == PacketSection.Type.LIFETIME
+                    && !shouldIgnoreLifetime(section.option, section.lifetime);
+        }
+
         // Note that this parses RA and may throw InvalidRaException (from
         // Buffer.position(int) or due to an invalid-length option) or IndexOutOfBoundsException
         // (from ByteBuffer.get(int) ) if parsing encounters something non-compliant with
@@ -784,8 +803,9 @@
             addLifetimeSection(ICMP6_RA_ROUTER_LIFETIME_LEN, 0, routerLifetime);
             builder.updateRouterLifetime(routerLifetime);
 
-            // Ensures that the RA is not truncated.
-            mPacket.position(ICMP6_RA_OPTION_OFFSET);
+            // Add remaining fields (reachable time and retransmission timer) to match section.
+            addMatchUntil(ICMP6_RA_OPTION_OFFSET);
+
             while (mPacket.hasRemaining()) {
                 final int position = mPacket.position();
                 final int optionType = getUint8(mPacket, position);
@@ -796,7 +816,7 @@
                         mPrefixOptionOffsets.add(position);
 
                         // Parse valid lifetime
-                        addMatchSection(ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN);
+                        addMatchSection(ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET);
                         lifetime = getUint32(mPacket, mPacket.position());
                         addLifetimeSection(ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN,
                                 ICMP6_PREFIX_OPTION_TYPE, lifetime);
@@ -860,7 +880,7 @@
         long minLifetime() {
             long minLifetime = Long.MAX_VALUE;
             for (PacketSection section : mPacketSections) {
-                if (section.type == PacketSection.Type.LIFETIME) {
+                if (isRelevantLifetime(section)) {
                     minLifetime = Math.min(minLifetime, section.lifetime);
                 }
             }
@@ -900,9 +920,10 @@
                                     section.start + section.length),
                             nextFilterLabel);
                 }
+
                 // Generate code to test the lifetimes haven't gone down too far.
-                // The packet is accepted if any of its lifetimes are lower than filterLifetime.
-                if (section.type == PacketSection.Type.LIFETIME) {
+                // The packet is accepted if any non-ignored lifetime is lower than filterLifetime.
+                if (isRelevantLifetime(section)) {
                     switch (section.length) {
                         case 4: gen.addLoad32(Register.R0, section.start); break;
                         case 2: gen.addLoad16(Register.R0, section.start); break;
@@ -1911,6 +1932,7 @@
         pw.println("Capabilities: " + mApfCapabilities);
         pw.println("Receive thread: " + (mReceiveThread != null ? "RUNNING" : "STOPPED"));
         pw.println("Multicast: " + (mMulticastFilter ? "DROP" : "ALLOW"));
+        pw.println("Minimum RDNSS lifetime: " + mMinRdnssLifetimeSec);
         try {
             pw.println("IPv4 address: " + InetAddress.getByAddress(mIPv4Address).getHostAddress());
         } catch (UnknownHostException|NullPointerException e) {}
diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java
index e88c7cc..0d56d63 100644
--- a/src/android/net/dhcp/DhcpClient.java
+++ b/src/android/net/dhcp/DhcpClient.java
@@ -46,8 +46,6 @@
 
 import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.Context;
 import android.net.DhcpResults;
 import android.net.InetAddresses;
@@ -73,6 +71,9 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.HexDump;
 import com.android.internal.util.MessageUtils;
@@ -829,41 +830,19 @@
                 (leaseTimeMillis > 0) ? SystemClock.elapsedRealtime() + leaseTimeMillis : 0;
     }
 
-    /**
-     * Retransmits packets using jittered exponential backoff with an optional timeout. Packet
-     * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. If a subclass
-     * sets mTimeout to a positive value, then timeout() is called by an AlarmManager alarm mTimeout
-     * milliseconds after entering the state. Kicks and timeouts are cancelled when leaving the
-     * state.
-     *
-     * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a
-     * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET
-     * sent by the receive thread. They may also set mTimeout and implement timeout.
-     */
-    abstract class PacketRetransmittingState extends LoggingState {
-
-        private int mTimer;
+    abstract class TimeoutState extends LoggingState {
         protected int mTimeout = 0;
 
         @Override
         public void enter() {
             super.enter();
-            initTimer();
             maybeInitTimeout();
-            sendMessage(CMD_KICK);
         }
 
         @Override
         public boolean processMessage(Message message) {
             super.processMessage(message);
             switch (message.what) {
-                case CMD_KICK:
-                    sendPacket();
-                    scheduleKick();
-                    return HANDLED;
-                case CMD_RECEIVED_PACKET:
-                    receivePacket((DhcpPacket) message.obj);
-                    return HANDLED;
                 case CMD_TIMEOUT:
                     timeout();
                     return HANDLED;
@@ -875,12 +854,66 @@
         @Override
         public void exit() {
             super.exit();
-            mKickAlarm.cancel();
             mTimeoutAlarm.cancel();
         }
 
-        abstract protected boolean sendPacket();
-        abstract protected void receivePacket(DhcpPacket packet);
+        protected abstract void timeout();
+        private void maybeInitTimeout() {
+            if (mTimeout > 0) {
+                long alarmTime = SystemClock.elapsedRealtime() + mTimeout;
+                mTimeoutAlarm.schedule(alarmTime);
+            }
+        }
+    }
+
+    /**
+     * Retransmits packets using jittered exponential backoff with an optional timeout. Packet
+     * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. If a subclass
+     * sets mTimeout to a positive value, then timeout() is called by an AlarmManager alarm mTimeout
+     * milliseconds after entering the state. Kicks and timeouts are cancelled when leaving the
+     * state.
+     *
+     * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a
+     * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET
+     * sent by the receive thread. They may also set mTimeout and implement timeout.
+     */
+    abstract class PacketRetransmittingState extends TimeoutState {
+        private int mTimer;
+
+        @Override
+        public void enter() {
+            super.enter();
+            initTimer();
+            sendMessage(CMD_KICK);
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            if (super.processMessage(message) == HANDLED) {
+                return HANDLED;
+            }
+
+            switch (message.what) {
+                case CMD_KICK:
+                    sendPacket();
+                    scheduleKick();
+                    return HANDLED;
+                case CMD_RECEIVED_PACKET:
+                    receivePacket((DhcpPacket) message.obj);
+                    return HANDLED;
+                default:
+                    return NOT_HANDLED;
+            }
+        }
+
+        @Override
+        public void exit() {
+            super.exit();
+            mKickAlarm.cancel();
+        }
+
+        protected abstract boolean sendPacket();
+        protected abstract void receivePacket(DhcpPacket packet);
         protected void timeout() {}
 
         protected void initTimer() {
@@ -903,13 +936,6 @@
                 mTimer = MAX_TIMEOUT_MS;
             }
         }
-
-        protected void maybeInitTimeout() {
-            if (mTimeout > 0) {
-                long alarmTime = SystemClock.elapsedRealtime() + mTimeout;
-                mTimeoutAlarm.schedule(alarmTime);
-            }
-        }
     }
 
     class ObtainingConfigurationState extends LoggingState {
@@ -1167,7 +1193,7 @@
             startNewTransaction();
         }
 
-        abstract protected Inet4Address packetDestination();
+        protected abstract Inet4Address packetDestination();
 
         protected boolean sendPacket() {
             return sendRequestPacket(
diff --git a/src/android/net/dhcp/DhcpLease.java b/src/android/net/dhcp/DhcpLease.java
index 6849cfa..37d9cc0 100644
--- a/src/android/net/dhcp/DhcpLease.java
+++ b/src/android/net/dhcp/DhcpLease.java
@@ -16,12 +16,13 @@
 
 package android.net.dhcp;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.net.MacAddress;
 import android.os.SystemClock;
 import android.text.TextUtils;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.util.HexDump;
 
 import java.net.Inet4Address;
diff --git a/src/android/net/dhcp/DhcpLeaseRepository.java b/src/android/net/dhcp/DhcpLeaseRepository.java
index 0a15cd7..d22193b 100644
--- a/src/android/net/dhcp/DhcpLeaseRepository.java
+++ b/src/android/net/dhcp/DhcpLeaseRepository.java
@@ -27,14 +27,15 @@
 
 import static java.lang.Math.min;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.net.IpPrefix;
 import android.net.MacAddress;
 import android.net.dhcp.DhcpServer.Clock;
 import android.net.util.SharedLog;
 import android.util.ArrayMap;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.net.Inet4Address;
 import java.util.ArrayList;
 import java.util.Collections;
diff --git a/src/android/net/dhcp/DhcpPacket.java b/src/android/net/dhcp/DhcpPacket.java
index 79e1c4f..efce6a5 100644
--- a/src/android/net/dhcp/DhcpPacket.java
+++ b/src/android/net/dhcp/DhcpPacket.java
@@ -3,7 +3,6 @@
 import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ALL;
 import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
 
-import android.annotation.Nullable;
 import android.net.DhcpResults;
 import android.net.LinkAddress;
 import android.net.metrics.DhcpErrorEvent;
@@ -13,7 +12,8 @@
 import android.system.OsConstants;
 import android.text.TextUtils;
 
-import com.android.internal.annotations.VisibleForTesting;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import java.io.UnsupportedEncodingException;
 import java.net.Inet4Address;
diff --git a/src/android/net/dhcp/DhcpPacketListener.java b/src/android/net/dhcp/DhcpPacketListener.java
index 97d26c7..c9235da 100644
--- a/src/android/net/dhcp/DhcpPacketListener.java
+++ b/src/android/net/dhcp/DhcpPacketListener.java
@@ -16,12 +16,13 @@
 
 package android.net.dhcp;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.net.util.FdEventsReader;
 import android.os.Handler;
 import android.system.Os;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.io.FileDescriptor;
 import java.net.Inet4Address;
 import java.net.InetSocketAddress;
diff --git a/src/android/net/dhcp/DhcpServer.java b/src/android/net/dhcp/DhcpServer.java
index f75fe18..37dd972 100644
--- a/src/android/net/dhcp/DhcpServer.java
+++ b/src/android/net/dhcp/DhcpServer.java
@@ -38,8 +38,6 @@
 
 import static java.lang.Integer.toUnsignedLong;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.net.INetworkStackStatusCallback;
 import android.net.MacAddress;
 import android.net.TrafficStats;
@@ -57,7 +55,10 @@
 import android.text.TextUtils;
 import android.util.Pair;
 
-import com.android.internal.annotations.VisibleForTesting;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.internal.util.HexDump;
 
 import java.io.FileDescriptor;
diff --git a/src/android/net/dhcp/DhcpServingParams.java b/src/android/net/dhcp/DhcpServingParams.java
index 230b693..eafe44e 100644
--- a/src/android/net/dhcp/DhcpServingParams.java
+++ b/src/android/net/dhcp/DhcpServingParams.java
@@ -25,13 +25,14 @@
 
 import static java.lang.Integer.toUnsignedLong;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.shared.Inet4AddressUtils;
 import android.util.ArraySet;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.net.Inet4Address;
 import java.util.Arrays;
 import java.util.Collections;
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index 98e1e49..8808ee2 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -18,10 +18,10 @@
 
 import static android.net.RouteInfo.RTN_UNICAST;
 import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 
 import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission;
 
-import android.annotation.NonNull;
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.DhcpResults;
@@ -43,7 +43,9 @@
 import android.net.shared.InitialConfiguration;
 import android.net.shared.ProvisioningConfiguration;
 import android.net.util.InterfaceParams;
+import android.net.util.NetworkStackUtils;
 import android.net.util.SharedLog;
+import android.os.Build;
 import android.os.ConditionVariable;
 import android.os.IBinder;
 import android.os.Message;
@@ -56,6 +58,8 @@
 import android.util.Pair;
 import android.util.SparseArray;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IState;
 import com.android.internal.util.IndentingPrintWriter;
@@ -64,6 +68,7 @@
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.WakeupMessage;
+import com.android.networkstack.apishim.ShimUtils;
 import com.android.server.NetworkObserverRegistry;
 import com.android.server.NetworkStackService.NetworkStackServiceManager;
 
@@ -312,9 +317,15 @@
     // IpClient shares a handler with DhcpClient: commands must not overlap
     public static final int DHCPCLIENT_CMD_BASE = 1000;
 
+    // Settings and default values.
     private static final int MAX_LOG_RECORDS = 500;
     private static final int MAX_PACKET_RECORDS = 100;
 
+    @VisibleForTesting
+    static final String CONFIG_MIN_RDNSS_LIFETIME = "ipclient_min_rdnss_lifetime";
+    private static final int DEFAULT_MIN_RDNSS_LIFETIME =
+            ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q) ? 120 : 0;
+
     private static final boolean NO_CALLBACKS = false;
     private static final boolean SEND_CALLBACKS = true;
 
@@ -354,6 +365,9 @@
     private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
     private final InterfaceController mInterfaceCtrl;
 
+    // Ignore nonzero RDNSS option lifetimes below this value. 0 = disabled.
+    private final int mMinRdnssLifetimeSec;
+
     private InterfaceParams mInterfaceParams;
 
     /**
@@ -410,6 +424,14 @@
                 NetworkStackIpMemoryStore ipMemoryStore) {
             return new DhcpClient.Dependencies(ipMemoryStore);
         }
+
+        /**
+         * Read an integer DeviceConfig property.
+         */
+        public int getDeviceConfigPropertyInt(String name, int defaultValue) {
+            return NetworkStackUtils.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, name,
+                    defaultValue);
+        }
     }
 
     public IpClient(Context context, String ifName, IIpClientCallbacks callback,
@@ -448,9 +470,15 @@
         mNetd = deps.getNetd(mContext);
         mInterfaceCtrl = new InterfaceController(mInterfaceName, mNetd, mLog);
 
+        mMinRdnssLifetimeSec = mDependencies.getDeviceConfigPropertyInt(
+                CONFIG_MIN_RDNSS_LIFETIME, DEFAULT_MIN_RDNSS_LIFETIME);
+
+        IpClientLinkObserver.Configuration config = new IpClientLinkObserver.Configuration(
+                mMinRdnssLifetimeSec);
+
         mLinkObserver = new IpClientLinkObserver(
                 mInterfaceName,
-                () -> sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED)) {
+                () -> sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED), config) {
             @Override
             public void onInterfaceAdded(String iface) {
                 super.onInterfaceAdded(iface);
@@ -1499,6 +1527,7 @@
             // Get the Configuration for ApfFilter from Context
             apfConfig.ieee802_3Filter = ApfCapabilities.getApfDrop8023Frames();
             apfConfig.ethTypeBlackList = ApfCapabilities.getApfEtherTypeBlackList();
+            apfConfig.minRdnssLifetimeSec = mMinRdnssLifetimeSec;
             mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback);
             // TODO: investigate the effects of any multicast filtering racing/interfering with the
             // rest of this IP configuration startup.
diff --git a/src/android/net/ip/IpClientLinkObserver.java b/src/android/net/ip/IpClientLinkObserver.java
index 8ad99aa..02bf5f0 100644
--- a/src/android/net/ip/IpClientLinkObserver.java
+++ b/src/android/net/ip/IpClientLinkObserver.java
@@ -71,20 +71,31 @@
         void update();
     }
 
+    /** Configuration parameters for IpClientLinkObserver. */
+    public static class Configuration {
+        public final int minRdnssLifetime;
+
+        public Configuration(int minRdnssLifetime) {
+            this.minRdnssLifetime = minRdnssLifetime;
+        }
+    }
+
     private final String mInterfaceName;
     private final Callback mCallback;
     private final LinkProperties mLinkProperties;
     private DnsServerRepository mDnsServerRepository;
+    private final Configuration mConfig;
 
     private static final boolean DBG = false;
 
-    public IpClientLinkObserver(String iface, Callback callback) {
+    public IpClientLinkObserver(String iface, Callback callback, Configuration config) {
         mTag = "NetlinkTracker/" + iface;
         mInterfaceName = iface;
         mCallback = callback;
         mLinkProperties = new LinkProperties();
         mLinkProperties.setInterfaceName(mInterfaceName);
-        mDnsServerRepository = new DnsServerRepository();
+        mConfig = config;
+        mDnsServerRepository = new DnsServerRepository(config.minRdnssLifetime);
     }
 
     private void maybeLog(String operation, String iface, LinkAddress address) {
@@ -197,7 +208,7 @@
         // Clear the repository before clearing mLinkProperties. That way, if a clear() happens
         // while interfaceDnsServerInfo() is being called, we'll end up with no DNS servers in
         // mLinkProperties, as desired.
-        mDnsServerRepository = new DnsServerRepository();
+        mDnsServerRepository = new DnsServerRepository(mConfig.minRdnssLifetime);
         mLinkProperties.clear();
         mLinkProperties.setInterfaceName(mInterfaceName);
     }
@@ -260,10 +271,16 @@
          */
         private HashMap<InetAddress, DnsServerEntry> mIndex;
 
-        DnsServerRepository() {
+        /**
+         * Minimum (non-zero) RDNSS lifetime to accept.
+         */
+        private final int mMinLifetime;
+
+        DnsServerRepository(int minLifetime) {
             mCurrentServers = new HashSet<>();
             mAllServers = new ArrayList<>(NUM_SERVERS);
             mIndex = new HashMap<>(NUM_SERVERS);
+            mMinLifetime = minLifetime;
         }
 
         /** Sets the DNS servers of the provided LinkProperties object to the current servers. */
@@ -277,6 +294,9 @@
          * @param addresses the string representations of the IP addresses of DNS servers to use.
          */
         public synchronized boolean addServers(long lifetime, String[] addresses) {
+            // If the servers are below the minimum lifetime, don't change anything.
+            if (lifetime != 0 && lifetime < mMinLifetime) return false;
+
             // The lifetime is actually an unsigned 32-bit number, but Java doesn't have unsigned.
             // Technically 0xffffffff (the maximum) is special and means "forever", but 2^32 seconds
             // (136 years) is close enough.
@@ -326,7 +346,7 @@
 
             // Prune excess or expired entries.
             for (int i = mAllServers.size() - 1; i >= 0; i--) {
-                if (i >= NUM_SERVERS || mAllServers.get(i).expiry < now) {
+                if (i >= NUM_SERVERS || mAllServers.get(i).expiry <= now) {
                     DnsServerEntry removed = mAllServers.remove(i);
                     mIndex.remove(removed.address);
                     changed |= mCurrentServers.remove(removed.address);
diff --git a/src/android/net/util/FdEventsReader.java b/src/android/net/util/FdEventsReader.java
index e82c69b..5a1154f 100644
--- a/src/android/net/util/FdEventsReader.java
+++ b/src/android/net/util/FdEventsReader.java
@@ -19,14 +19,15 @@
 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.MessageQueue;
 import android.system.ErrnoException;
 import android.system.OsConstants;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 
diff --git a/src/android/net/util/NetworkStackUtils.java b/src/android/net/util/NetworkStackUtils.java
index 0a18c0e..b2deefd 100644
--- a/src/android/net/util/NetworkStackUtils.java
+++ b/src/android/net/util/NetworkStackUtils.java
@@ -16,14 +16,15 @@
 
 package android.net.util;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.provider.DeviceConfig;
 import android.util.Log;
 import android.util.SparseArray;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.net.Inet4Address;
diff --git a/src/com/android/networkstack/metrics/DataStallDetectionStats.java b/src/com/android/networkstack/metrics/DataStallDetectionStats.java
index 2523ecd..7a1f9ac 100644
--- a/src/com/android/networkstack/metrics/DataStallDetectionStats.java
+++ b/src/com/android/networkstack/metrics/DataStallDetectionStats.java
@@ -16,11 +16,12 @@
 
 package com.android.networkstack.metrics;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.net.util.NetworkStackUtils;
 import android.net.wifi.WifiInfo;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.util.HexDump;
 import com.android.server.connectivity.nano.CellularData;
 import com.android.server.connectivity.nano.DataStallEventProto;
diff --git a/src/com/android/networkstack/metrics/DataStallStatsUtils.java b/src/com/android/networkstack/metrics/DataStallStatsUtils.java
index 59e8fd3..e7a6c3d 100644
--- a/src/com/android/networkstack/metrics/DataStallStatsUtils.java
+++ b/src/com/android/networkstack/metrics/DataStallStatsUtils.java
@@ -16,11 +16,11 @@
 
 package com.android.networkstack.metrics;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.net.captiveportal.CaptivePortalProbeResult;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.util.HexDump;
diff --git a/src/com/android/networkstack/util/DnsUtils.java b/src/com/android/networkstack/util/DnsUtils.java
index 759807b..c47ccc1 100644
--- a/src/com/android/networkstack/util/DnsUtils.java
+++ b/src/com/android/networkstack/util/DnsUtils.java
@@ -20,14 +20,15 @@
 import static android.net.DnsResolver.TYPE_A;
 import static android.net.DnsResolver.TYPE_AAAA;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.net.DnsResolver;
 import android.net.Network;
 import android.net.TrafficStats;
 import android.net.util.Stopwatch;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.util.TrafficStatsConstants;
 import com.android.server.connectivity.NetworkMonitor.DnsLogFunc;
 
diff --git a/src/com/android/server/NetworkObserverRegistry.java b/src/com/android/server/NetworkObserverRegistry.java
index b83bc5c..dcb42c0 100644
--- a/src/com/android/server/NetworkObserverRegistry.java
+++ b/src/com/android/server/NetworkObserverRegistry.java
@@ -17,7 +17,6 @@
 
 import static android.net.RouteInfo.RTN_UNICAST;
 
-import android.annotation.NonNull;
 import android.net.INetd;
 import android.net.INetdUnsolicitedEventListener;
 import android.net.InetAddresses;
@@ -28,6 +27,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
diff --git a/src/com/android/server/NetworkStackService.java b/src/com/android/server/NetworkStackService.java
index 9e5ed74..74a5da0 100644
--- a/src/com/android/server/NetworkStackService.java
+++ b/src/com/android/server/NetworkStackService.java
@@ -22,8 +22,6 @@
 
 import static com.android.server.util.PermissionUtil.checkDumpPermission;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
@@ -49,6 +47,8 @@
 import android.os.RemoteException;
 import android.util.ArraySet;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.annotations.GuardedBy;
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java
index e08170a..bda0c9a 100644
--- a/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -68,8 +68,6 @@
 import static com.android.networkstack.util.DnsUtils.PRIVATE_DNS_PROBE_HOST_SUFFIX;
 import static com.android.networkstack.util.DnsUtils.TYPE_ADDRCONFIG;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -115,6 +113,8 @@
 import android.util.Pair;
 
 import androidx.annotation.ArrayRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
 
 import com.android.internal.annotations.VisibleForTesting;
diff --git a/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java b/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
index 4896ef2..834aa2d 100644
--- a/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
+++ b/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
@@ -19,8 +19,6 @@
 import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
 import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
@@ -34,6 +32,9 @@
 import android.net.ipmemorystore.Status;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.net.InetAddress;
diff --git a/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java b/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
index 55ab8d4..8d57d61 100644
--- a/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
+++ b/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
@@ -24,8 +24,6 @@
 import static com.android.server.connectivity.ipmemorystore.IpMemoryStoreDatabase.EXPIRY_ERROR;
 import static com.android.server.connectivity.ipmemorystore.RegularMaintenanceJobService.InterruptMaintenance;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.Context;
 import android.database.SQLException;
 import android.database.sqlite.SQLiteDatabase;
@@ -44,6 +42,9 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.File;
diff --git a/src/com/android/server/connectivity/ipmemorystore/Utils.java b/src/com/android/server/connectivity/ipmemorystore/Utils.java
index 9cbf490..d8a46ed 100644
--- a/src/com/android/server/connectivity/ipmemorystore/Utils.java
+++ b/src/com/android/server/connectivity/ipmemorystore/Utils.java
@@ -16,10 +16,11 @@
 
 package com.android.server.connectivity.ipmemorystore;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.net.ipmemorystore.Blob;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 /** {@hide} */
 public class Utils {
     /** Pretty print */
diff --git a/src/com/android/server/util/NetworkStackConstants.java b/src/com/android/server/util/NetworkStackConstants.java
index 3174a9b..c011b6a 100644
--- a/src/com/android/server/util/NetworkStackConstants.java
+++ b/src/com/android/server/util/NetworkStackConstants.java
@@ -95,6 +95,7 @@
      */
     public static final int IPV6_ADDR_LEN = 16;
     public static final int IPV6_HEADER_LEN = 40;
+    public static final int IPV6_LEN_OFFSET = 4;
     public static final int IPV6_PROTOCOL_OFFSET = 6;
     public static final int IPV6_SRC_ADDR_OFFSET = 8;
     public static final int IPV6_DST_ADDR_OFFSET = 24;
@@ -108,6 +109,7 @@
      *     - https://tools.ietf.org/html/rfc4861
      */
     public static final int ICMPV6_HEADER_MIN_LEN = 4;
+    public static final int ICMPV6_CHECKSUM_OFFSET = 2;
     public static final int ICMPV6_ECHO_REPLY_TYPE = 129;
     public static final int ICMPV6_ECHO_REQUEST_TYPE = 128;
     public static final int ICMPV6_ROUTER_SOLICITATION    = 133;
@@ -116,9 +118,14 @@
     public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136;
     public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8;
     public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8;
-    public static final int ICMPV6_ND_OPTION_SLLA = 1;
-    public static final int ICMPV6_ND_OPTION_TLLA = 2;
-    public static final int ICMPV6_ND_OPTION_MTU  = 5;
+    public static final int ICMPV6_ND_OPTION_SLLA  = 1;
+    public static final int ICMPV6_ND_OPTION_TLLA  = 2;
+    public static final int ICMPV6_ND_OPTION_PIO   = 3;
+    public static final int ICMPV6_ND_OPTION_MTU   = 5;
+    public static final int ICMPV6_ND_OPTION_RDNSS = 25;
+
+
+    public static final int ICMPV6_RA_HEADER_LEN = 16;
 
     /**
      * UDP constants.
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
index f32171c..f34c26e 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
@@ -26,6 +26,24 @@
 import static android.net.ipmemorystore.Status.SUCCESS;
 import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
 import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
+import static android.system.OsConstants.ETH_P_IPV6;
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.IPPROTO_TCP;
+
+import static com.android.internal.util.BitUtils.uint16;
+import static com.android.server.util.NetworkStackConstants.ETHER_HEADER_LEN;
+import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_OFFSET;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_CHECKSUM_OFFSET;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_RDNSS;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_RA_HEADER_LEN;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
+import static com.android.server.util.NetworkStackConstants.IPV6_HEADER_LEN;
+import static com.android.server.util.NetworkStackConstants.IPV6_LEN_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
 
 import static junit.framework.Assert.fail;
 
@@ -37,6 +55,7 @@
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -50,7 +69,9 @@
 import android.net.ConnectivityManager;
 import android.net.INetd;
 import android.net.InetAddresses;
+import android.net.IpPrefix;
 import android.net.LinkProperties;
+import android.net.MacAddress;
 import android.net.NetworkStackIpMemoryStore;
 import android.net.TestNetworkInterface;
 import android.net.TestNetworkManager;
@@ -63,6 +84,7 @@
 import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener;
 import android.net.ipmemorystore.Status;
 import android.net.shared.ProvisioningConfiguration;
+import android.net.util.IpUtils;
 import android.net.util.NetworkStackUtils;
 import android.net.util.PacketReader;
 import android.os.Handler;
@@ -96,10 +118,12 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.net.Inet4Address;
+import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
@@ -212,6 +236,8 @@
     private class Dependencies extends IpClient.Dependencies {
         private boolean mIsDhcpLeaseCacheEnabled;
         private boolean mIsDhcpRapidCommitEnabled;
+        // Can't use SparseIntArray, it doesn't have an easy way to know if a key is not present.
+        private HashMap<String, Integer> mIntConfigProperties = new HashMap<>();
 
         public void setDhcpLeaseCacheEnabled(final boolean enable) {
             mIsDhcpLeaseCacheEnabled = enable;
@@ -251,6 +277,19 @@
                 }
             };
         }
+
+        @Override
+        public int getDeviceConfigPropertyInt(String name, int defaultValue) {
+            Integer value = mIntConfigProperties.get(name);
+            if (value == null) {
+                throw new IllegalStateException("Non-mocked device config property " + name);
+            }
+            return value;
+        }
+
+        public void setDeviceConfigProperty(String name, int value) {
+            mIntConfigProperties.put(name, value);
+        }
     }
 
     @Before
@@ -265,6 +304,8 @@
         when(mNetworkStackServiceManager.getIpMemoryStoreService())
                 .thenReturn(mIpMemoryStoreService);
 
+        mDependencies.setDeviceConfigProperty(IpClient.CONFIG_MIN_RDNSS_LIFETIME, 67);
+
         setUpTapInterface();
         setUpIpClient();
     }
@@ -385,7 +426,10 @@
 
     private void sendResponse(final ByteBuffer packet) throws IOException {
         try (FileOutputStream out = new FileOutputStream(mPacketReader.createFd())) {
-            out.write(packet.array());
+            byte[] packetBytes = new byte[packet.limit()];
+            packet.get(packetBytes);
+            packet.flip();  // So we can reuse it in the future.
+            out.write(packetBytes);
         }
     }
 
@@ -693,4 +737,180 @@
         verify(mCb).onProvisioningFailure(any());
         verify(mCb, never()).setNeighborDiscoveryOffload(true);
     }
-}
+
+    private boolean isRouterSolicitation(final byte[] packetBytes) {
+        ByteBuffer packet = ByteBuffer.wrap(packetBytes);
+        return packet.getShort(ETHER_TYPE_OFFSET) == (short) ETH_P_IPV6
+                && packet.get(ETHER_HEADER_LEN + IPV6_PROTOCOL_OFFSET) == (byte) IPPROTO_ICMPV6
+                && packet.get(ETHER_HEADER_LEN + IPV6_HEADER_LEN)
+                        == (byte) ICMPV6_ROUTER_SOLICITATION;
+    }
+
+    private void waitForRouterSolicitation() throws ParseException {
+        byte[] packet;
+        while ((packet = mPacketReader.popPacket(PACKET_TIMEOUT_MS)) != null) {
+            if (isRouterSolicitation(packet)) return;
+        }
+        fail("No router solicitation received on interface within timeout");
+    }
+
+    // TODO: move this and the following method to a common location and use them in ApfTest.
+    private static ByteBuffer buildPioOption(int valid, int preferred, String prefixString)
+            throws Exception {
+        final int optLen = 4;
+        IpPrefix prefix = new IpPrefix(prefixString);
+        ByteBuffer option = ByteBuffer.allocate(optLen * ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR);
+        option.put((byte) ICMPV6_ND_OPTION_PIO);      // Type
+        option.put((byte) optLen);                    // Length in 8-byte units
+        option.put((byte) prefix.getPrefixLength());  // Prefix length
+        option.put((byte) 0b11000000);                // L = 1, A = 1
+        option.putInt(valid);
+        option.putInt(preferred);
+        option.putInt(0);                             // Reserved
+        option.put(prefix.getRawAddress());
+        option.flip();
+        return option;
+    }
+
+    private static ByteBuffer buildRdnssOption(int lifetime, String... servers) throws Exception {
+        final int optLen = 1 + 2 * servers.length;
+        ByteBuffer option = ByteBuffer.allocate(optLen * ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR);
+        option.put((byte) ICMPV6_ND_OPTION_RDNSS);  // Type
+        option.put((byte) optLen);                  // Length in 8-byte units
+        option.putShort((short) 0);                 // Reserved
+        option.putInt(lifetime);                    // Lifetime
+        for (String server : servers) {
+            option.put(InetAddress.getByName(server).getAddress());
+        }
+        option.flip();
+        return option;
+    }
+
+    // HACK: these functions are here because IpUtils#transportChecksum is private. Even if we made
+    // that public, it won't be available on Q devices, and this test needs to run on Q devices.
+    // TODO: move the IpUtils code to frameworks/lib/net and link it statically.
+    private static int checksumFold(int sum) {
+        while (sum > 0xffff) {
+            sum = (sum >> 16) + (sum & 0xffff);
+        }
+        return sum;
+    }
+
+    private static short checksumAdjust(short checksum, short oldWord, short newWord) {
+        checksum = (short) ~checksum;
+        int tempSum = checksumFold(uint16(checksum) + uint16(newWord) + 0xffff - uint16(oldWord));
+        return (short) ~tempSum;
+    }
+
+    private static short icmpv6Checksum(ByteBuffer buf, int ipOffset, int transportOffset,
+            int transportLen) {
+        // The ICMPv6 checksum is the same as the TCP checksum, except the pseudo-header uses
+        // 58 (ICMPv6) instead of 6 (TCP). Calculate the TCP checksum, and then do an incremental
+        // checksum adjustment  for the change in the next header byte.
+        short checksum = IpUtils.tcpChecksum(buf, ipOffset, transportOffset, transportLen);
+        return checksumAdjust(checksum, (short) IPPROTO_TCP, (short) IPPROTO_ICMPV6);
+    }
+
+    private static ByteBuffer buildRaPacket(ByteBuffer... options) throws Exception {
+        final MacAddress srcMac = MacAddress.fromString("33:33:00:00:00:01");
+        final MacAddress dstMac = MacAddress.fromString("01:02:03:04:05:06");
+        final byte[] routerLinkLocal = InetAddresses.parseNumericAddress("fe80::1").getAddress();
+        final byte[] allNodes = InetAddresses.parseNumericAddress("ff02::1").getAddress();
+
+        final ByteBuffer packet = ByteBuffer.allocate(TEST_DEFAULT_MTU);
+        int icmpLen = ICMPV6_RA_HEADER_LEN;
+
+        // Ethernet header.
+        packet.put(srcMac.toByteArray());
+        packet.put(dstMac.toByteArray());
+        packet.putShort((short) ETHER_TYPE_IPV6);
+
+        // IPv6 header.
+        packet.putInt(0x600abcde);                       // Version, traffic class, flowlabel
+        packet.putShort((short) 0);                      // Length, TBD
+        packet.put((byte) IPPROTO_ICMPV6);               // Next header
+        packet.put((byte) 0xff);                         // Hop limit
+        packet.put(routerLinkLocal);                     // Source address
+        packet.put(allNodes);                            // Destination address
+
+        // Router advertisement.
+        packet.put((byte) ICMPV6_ROUTER_ADVERTISEMENT);  // ICMP type
+        packet.put((byte) 0);                            // ICMP code
+        packet.putShort((short) 0);                      // Checksum, TBD
+        packet.put((byte) 0);                            // Hop limit, unspecified
+        packet.put((byte) 0);                            // M=0, O=0
+        packet.putShort((short) 1800);                   // Router lifetime
+        packet.putInt(0);                                // Reachable time, unspecified
+        packet.putInt(100);                              // Retrans time 100ms.
+
+        for (ByteBuffer option : options) {
+            packet.put(option);
+            option.clear();  // So we can reuse it in a future packet.
+            icmpLen += option.capacity();
+        }
+
+        // Populate length and checksum fields.
+        final int transportOffset = ETHER_HEADER_LEN + IPV6_HEADER_LEN;
+        final short checksum = icmpv6Checksum(packet, ETHER_HEADER_LEN, transportOffset, icmpLen);
+        packet.putShort(ETHER_HEADER_LEN + IPV6_LEN_OFFSET, (short) icmpLen);
+        packet.putShort(transportOffset + ICMPV6_CHECKSUM_OFFSET, checksum);
+
+        packet.flip();
+        return packet;
+    }
+
+    @Test
+    public void testRaRdnss() throws Exception {
+        // Speed up the test by removing router_solicitation_delay.
+        // We don't need to restore the default value because the interface is removed in tearDown.
+        // TODO: speed up further by not waiting for RA but keying off first IPv6 packet.
+        mNetd.setProcSysNet(INetd.IPV6, INetd.CONF, mIfaceName, "router_solicitation_delay", "0");
+
+        ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+                .withoutIpReachabilityMonitor()
+                .withoutIPv4()
+                .build();
+        mIpc.startProvisioning(config);
+
+        final String dnsServer = "2001:4860:4860::64";
+        final String lowlifeDnsServer = "2001:4860:4860::6464";
+
+        final ByteBuffer pio = buildPioOption(600, 300, "2001:db8:1::/64");
+        ByteBuffer rdnss1 = buildRdnssOption(60, lowlifeDnsServer);
+        ByteBuffer rdnss2 = buildRdnssOption(600, dnsServer);
+        ByteBuffer ra = buildRaPacket(pio, rdnss1, rdnss2);
+
+        waitForRouterSolicitation();
+        sendResponse(ra);
+
+        ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class);
+        verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture());
+        LinkProperties lp = captor.getValue();
+
+        // Expect that DNS servers with lifetimes below CONFIG_MIN_RDNSS_LIFETIME are not accepted.
+        assertNotNull(lp);
+        assertEquals(1, lp.getDnsServers().size());
+        assertTrue(lp.getDnsServers().contains(InetAddress.getByName(dnsServer)));
+        reset(mCb);
+
+        // If the RDNSS lifetime is above the minimum, the DNS server is accepted.
+        rdnss1 = buildRdnssOption(68, lowlifeDnsServer);
+        ra = buildRaPacket(pio, rdnss1, rdnss2);
+        sendResponse(ra);
+        verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(captor.capture());
+        lp = captor.getValue();
+        assertNotNull(lp);
+        assertEquals(2, lp.getDnsServers().size());
+        assertTrue(lp.getDnsServers().contains(InetAddress.getByName(dnsServer)));
+        assertTrue(lp.getDnsServers().contains(InetAddress.getByName(lowlifeDnsServer)));
+        reset(mCb);
+
+        // Expect that setting RDNSS lifetime of 0 causes loss of provisioning.
+        rdnss1 = buildRdnssOption(0, dnsServer);
+        rdnss2 = buildRdnssOption(0, lowlifeDnsServer);
+        ra = buildRaPacket(pio, rdnss1, rdnss2);
+        sendResponse(ra);
+
+        verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningFailure(captor.capture());
+        lp = captor.getValue();
+    }}
diff --git a/tests/lib/Android.bp b/tests/lib/Android.bp
index 1db4054..249088f 100644
--- a/tests/lib/Android.bp
+++ b/tests/lib/Android.bp
@@ -15,6 +15,19 @@
 //
 
 java_library {
+    name: "net-tests-utils-multivariant",
+    srcs: [
+        "multivariant/**/*.java",
+        "multivariant/**/*.kt",
+    ],
+    host_supported: true,
+    static_libs: [
+        "kotlin-test",
+        "junit",
+    ],
+}
+
+java_library {
     name: "net-tests-utils",
     srcs: [
         "src/**/*.java",
@@ -22,8 +35,7 @@
     ],
     defaults: ["lib_mockito_extended"],
     static_libs: [
-        "kotlin-test",
-        "junit",
+        "net-tests-utils-multivariant",
     ],
 }
 
diff --git a/tests/lib/src/com/android/testutils/ConcurrentUtils.kt b/tests/lib/multivariant/com/android/testutils/ConcurrentUtils.kt
similarity index 100%
rename from tests/lib/src/com/android/testutils/ConcurrentUtils.kt
rename to tests/lib/multivariant/com/android/testutils/ConcurrentUtils.kt
diff --git a/tests/lib/src/com/android/testutils/ExceptionUtils.java b/tests/lib/multivariant/com/android/testutils/ExceptionUtils.java
similarity index 100%
rename from tests/lib/src/com/android/testutils/ExceptionUtils.java
rename to tests/lib/multivariant/com/android/testutils/ExceptionUtils.java
diff --git a/tests/lib/multivariant/com/android/testutils/FileUtils.kt b/tests/lib/multivariant/com/android/testutils/FileUtils.kt
new file mode 100644
index 0000000..678f977
--- /dev/null
+++ b/tests/lib/multivariant/com/android/testutils/FileUtils.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 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.testutils
+
+// This function is private because the 2 is hardcoded here, and is not correct if not called
+// directly from __LINE__ or __FILE__.
+private fun callerStackTrace(): StackTraceElement = try {
+    throw RuntimeException()
+} catch (e: RuntimeException) {
+    e.stackTrace[2] // 0 is here, 1 is get() in __FILE__ or __LINE__
+}
+val __FILE__: String get() = callerStackTrace().fileName
+val __LINE__: Int get() = callerStackTrace().lineNumber
diff --git a/tests/lib/src/com/android/testutils/MiscAsserts.kt b/tests/lib/multivariant/com/android/testutils/MiscAsserts.kt
similarity index 98%
rename from tests/lib/src/com/android/testutils/MiscAsserts.kt
rename to tests/lib/multivariant/com/android/testutils/MiscAsserts.kt
index 5019dcd..17540a8 100644
--- a/tests/lib/src/com/android/testutils/MiscAsserts.kt
+++ b/tests/lib/multivariant/com/android/testutils/MiscAsserts.kt
@@ -16,7 +16,6 @@
 
 package com.android.testutils
 
-import android.util.Log
 import com.android.testutils.ExceptionUtils.ThrowingRunnable
 import java.lang.reflect.Modifier
 import kotlin.system.measureTimeMillis
@@ -80,7 +79,6 @@
 fun assertRunsInAtMost(descr: String, timeLimit: Long, fn: () -> Unit) {
     val timeTaken = measureTimeMillis(fn)
     val msg = String.format("%s: took %dms, limit was %dms", descr, timeTaken, timeLimit)
-    Log.d(TAG, msg)
     assertTrue(timeTaken <= timeLimit, msg)
 }
 
diff --git a/tests/lib/src/com/android/testutils/TrackRecord.kt b/tests/lib/multivariant/com/android/testutils/TrackRecord.kt
similarity index 100%
rename from tests/lib/src/com/android/testutils/TrackRecord.kt
rename to tests/lib/multivariant/com/android/testutils/TrackRecord.kt
diff --git a/tests/lib/src/com/android/testutils/FileUtils.kt b/tests/lib/src/com/android/testutils/FileUtils.kt
deleted file mode 100644
index edd8d83..0000000
--- a/tests/lib/src/com/android/testutils/FileUtils.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.android.testutils
-
-// This function is private because the 2 is hardcoded here, and is not correct if not called
-// directly from __LINE__ or __FILE__.
-private fun callerStackTrace(): StackTraceElement = try {
-    throw RuntimeException()
-} catch (e: RuntimeException) {
-    e.stackTrace[2] // 0 is here, 1 is get() in __FILE__ or __LINE__
-}
-val __FILE__: String get() = callerStackTrace().fileName
-val __LINE__: Int get() = callerStackTrace().lineNumber
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 03bcf95..a63bc61 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -14,18 +14,16 @@
 // limitations under the License.
 //
 
-android_test {
-    name: "NetworkStackTests",
+java_defaults {
+    name: "NetworkStackTestsDefaults",
     certificate: "platform",
     srcs: ["src/**/*.java", "src/**/*.kt"],
-    test_suites: ["device-tests"],
     resource_dirs: ["res"],
     static_libs: [
         "androidx.test.rules",
         "kotlin-reflect",
         "mockito-target-extended-minus-junit4",
         "net-tests-utils",
-        "NetworkStackApiCurrentLib",
         "testables",
     ],
     libs: [
@@ -42,6 +40,21 @@
     ],
 }
 
+android_test {
+    name: "NetworkStackNextTests",
+    srcs: [], // TODO: tests that only apply to the current, non-stable API can be added here
+    test_suites: ["device-tests"],
+    defaults: ["NetworkStackTestsDefaults"],
+    static_libs: ["NetworkStackApiCurrentLib"],
+}
+
+android_test {
+    name: "NetworkStackTests",
+    test_suites: ["device-tests"],
+    defaults: ["NetworkStackTestsDefaults"],
+    static_libs: ["NetworkStackApiStableLib"],
+}
+
 // Additional dependencies of libnetworkstackutilsjni that are not provided by the system when
 // running as a test application.
 // Using java_defaults as jni_libs does not support filegroups.
@@ -80,6 +93,6 @@
         "libutilscallstack",
         "libziparchive",
         "libz",
-        "netd_aidl_interface-V2-cpp",
+        "netd_aidl_interface-cpp",
     ],
 }
diff --git a/tests/unit/jni/Android.bp b/tests/unit/jni/Android.bp
index 9520520..4bef7c8 100644
--- a/tests/unit/jni/Android.bp
+++ b/tests/unit/jni/Android.bp
@@ -32,7 +32,7 @@
         "liblog",
         "libcutils",
         "libnativehelper",
-        "netd_aidl_interface-V2-cpp",
+        "netd_aidl_interface-cpp",
     ],
     static_libs: [
         "libapf",
diff --git a/tests/unit/res/raw/test.db b/tests/unit/res/raw/test.db
new file mode 100644
index 0000000..05cc77b
--- /dev/null
+++ b/tests/unit/res/raw/test.db
Binary files differ
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java
index 8b02b49..006ad19 100644
--- a/tests/unit/src/android/net/apf/ApfTest.java
+++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -130,6 +130,8 @@
     private static final boolean DROP_802_3_FRAMES = true;
     private static final boolean ALLOW_802_3_FRAMES = false;
 
+    private static final int MIN_RDNSS_LIFETIME_SEC = 0;
+
     // Constants for opcode encoding
     private static final byte LI_OP   = (byte)(13 << 3);
     private static final byte LDDW_OP = (byte)(22 << 3);
@@ -146,6 +148,8 @@
         config.multicastFilter = ALLOW_MULTICAST;
         config.ieee802_3Filter = ALLOW_802_3_FRAMES;
         config.ethTypeBlackList = new int[0];
+        config.minRdnssLifetimeSec = MIN_RDNSS_LIFETIME_SEC;
+        config.minRdnssLifetimeSec = 67;
         return config;
     }
 
@@ -1008,15 +1012,16 @@
     private static final byte[] ETH_BROADCAST_MAC_ADDRESS =
             {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
 
+    private static final int IP_HEADER_OFFSET = ETH_HEADER_LEN;
+
     private static final int IPV4_HEADER_LEN          = 20;
-    private static final int IPV4_VERSION_IHL_OFFSET  = ETH_HEADER_LEN + 0;
-    private static final int IPV4_TOTAL_LENGTH_OFFSET = ETH_HEADER_LEN + 2;
-    private static final int IPV4_PROTOCOL_OFFSET     = ETH_HEADER_LEN + 9;
-    private static final int IPV4_SRC_ADDR_OFFSET     = ETH_HEADER_LEN + 12;
-    private static final int IPV4_DEST_ADDR_OFFSET    = ETH_HEADER_LEN + 16;
+    private static final int IPV4_TOTAL_LENGTH_OFFSET = IP_HEADER_OFFSET + 2;
+    private static final int IPV4_PROTOCOL_OFFSET     = IP_HEADER_OFFSET + 9;
+    private static final int IPV4_SRC_ADDR_OFFSET     = IP_HEADER_OFFSET + 12;
+    private static final int IPV4_DEST_ADDR_OFFSET    = IP_HEADER_OFFSET + 16;
 
     private static final int IPV4_TCP_HEADER_LEN           = 20;
-    private static final int IPV4_TCP_HEADER_OFFSET        = ETH_HEADER_LEN + IPV4_HEADER_LEN;
+    private static final int IPV4_TCP_HEADER_OFFSET        = IP_HEADER_OFFSET + IPV4_HEADER_LEN;
     private static final int IPV4_TCP_SRC_PORT_OFFSET      = IPV4_TCP_HEADER_OFFSET + 0;
     private static final int IPV4_TCP_DEST_PORT_OFFSET     = IPV4_TCP_HEADER_OFFSET + 2;
     private static final int IPV4_TCP_SEQ_NUM_OFFSET       = IPV4_TCP_HEADER_OFFSET + 4;
@@ -1024,7 +1029,7 @@
     private static final int IPV4_TCP_HEADER_LENGTH_OFFSET = IPV4_TCP_HEADER_OFFSET + 12;
     private static final int IPV4_TCP_HEADER_FLAG_OFFSET   = IPV4_TCP_HEADER_OFFSET + 13;
 
-    private static final int IPV4_UDP_HEADER_OFFSET    = ETH_HEADER_LEN + IPV4_HEADER_LEN;;
+    private static final int IPV4_UDP_HEADER_OFFSET    = IP_HEADER_OFFSET + IPV4_HEADER_LEN;
     private static final int IPV4_UDP_SRC_PORT_OFFSET  = IPV4_UDP_HEADER_OFFSET + 0;
     private static final int IPV4_UDP_DEST_PORT_OFFSET = IPV4_UDP_HEADER_OFFSET + 2;
     private static final int IPV4_UDP_LENGTH_OFFSET    = IPV4_UDP_HEADER_OFFSET + 4;
@@ -1033,11 +1038,11 @@
             {(byte) 255, (byte) 255, (byte) 255, (byte) 255};
 
     private static final int IPV6_HEADER_LEN             = 40;
-    private static final int IPV6_PAYLOAD_LENGTH_OFFSET  = ETH_HEADER_LEN + 4;
-    private static final int IPV6_NEXT_HEADER_OFFSET     = ETH_HEADER_LEN + 6;
-    private static final int IPV6_SRC_ADDR_OFFSET        = ETH_HEADER_LEN + 8;
-    private static final int IPV6_DEST_ADDR_OFFSET       = ETH_HEADER_LEN + 24;
-    private static final int IPV6_TCP_HEADER_OFFSET      = ETH_HEADER_LEN + IPV6_HEADER_LEN;
+    private static final int IPV6_PAYLOAD_LENGTH_OFFSET  = IP_HEADER_OFFSET + 4;
+    private static final int IPV6_NEXT_HEADER_OFFSET     = IP_HEADER_OFFSET + 6;
+    private static final int IPV6_SRC_ADDR_OFFSET        = IP_HEADER_OFFSET + 8;
+    private static final int IPV6_DEST_ADDR_OFFSET       = IP_HEADER_OFFSET + 24;
+    private static final int IPV6_TCP_HEADER_OFFSET      = IP_HEADER_OFFSET + IPV6_HEADER_LEN;
     private static final int IPV6_TCP_SRC_PORT_OFFSET    = IPV6_TCP_HEADER_OFFSET + 0;
     private static final int IPV6_TCP_DEST_PORT_OFFSET   = IPV6_TCP_HEADER_OFFSET + 2;
     private static final int IPV6_TCP_SEQ_NUM_OFFSET     = IPV6_TCP_HEADER_OFFSET + 4;
@@ -1048,19 +1053,23 @@
     private static final byte[] IPV6_ALL_ROUTERS_ADDRESS =
             { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 };
 
-    private static final int ICMP6_TYPE_OFFSET           = ETH_HEADER_LEN + IPV6_HEADER_LEN;
+    private static final int ICMP6_TYPE_OFFSET           = IP_HEADER_OFFSET + IPV6_HEADER_LEN;
     private static final int ICMP6_ROUTER_SOLICITATION   = 133;
     private static final int ICMP6_ROUTER_ADVERTISEMENT  = 134;
     private static final int ICMP6_NEIGHBOR_SOLICITATION = 135;
     private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136;
 
     private static final int ICMP6_RA_HEADER_LEN = 16;
-    private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET =
-            ETH_HEADER_LEN + IPV6_HEADER_LEN + 6;
     private static final int ICMP6_RA_CHECKSUM_OFFSET =
-            ETH_HEADER_LEN + IPV6_HEADER_LEN + 2;
+            IP_HEADER_OFFSET + IPV6_HEADER_LEN + 2;
+    private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET =
+            IP_HEADER_OFFSET + IPV6_HEADER_LEN + 6;
+    private static final int ICMP6_RA_REACHABLE_TIME_OFFSET =
+            IP_HEADER_OFFSET + IPV6_HEADER_LEN + 8;
+    private static final int ICMP6_RA_RETRANSMISSION_TIMER_OFFSET =
+            IP_HEADER_OFFSET + IPV6_HEADER_LEN + 12;
     private static final int ICMP6_RA_OPTION_OFFSET =
-            ETH_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN;
+            IP_HEADER_OFFSET + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN;
 
     private static final int ICMP6_PREFIX_OPTION_TYPE                      = 3;
     private static final int ICMP6_PREFIX_OPTION_LEN                       = 32;
@@ -1124,6 +1133,30 @@
         return apfFilter;
     }
 
+    private static void setIpv4VersionFields(ByteBuffer packet) {
+        packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IP);
+        packet.put(IP_HEADER_OFFSET, (byte) 0x45);
+    }
+
+    private static void setIpv6VersionFields(ByteBuffer packet) {
+        packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IPV6);
+        packet.put(IP_HEADER_OFFSET, (byte) 0x60);
+    }
+
+    private static ByteBuffer makeIpv4Packet(int proto) {
+        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+        setIpv4VersionFields(packet);
+        packet.put(IPV4_PROTOCOL_OFFSET, (byte) proto);
+        return packet;
+    }
+
+    private static ByteBuffer makeIpv6Packet(int nextHeader) {
+        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+        setIpv6VersionFields(packet);
+        packet.put(IPV6_NEXT_HEADER_OFFSET, (byte) nextHeader);
+        return packet;
+    }
+
     @Test
     public void testApfFilterIPv4() throws Exception {
         MockIpClientCallback ipClientCallback = new MockIpClientCallback();
@@ -1157,7 +1190,7 @@
         // Verify multicast/broadcast IPv4, not DHCP to us, is dropped
         put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
         assertDrop(program, packet.array());
-        packet.put(IPV4_VERSION_IHL_OFFSET, (byte)0x45);
+        packet.put(IP_HEADER_OFFSET, (byte) 0x45);
         assertDrop(program, packet.array());
         packet.put(IPV4_PROTOCOL_OFFSET, (byte)IPPROTO_UDP);
         assertDrop(program, packet.array());
@@ -1189,8 +1222,7 @@
         byte[] program = ipClientCallback.getApfProgram();
 
         // Verify empty IPv6 packet is passed
-        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+        ByteBuffer packet = makeIpv6Packet(IPPROTO_UDP);
         assertPass(program, packet.array());
 
         // Verify empty ICMPv6 packet is passed
@@ -1234,28 +1266,25 @@
         byte[] program = ipClientCallback.getApfProgram();
 
         // Construct IPv4 and IPv6 multicast packets.
-        ByteBuffer mcastv4packet = ByteBuffer.wrap(new byte[100]);
-        mcastv4packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+        ByteBuffer mcastv4packet = makeIpv4Packet(IPPROTO_UDP);
         put(mcastv4packet, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr);
 
-        ByteBuffer mcastv6packet = ByteBuffer.wrap(new byte[100]);
-        mcastv6packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
-        mcastv6packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_UDP);
+        ByteBuffer mcastv6packet = makeIpv6Packet(IPPROTO_UDP);
         put(mcastv6packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr);
 
         // Construct IPv4 broadcast packet.
-        ByteBuffer bcastv4packet1 = ByteBuffer.wrap(new byte[100]);
+        ByteBuffer bcastv4packet1 = makeIpv4Packet(IPPROTO_UDP);
         bcastv4packet1.put(ETH_BROADCAST_MAC_ADDRESS);
         bcastv4packet1.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
         put(bcastv4packet1, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr);
 
-        ByteBuffer bcastv4packet2 = ByteBuffer.wrap(new byte[100]);
+        ByteBuffer bcastv4packet2 = makeIpv4Packet(IPPROTO_UDP);
         bcastv4packet2.put(ETH_BROADCAST_MAC_ADDRESS);
         bcastv4packet2.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
         put(bcastv4packet2, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
 
         // Construct IPv4 broadcast with L2 unicast address packet (b/30231088).
-        ByteBuffer bcastv4unicastl2packet = ByteBuffer.wrap(new byte[100]);
+        ByteBuffer bcastv4unicastl2packet = makeIpv4Packet(IPPROTO_UDP);
         bcastv4unicastl2packet.put(TestApfFilter.MOCK_MAC_ADDR);
         bcastv4unicastl2packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
         put(bcastv4unicastl2packet, IPV4_DEST_ADDR_OFFSET, broadcastIpv4Addr);
@@ -1314,9 +1343,7 @@
 
         // Construct a multicast ICMPv6 ECHO request.
         final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};
-        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
-        packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
+        ByteBuffer packet = makeIpv6Packet(IPPROTO_ICMPV6);
         packet.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ECHO_REQUEST_TYPE);
         put(packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr);
 
@@ -1333,6 +1360,8 @@
 
         // However, we should still let through all other ICMPv6 types.
         ByteBuffer raPacket = ByteBuffer.wrap(packet.array().clone());
+        setIpv6VersionFields(packet);
+        packet.put(IPV6_NEXT_HEADER_OFFSET, (byte) IPPROTO_ICMPV6);
         raPacket.put(ICMP6_TYPE_OFFSET, (byte) NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT);
         assertPass(ipClientCallback.getApfProgram(), raPacket.array());
 
@@ -1357,11 +1386,11 @@
         assertPass(program, packet.array());
 
         // Verify empty packet with IPv4 is passed
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+        setIpv4VersionFields(packet);
         assertPass(program, packet.array());
 
         // Verify empty IPv6 packet is passed
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+        setIpv6VersionFields(packet);
         assertPass(program, packet.array());
 
         // Now turn on the filter
@@ -1377,11 +1406,11 @@
         assertDrop(program, packet.array());
 
         // Verify that IPv4 (as example of Ethernet II) frame will pass
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+        setIpv4VersionFields(packet);
         assertPass(program, packet.array());
 
         // Verify that IPv6 (as example of Ethernet II) frame will pass
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+        setIpv6VersionFields(packet);
         assertPass(program, packet.array());
 
         apfFilter.shutdown();
@@ -1404,11 +1433,11 @@
         assertPass(program, packet.array());
 
         // Verify empty packet with IPv4 is passed
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+        setIpv4VersionFields(packet);
         assertPass(program, packet.array());
 
         // Verify empty IPv6 packet is passed
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+        setIpv6VersionFields(packet);
         assertPass(program, packet.array());
 
         // Now add IPv4 to the black list
@@ -1419,11 +1448,11 @@
         program = ipClientCallback.getApfProgram();
 
         // Verify that IPv4 frame will be dropped
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+        setIpv4VersionFields(packet);
         assertDrop(program, packet.array());
 
         // Verify that IPv6 frame will pass
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+        setIpv6VersionFields(packet);
         assertPass(program, packet.array());
 
         // Now let us have both IPv4 and IPv6 in the black list
@@ -1434,11 +1463,11 @@
         program = ipClientCallback.getApfProgram();
 
         // Verify that IPv4 frame will be dropped
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+        setIpv4VersionFields(packet);
         assertDrop(program, packet.array());
 
         // Verify that IPv6 frame will be dropped
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+        setIpv6VersionFields(packet);
         assertDrop(program, packet.array());
 
         apfFilter.shutdown();
@@ -1572,18 +1601,18 @@
         // src: 10.0.0.6, port: 54321
         // dst: 10.0.0.5, port: 12345
         assertDrop(program,
-                ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
+                ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
                         dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */));
         // Verify IPv4 non-keepalive ack packet from the same source address is passed
         assertPass(program,
-                ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
+                ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
                         dstPort, srcPort, ackNum + 100, seqNum, 0 /* dataLength */));
         assertPass(program,
-                ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
+                ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
                         dstPort, srcPort, ackNum, seqNum + 1, 10 /* dataLength */));
         // Verify IPv4 packet from another address is passed
         assertPass(program,
-                ipv4Packet(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, anotherSrcPort,
+                ipv4TcpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, anotherSrcPort,
                         anotherDstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */));
 
         // Remove IPv4 keepalive filter
@@ -1611,15 +1640,15 @@
             // src: 2404:0:0:0:0:0:faf2, port: 54321
             // dst: 2404:0:0:0:0:0:faf1, port: 12345
             assertDrop(program,
-                    ipv6Packet(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
+                    ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
                             dstPort, srcPort, ackNum, seqNum + 1));
             // Verify IPv6 non-keepalive ack packet from the same source address is passed
             assertPass(program,
-                    ipv6Packet(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
+                    ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
                             dstPort, srcPort, ackNum + 100, seqNum));
             // Verify IPv6 packet from another address is passed
             assertPass(program,
-                    ipv6Packet(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, anotherSrcPort,
+                    ipv6TcpPacket(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, anotherSrcPort,
                             anotherDstPort, anotherSeqNum, anotherAckNum));
 
             // Remove IPv6 keepalive filter
@@ -1634,30 +1663,30 @@
             // src: 10.0.0.6, port: 54321
             // dst: 10.0.0.5, port: 12345
             assertDrop(program,
-                    ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
+                    ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
                             dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */));
             // Verify IPv4 non-keepalive ack packet from the same source address is passed
             assertPass(program,
-                    ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
+                    ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
                             dstPort, srcPort, ackNum + 100, seqNum, 0 /* dataLength */));
             // Verify IPv4 packet from another address is passed
             assertPass(program,
-                    ipv4Packet(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, anotherSrcPort,
+                    ipv4TcpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, anotherSrcPort,
                             anotherDstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */));
 
             // Verify IPv6 keepalive ack packet is dropped
             // src: 2404:0:0:0:0:0:faf2, port: 54321
             // dst: 2404:0:0:0:0:0:faf1, port: 12345
             assertDrop(program,
-                    ipv6Packet(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
+                    ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
                             dstPort, srcPort, ackNum, seqNum + 1));
             // Verify IPv6 non-keepalive ack packet from the same source address is passed
             assertPass(program,
-                    ipv6Packet(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
+                    ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
                             dstPort, srcPort, ackNum + 100, seqNum));
             // Verify IPv6 packet from another address is passed
             assertPass(program,
-                    ipv6Packet(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, anotherSrcPort,
+                    ipv6TcpPacket(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, anotherSrcPort,
                             anotherDstPort, anotherSeqNum, anotherAckNum));
 
             // Remove keepalive filters
@@ -1671,32 +1700,29 @@
 
         // Verify IPv4, IPv6 packets are passed
         assertPass(program,
-                ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
+                ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
                         dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */));
         assertPass(program,
-                ipv6Packet(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
+                ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
                         dstPort, srcPort, ackNum, seqNum + 1));
         assertPass(program,
-                ipv4Packet(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, srcPort,
+                ipv4TcpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, srcPort,
                         dstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */));
         assertPass(program,
-                ipv6Packet(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, srcPort,
+                ipv6TcpPacket(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, srcPort,
                         dstPort, anotherSeqNum, anotherAckNum));
 
         apfFilter.shutdown();
     }
 
-    private static byte[] ipv4Packet(byte[] sip, byte[] dip, int sport,
+    private static byte[] ipv4TcpPacket(byte[] sip, byte[] dip, int sport,
             int dport, int seq, int ack, int dataLength) {
         final int totalLength = dataLength + IPV4_HEADER_LEN + IPV4_TCP_HEADER_LEN;
 
         ByteBuffer packet = ByteBuffer.wrap(new byte[totalLength + ETH_HEADER_LEN]);
 
-        // ether type
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IP);
-
-        // IPv4 header
-        packet.put(IPV4_VERSION_IHL_OFFSET, (byte) 0x45);
+        // Ethertype and IPv4 header
+        setIpv4VersionFields(packet);
         packet.putShort(IPV4_TOTAL_LENGTH_OFFSET, (short) totalLength);
         packet.put(IPV4_PROTOCOL_OFFSET, (byte) IPPROTO_TCP);
         put(packet, IPV4_SRC_ADDR_OFFSET, sip);
@@ -1713,10 +1739,11 @@
         return packet.array();
     }
 
-    private static byte[] ipv6Packet(byte[] sip, byte[] tip, int sport,
+    private static byte[] ipv6TcpPacket(byte[] sip, byte[] tip, int sport,
             int dport, int seq, int ack) {
         ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IPV6);
+        setIpv6VersionFields(packet);
+        packet.put(IPV6_NEXT_HEADER_OFFSET, (byte) IPPROTO_TCP);
         put(packet, IPV6_SRC_ADDR_OFFSET, sip);
         put(packet, IPV6_DEST_ADDR_OFFSET, tip);
         packet.putShort(IPV6_TCP_SRC_PORT_OFFSET, (short) sport);
@@ -1787,11 +1814,8 @@
         final int udpLength = UDP_HEADER_LEN + dataLength;
         ByteBuffer packet = ByteBuffer.wrap(new byte[totalLength + ETH_HEADER_LEN]);
 
-        // ether type
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IP);
-
-        // IPv4 header
-        packet.put(IPV4_VERSION_IHL_OFFSET, (byte) 0x45);
+        // Ethertype and IPv4 header
+        setIpv4VersionFields(packet);
         packet.putShort(IPV4_TOTAL_LENGTH_OFFSET, (short) totalLength);
         packet.put(IPV4_PROTOCOL_OFFSET, (byte) IPPROTO_UDP);
         put(packet, IPV4_SRC_ADDR_OFFSET, sip);
@@ -1927,13 +1951,13 @@
         assertDrop(program, packet.array());
         packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, originalChecksum);
 
-        // Verify other changes to RA make it not match filter
-        final byte originalFirstByte = packet.get(0);
-        packet.put(0, (byte)-1);
+        // Verify other changes to RA (e.g., a change in the source address) make it not match.
+        final int offset = IPV6_SRC_ADDR_OFFSET + 5;
+        final byte originalByte = packet.get(offset);
+        packet.put(offset, (byte) (~originalByte));
         assertPass(program, packet.array());
-        packet.put(0, (byte)0);
+        packet.put(offset, originalByte);
         assertDrop(program, packet.array());
-        packet.put(0, originalFirstByte);
     }
 
     // Test that when ApfFilter is shown the given packet, it generates a program to filter it
@@ -1984,6 +2008,25 @@
         ipClientCallback.assertNoProgramUpdate();
     }
 
+    private ByteBuffer makeBaseRaPacket() {
+        ByteBuffer basePacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]);
+        final int ROUTER_LIFETIME = 1000;
+        final int VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET = ETH_HEADER_LEN;
+        // IPv6, traffic class = 0, flow label = 0x12345
+        final int VERSION_TRAFFIC_CLASS_FLOW_LABEL = 0x60012345;
+
+        basePacket.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IPV6);
+        basePacket.putInt(VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET,
+                VERSION_TRAFFIC_CLASS_FLOW_LABEL);
+        basePacket.put(IPV6_NEXT_HEADER_OFFSET, (byte) IPPROTO_ICMPV6);
+        basePacket.put(ICMP6_TYPE_OFFSET, (byte) ICMP6_ROUTER_ADVERTISEMENT);
+        basePacket.putShort(ICMP6_RA_ROUTER_LIFETIME_OFFSET, (short) ROUTER_LIFETIME);
+        basePacket.position(IPV6_DEST_ADDR_OFFSET);
+        basePacket.put(IPV6_ALL_NODES_ADDRESS);
+
+        return basePacket;
+    }
+
     @Test
     public void testApfFilterRa() throws Exception {
         MockIpClientCallback ipClientCallback = new MockIpClientCallback();
@@ -2005,15 +2048,7 @@
         final int VERSION_TRAFFIC_CLASS_FLOW_LABEL = 0x60012345;
 
         // Verify RA is passed the first time
-        ByteBuffer basePacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]);
-        basePacket.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
-        basePacket.putInt(VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET,
-                VERSION_TRAFFIC_CLASS_FLOW_LABEL);
-        basePacket.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
-        basePacket.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_ADVERTISEMENT);
-        basePacket.putShort(ICMP6_RA_ROUTER_LIFETIME_OFFSET, (short)ROUTER_LIFETIME);
-        basePacket.position(IPV6_DEST_ADDR_OFFSET);
-        basePacket.put(IPV6_ALL_NODES_ADDRESS);
+        ByteBuffer basePacket = makeBaseRaPacket();
         assertPass(program, basePacket.array());
 
         verifyRaLifetime(apfFilter, ipClientCallback, basePacket, ROUTER_LIFETIME);
@@ -2059,6 +2094,16 @@
         verifyRaLifetime(apfFilter, ipClientCallback, rdnssOptionPacket, RDNSS_LIFETIME);
         verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, RDNSS_LIFETIME, -1));
 
+        final int lowLifetime = 60;
+        ByteBuffer lowLifetimeRdnssOptionPacket = ByteBuffer.wrap(
+                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN + IPV6_ADDR_LEN]);
+        basePacket.clear();
+        lowLifetimeRdnssOptionPacket.put(basePacket);
+        addRdnssOption(lowLifetimeRdnssOptionPacket, lowLifetime, "2620:fe::9");
+        verifyRaLifetime(apfFilter, ipClientCallback, lowLifetimeRdnssOptionPacket,
+                ROUTER_LIFETIME);
+        verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, lowLifetime, -1));
+
         ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap(
                 new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN + IPV6_ADDR_LEN]);
         basePacket.clear();
@@ -2067,6 +2112,16 @@
         verifyRaLifetime(apfFilter, ipClientCallback, routeInfoOptionPacket, ROUTE_LIFETIME);
         verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, ROUTE_LIFETIME, -1, -1));
 
+        // Check that RIOs differing only in the first 4 bytes are different.
+        ByteBuffer similarRouteInfoOptionPacket = ByteBuffer.wrap(
+                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN + IPV6_ADDR_LEN]);
+        basePacket.clear();
+        similarRouteInfoOptionPacket.put(basePacket);
+        addRioOption(similarRouteInfoOptionPacket, ROUTE_LIFETIME, "64:ff9b::/64");
+        // Packet should be passed because it is different.
+        program = ipClientCallback.getApfProgram();
+        assertPass(program, similarRouteInfoOptionPacket.array());
+
         ByteBuffer dnsslOptionPacket = ByteBuffer.wrap(
                 new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
         basePacket.clear();
@@ -2082,12 +2137,13 @@
         verifyRaLifetime(apfFilter, ipClientCallback, largeRaPacket, 300);
         verifyRaEvent(new RaEvent(1800, 600, 300, 1200, 7200, -1));
 
-        // Verify that current program filters all the RAs:
+        // Verify that current program filters all the RAs (note: ApfFilter.MAX_RAS == 10).
         program = ipClientCallback.getApfProgram();
         verifyRaLifetime(program, basePacket, ROUTER_LIFETIME);
         verifyRaLifetime(program, newFlowLabelPacket, ROUTER_LIFETIME);
         verifyRaLifetime(program, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
         verifyRaLifetime(program, rdnssOptionPacket, RDNSS_LIFETIME);
+        verifyRaLifetime(program, lowLifetimeRdnssOptionPacket, ROUTER_LIFETIME);
         verifyRaLifetime(program, routeInfoOptionPacket, ROUTE_LIFETIME);
         verifyRaLifetime(program, dnsslOptionPacket, ROUTER_LIFETIME);
         verifyRaLifetime(program, largeRaPacket, 300);
@@ -2095,6 +2151,46 @@
         apfFilter.shutdown();
     }
 
+    @Test
+    public void testRaWithDifferentReachableTimeAndRetransTimer() throws Exception {
+        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+        final ApfConfiguration config = getDefaultConfig();
+        config.multicastFilter = DROP_MULTICAST;
+        config.ieee802_3Filter = DROP_802_3_FRAMES;
+        final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+        byte[] program = ipClientCallback.getApfProgram();
+        final int RA_REACHABLE_TIME = 1800;
+        final int RA_RETRANSMISSION_TIMER = 1234;
+
+        // Create an Ra packet without options
+        // Reachable time = 1800, retransmission timer = 1234
+        ByteBuffer raPacket = makeBaseRaPacket();
+        raPacket.position(ICMP6_RA_REACHABLE_TIME_OFFSET);
+        raPacket.putInt(RA_REACHABLE_TIME);
+        raPacket.putInt(RA_RETRANSMISSION_TIMER);
+        // First RA passes filter
+        assertPass(program, raPacket.array());
+
+        // Assume apf is shown the given RA, it generates program to filter it.
+        ipClientCallback.resetApfProgramWait();
+        apfFilter.pretendPacketReceived(raPacket.array());
+        program = ipClientCallback.getApfProgram();
+        assertDrop(program, raPacket.array());
+
+        // A packet with different reachable time should be passed.
+        // Reachable time = 2300, retransmission timer = 1234
+        raPacket.clear();
+        raPacket.putInt(ICMP6_RA_REACHABLE_TIME_OFFSET, RA_REACHABLE_TIME + 500);
+        assertPass(program, raPacket.array());
+
+        // A packet with different retransmission timer should be passed.
+        // Reachable time = 1800, retransmission timer = 2234
+        raPacket.clear();
+        raPacket.putInt(ICMP6_RA_REACHABLE_TIME_OFFSET, RA_REACHABLE_TIME);
+        raPacket.putInt(ICMP6_RA_RETRANSMISSION_TIMER_OFFSET, RA_RETRANSMISSION_TIMER + 1000);
+        assertPass(program, raPacket.array());
+    }
+
     /**
      * Stage a file for testing, i.e. make it native accessible. Given a resource ID,
      * copy that resource into the app's data directory and return the path to it.
diff --git a/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
index ba86fcf..4e34969 100644
--- a/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -48,6 +48,10 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.networkstack.tests.R;
+
+import libcore.io.Streams;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -56,6 +60,9 @@
 import org.mockito.MockitoAnnotations;
 
 import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.lang.reflect.Modifier;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
@@ -73,12 +80,15 @@
 public class IpMemoryStoreServiceTest {
     private static final String TEST_CLIENT_ID = "testClientId";
     private static final String TEST_DATA_NAME = "testData";
+    private static final String TEST_DATABASE_NAME = "test.db";
 
     private static final int TEST_DATABASE_SIZE_THRESHOLD = 100 * 1024; //100KB
     private static final int DEFAULT_TIMEOUT_MS = 5000;
     private static final int LONG_TIMEOUT_MS = 30000;
     private static final int FAKE_KEY_COUNT = 20;
     private static final long LEASE_EXPIRY_NULL = -1L;
+    private static final long UNIX_TIME_MS_2000_01_01 = 946652400000L;
+    private static final long UNIX_TIME_MS_2100_01_01 = 4102412400000L;
     private static final int MTU_NULL = -1;
     private static final String[] FAKE_KEYS;
     private static final byte[] TEST_BLOB_DATA = new byte[] { -3, 6, 8, -9, 12,
@@ -103,7 +113,7 @@
         MockitoAnnotations.initMocks(this);
         final Context context = InstrumentationRegistry.getContext();
         final File dir = context.getFilesDir();
-        mDbFile = new File(dir, "test.db");
+        mDbFile = new File(dir, TEST_DATABASE_NAME);
         doReturn(mDbFile).when(mMockContext).getDatabasePath(anyString());
         doReturn(mMockJobScheduler).when(mMockContext)
                 .getSystemService(Context.JOB_SCHEDULER_SERVICE);
@@ -129,6 +139,16 @@
         mDbFile.delete();
     }
 
+    private void copyTestData(final File file) throws Exception {
+        try (
+                InputStream in = InstrumentationRegistry.getContext()
+                        .getResources().openRawResource(R.raw.test);
+                OutputStream out = new FileOutputStream(file)
+        ) {
+            Streams.copy(in, out);
+        }
+    }
+
     /** Helper method to build test network attributes */
     private static NetworkAttributes.Builder buildTestNetworkAttributes(
             final Inet4Address ipAddress, final long expiry, final String hint,
@@ -321,20 +341,32 @@
                 })));
     }
 
-    /** Insert large data that db size will be over threshold for maintenance test usage. */
-    private void insertFakeDataAndOverThreshold() {
+    /**
+     * This method is used to generate test.db file.
+     *
+     * Here are the steps to update the test.db file if you need to change value in DB.
+     * 1. Create a new test like "testGenerateDB" and have only one line code "generateFakeData()".
+     * 2. Comment out "mDbFile.delete()" in tearDown() method.
+     * 3. Run "atest IpMemoryStoreServiceTest#testGenerateDB".
+     * 4. Run "adb root; adb pull /data/data/com.android.server.networkstack.tests/files/test.db
+     *    $YOUR_CODE_BASE/package/module/NetworkStack/tests/unit/res/raw/test.db".
+     *
+     */
+    private void generateFakeData() {
+        final int fakeDataCount = 1000;
+        final int expiredRecordsCount = 100;
         try {
             final NetworkAttributes.Builder na = buildTestNetworkAttributes(
                     (Inet4Address) Inet4Address.getByName("1.2.3.4"), LEASE_EXPIRY_NULL,
                     "hint1", Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")),
                     219);
             final long time = System.currentTimeMillis() - 1;
-            for (int i = 0; i < 1000; i++) {
+            for (int i = 0; i < fakeDataCount; i++) {
                 int errorCode = IpMemoryStoreDatabase.storeNetworkAttributes(
                         mService.mDb,
                         "fakeKey" + i,
-                        // Let first 100 records get expiry.
-                        i < 100 ? time : time + TimeUnit.HOURS.toMillis(i),
+                        i < expiredRecordsCount
+                                ? UNIX_TIME_MS_2000_01_01 : UNIX_TIME_MS_2100_01_01 + i,
                         na.build());
                 assertEquals(errorCode, Status.SUCCESS);
 
@@ -344,7 +376,7 @@
                 assertEquals(errorCode, Status.SUCCESS);
             }
 
-            // After added 5000 records, db size is larger than fake threshold(100KB).
+            // After inserted fake data, db size should be larger than threshold.
             assertTrue(mService.isDbSizeOverThreshold());
         } catch (final UnknownHostException e) {
             fail("Insert fake data fail");
@@ -634,8 +666,10 @@
     }
 
     @Test
-    public void testFullMaintenance() {
-        insertFakeDataAndOverThreshold();
+    public void testFullMaintenance() throws Exception {
+        copyTestData(mDbFile);
+        // After inserted test data, db size should be larger than threshold.
+        assertTrue(mService.isDbSizeOverThreshold());
 
         final InterruptMaintenance im = new InterruptMaintenance(0/* Fake JobId */);
         // Do full maintenance and then db size should go down and meet the threshold.
@@ -651,8 +685,10 @@
     }
 
     @Test
-    public void testInterruptMaintenance() {
-        insertFakeDataAndOverThreshold();
+    public void testInterruptMaintenance() throws Exception {
+        copyTestData(mDbFile);
+        // After inserted test data, db size should be larger than threshold.
+        assertTrue(mService.isDbSizeOverThreshold());
 
         final InterruptMaintenance im = new InterruptMaintenance(0/* Fake JobId */);