Snap for 7803083 from f90e90550b610f6e8fe5532a43618756e8c1d409 to mainline-tzdata2-release

Change-Id: I7facfdcc864191c2bc307a4b1f5d547c4931aa5e
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2732435
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+# Generated build files
+gen/com/android/networkstack/**
+
+# IntelliJ project files
+**/.idea
+**/*.iml
+**/*.ipr
diff --git a/Android.bp b/Android.bp
index 802ca42..0f577a4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -22,12 +22,12 @@
 //                                            /    \
 //           +NetworkStackApiStableShims --> /      \ <-- +NetworkStackApiCurrentShims
 //           +NetworkStackReleaseApiLevel   /        \    +NetworkStackDevApiLevel
-//           +jarjar apistub.api[latest].* /          \   +module src/
-//            to apistub.*                /            \
+//           +jarjar apishim.api[latest].* /          \
+//            to apishim.*                /            \
 //                                       /              \
-//         NetworkStackApiStableDependencies             \
+//                                      /                \
 //                                     /                  \               android libs w/ all code
-//                   +module src/ --> /                    \              (also used in unit tests)
+//                                    / <- +module src/ -> \              (also used in unit tests)
 //                                   /                      \                        |
 //               NetworkStackApiStableLib               NetworkStackApiCurrentLib <--*
 //                          |                                     |
@@ -41,6 +41,10 @@
 //                                                         TestNetworkStack
 
 // Common defaults to define SDK level
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 java_defaults {
     name: "NetworkStackDevApiLevel",
     min_sdk_version: "29",
@@ -49,32 +53,153 @@
 
 java_defaults {
     name: "NetworkStackReleaseApiLevel",
-    sdk_version: "system_30",
+    sdk_version: "module_31",
     min_sdk_version: "29",
-    target_sdk_version: "30",
+    target_sdk_version: "31",
+    libs: [
+        "framework-connectivity",
+        "framework-statsd",
+        "framework-wifi",
+    ]
 }
 
-// Filegroups for the API shims
-filegroup {
-    name: "NetworkStackApiCurrentShims",
+// Libraries for the API shims
+java_defaults {
+    name: "NetworkStackShimsDefaults",
+    libs: [
+        "androidx.annotation_annotation",
+        "networkstack-client",
+    ],
+    static_libs : [
+        "modules-utils-build_system"
+    ],
+    apex_available: [
+        "com.android.tethering",
+        "//apex_available:platform",  // For InProcessNetworkStack and InProcessTethering
+    ],
+    min_sdk_version: "29",
+}
+
+// Common shim code. This includes the shim interface definitions themselves, and things like
+// ShimUtils and UnsupportedApiLevelException. Compiles against system_current because ShimUtils
+// needs access to all Build.VERSION_CODES.*, which by definition are only in the newest SDK.
+// TODO: consider moving ShimUtils into a library (or removing it in favour of SdkLevel) and compile
+// this target against the lowest-supported SDK (currently 29).
+java_library {
+    name: "NetworkStackShimsCommon",
+    defaults: ["NetworkStackShimsDefaults"],
+    srcs: ["apishim/common/**/*.java"],
+    sdk_version: "system_current",
+    visibility: ["//visibility:private"],
+}
+
+// Each level of the shims (29, 30, ...) is its own java_library compiled against the corresponding
+// system_X SDK. this ensures that each shim can only use SDK classes that exist in its SDK level.
+java_library {
+    name: "NetworkStackApi29Shims",
+    defaults: ["NetworkStackShimsDefaults"],
+    srcs: ["apishim/29/**/*.java"],
+    libs: [
+        "NetworkStackShimsCommon",
+    ],
+    sdk_version: "system_29",
+    visibility: ["//visibility:private"],
+}
+
+java_library {
+    name: "NetworkStackApi30Shims",
+    defaults: ["NetworkStackShimsDefaults"],
     srcs: [
-        "apishim/common/**/*.java",
-        "apishim/29/**/*.java",
         "apishim/30/**/*.java",
+    ],
+    libs: [
+        "NetworkStackShimsCommon",
+        "NetworkStackApi29Shims",
+    ],
+    sdk_version: "system_30",
+    visibility: ["//visibility:private"],
+    lint: {
+        baseline_filename: "lint-baseline-api-30-shims.xml",
+    },
+}
+
+// Shims for APIs being added to the current development version of Android. These APIs are not
+// stable and have no defined version number. These could be called 10000, but they use the next
+// integer so if the next SDK release happens to use that integer, we don't need to rename them.
+java_library {
+    name: "NetworkStackApi31Shims",
+    defaults: ["NetworkStackShimsDefaults"],
+    srcs: [
         "apishim/31/**/*.java",
-        ":networkstack-module-utils-srcs",
+    ],
+    libs: [
+        "NetworkStackShimsCommon",
+        "NetworkStackApi29Shims",
+        "NetworkStackApi30Shims",
+        "framework-connectivity",
+    ],
+    sdk_version: "module_31",
+    visibility: ["//visibility:private"],
+}
+
+
+// Shims for APIs being added to the current development version of Android. These APIs are not
+// stable and have no defined version number. These could be called 10000, but they use the next
+// integer so if the next SDK release happens to use that integer, we don't need to rename them.
+java_library {
+    name: "NetworkStackApi32Shims",
+    defaults: ["NetworkStackShimsDefaults"],
+    srcs: [
+        "apishim/32/**/*.java",
+    ],
+    libs: [
+        "NetworkStackShimsCommon",
+        "NetworkStackApi29Shims",
+        "NetworkStackApi30Shims",
+        "NetworkStackApi31Shims",
+        "framework-connectivity",
+    ],
+    sdk_version: "module_current",
+    visibility: ["//visibility:private"],
+}
+
+// API current uses the API current shims directly.
+// The current (in-progress) shims are in the com.android.networkstack.apishim package and are
+// called directly by the networkstack code.
+java_library {
+    name: "NetworkStackApiCurrentShims",
+    defaults: ["NetworkStackShimsDefaults"],
+    static_libs: [
+        "NetworkStackShimsCommon",
+        "NetworkStackApi29Shims",
+        "NetworkStackApi30Shims",
+        "NetworkStackApi31Shims",
+        "NetworkStackApi32Shims",
+    ],
+    sdk_version: "module_current",
+    visibility: [
+        "//packages/modules/Connectivity/Tethering",
+        "//packages/modules/Connectivity/tests/cts/net",
     ],
 }
 
-// API stable shims only include the compat package, but it is jarjared to replace the non-compat
-// package
-filegroup {
+// API stable uses jarjar to rename the latest stable apishim package from
+// com.android.networkstack.apishim.apiXX to com.android.networkstack.apishim, which is called by
+// the networkstack code.
+java_library {
     name: "NetworkStackApiStableShims",
-    srcs: [
-        "apishim/common/**/*.java",
-        "apishim/29/**/*.java",
-        "apishim/30/**/*.java",
-        ":networkstack-module-utils-srcs",
+    defaults: ["NetworkStackShimsDefaults"],
+    static_libs: [
+        "NetworkStackShimsCommon",
+        "NetworkStackApi29Shims",
+        "NetworkStackApi30Shims",
+        "NetworkStackApi31Shims",
+    ],
+    jarjar_rules: "apishim/jarjar-rules-compat.txt",
+    sdk_version: "module_31",
+    visibility: [
+        "//packages/modules/Connectivity/Tethering",
+        "//packages/modules/Connectivity/tests/cts/net",
     ],
 }
 
@@ -84,12 +209,13 @@
     name: "NetworkStackAndroidLibraryDefaults",
     srcs: [
         ":framework-networkstack-shared-srcs",
+        ":networkstack-module-utils-srcs",
     ],
     libs: ["unsupportedappusage"],
     static_libs: [
         "androidx.annotation_annotation",
-        "netd_aidl_interface-java",
-        "netlink-client",
+        "modules-utils-build_system",
+        "netd_aidl_interface-lateststable-java",
         "networkstack-client",
         "net-utils-framework-common",
         // See note on statsprotos when adding/updating proto build rules
@@ -97,47 +223,54 @@
         "statsprotos",
         "captiveportal-lib",
         "net-utils-device-common",
+        "net-utils-device-common-netlink",
     ],
     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.
+// The versions of the android library containing network stack code compiled for each SDK variant.
 android_library {
     name: "NetworkStackApiCurrentLib",
     defaults: ["NetworkStackDevApiLevel", "NetworkStackAndroidLibraryDefaults"],
     srcs: [
-        ":NetworkStackApiCurrentShims",
         "src/**/*.java",
         ":statslog-networkstack-java-gen-current"
     ],
+    static_libs: ["NetworkStackApiCurrentShims"],
     manifest: "AndroidManifestBase.xml",
-    enabled: false, // Disabled in mainline-prod
-}
-
-// For API stable, first build the dependencies using jarjar compat rules, then build the sources
-// linking with the dependencies.
-java_library {
-    name: "NetworkStackApiStableDependencies",
-    defaults: ["NetworkStackReleaseApiLevel", "NetworkStackAndroidLibraryDefaults"],
-    srcs: [":NetworkStackApiStableShims"],
-    jarjar_rules: "apishim/jarjar-rules-compat.txt",
+    visibility: [
+        "//frameworks/base/tests/net/integration",
+        "//packages/modules/Connectivity/Tethering/tests/integration",
+        "//packages/modules/Connectivity/tests/cts/net",
+        "//packages/modules/NetworkStack/tests/unit",
+        "//packages/modules/NetworkStack/tests/integration",
+    ],
+    lint: {
+        baseline_filename: "lint-baseline-current-lib.xml",
+    },
 }
 
 android_library {
     name: "NetworkStackApiStableLib",
-    defaults: ["NetworkStackReleaseApiLevel"],
+    defaults: ["NetworkStackReleaseApiLevel", "NetworkStackAndroidLibraryDefaults"],
     srcs: [
         "src/**/*.java",
         ":statslog-networkstack-java-gen-stable",
     ],
-    // API stable uses a jarjared version of the shims
-    static_libs: [
-        "NetworkStackApiStableDependencies",
-    ],
+    static_libs: ["NetworkStackApiStableShims"],
     manifest: "AndroidManifestBase.xml",
+    visibility: [
+        "//frameworks/base/packages/Connectivity/tests/integration",
+        "//frameworks/base/tests/net/integration",
+        "//packages/modules/Connectivity/Tethering/tests/integration",
+        "//packages/modules/Connectivity/tests/cts/net",
+        "//packages/modules/Connectivity/tests/integration",
+        "//packages/modules/NetworkStack/tests/unit",
+        "//packages/modules/NetworkStack/tests/integration",
+    ],
+    lint: {
+        baseline_filename: "lint-baseline-stable-lib.xml",
+    },
 }
 
 filegroup {
@@ -146,7 +279,7 @@
     visibility: [
         "//packages/modules/NetworkStack/tests/unit",
         "//packages/modules/NetworkStack/tests/integration",
-        "//frameworks/base/packages/Tethering/tests/integration",
+        "//packages/modules/Connectivity/tests:__subpackages__",
         "//packages/modules/Connectivity/Tethering/tests/integration",
     ]
 }
@@ -180,8 +313,10 @@
     // The permission configuration *must* be included to ensure security of the device
     // The InProcessNetworkStack goes together with the PlatformCaptivePortalLogin, which replaces
     // the default CaptivePortalLogin.
-    required: ["PlatformNetworkPermissionConfig", "PlatformCaptivePortalLogin"],
-    enabled: false, // Disabled in mainline-prod
+    required: [
+        "PlatformNetworkPermissionConfig",
+        "PlatformCaptivePortalLogin",
+    ],
 }
 
 // Pre-merge the AndroidManifest for NetworkStackNext, so that its manifest can be merged on top
@@ -189,8 +324,7 @@
     name: "NetworkStackNextManifestBase",
     defaults: ["NetworkStackAppDefaults", "NetworkStackDevApiLevel"],
     static_libs: ["NetworkStackApiCurrentLib"],
-    manifest: "AndroidManifest.xml",
-    enabled: false, // Disabled in mainline-prod
+    manifest: "AndroidManifest.xml"
 }
 
 // NetworkStack build targeting the current API release, for testing on in-development SDK
@@ -201,8 +335,10 @@
     certificate: "networkstack",
     manifest: "AndroidManifest_Next.xml",
     // The permission configuration *must* be included to ensure security of the device
-    required: ["NetworkPermissionConfig"],
-    enabled: false, // Disabled in mainline-prod
+    required: [
+        "NetworkPermissionConfig",
+        "privapp_whitelist_com.android.networkstack",
+    ],
 }
 
 // Updatable network stack for finalized API
@@ -213,21 +349,11 @@
     certificate: "networkstack",
     manifest: "AndroidManifest.xml",
     // The permission configuration *must* be included to ensure security of the device
-    required: ["NetworkPermissionConfig"],
-    updatable: true,
-}
-
-// Android library to derive test APKs for integration tests
-android_library {
-    name: "TestNetworkStackLib",
-    defaults: ["NetworkStackAppDefaults", "NetworkStackReleaseApiLevel"],
-    static_libs: ["NetworkStackApiStableLib"],
-    manifest: "AndroidManifestBase.xml",
-    visibility: [
-        "//frameworks/base/tests/net/integration",
-        "//cts/tests/tests/net",
-        "//packages/modules/Connectivity/tests/cts/net",
+    required: [
+        "NetworkPermissionConfig",
+        "privapp_whitelist_com.android.networkstack",
     ],
+    updatable: true,
 }
 
 cc_library_shared {
@@ -303,7 +429,10 @@
     certificate: "networkstack",
     manifest: ":NetworkStackTestAndroidManifest",
     // The permission configuration *must* be included to ensure security of the device
-    required: ["NetworkPermissionConfig"],
+    required: [
+        "NetworkPermissionConfig",
+        "privapp_whitelist_com.android.networkstack",
+    ],
 }
 
 // When adding or modifying protos, the jarjar rules and possibly proguard rules need
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 55357a8..8750795 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -19,8 +19,9 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.android.networkstack"
   android:sharedUserId="android.uid.networkstack"
-  android:versionCode="309999900"
-  android:versionName="r_aml_309999900"
+  android:versionCode="319999900"
+  android:versionName="s_aml_319999900"
+  coreApp="true"
 >
     <!-- Permissions must be defined here, and not in the base manifest, as the network stack
          running in the system server process does not need any permission, and having privileged
diff --git a/AndroidManifest_InProcess.xml b/AndroidManifest_InProcess.xml
index 2cb146a..6c64d87 100644
--- a/AndroidManifest_InProcess.xml
+++ b/AndroidManifest_InProcess.xml
@@ -19,7 +19,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.networkstack.inprocess"
           android:sharedUserId="android.uid.system"
-          android:process="system">
+          android:process="system"
+          coreApp="true">
     <application>
         <service android:name="com.android.server.NetworkStackService"
                  android:process="system"
diff --git a/AndroidManifest_Next.xml b/AndroidManifest_Next.xml
index 02fcb64..244d465 100644
--- a/AndroidManifest_Next.xml
+++ b/AndroidManifest_Next.xml
@@ -17,6 +17,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.networkstack"
           android:sharedUserId="android.uid.networkstack"
-          android:versionCode="300000000"
-          android:versionName="R-next">
+          android:versionCode="320000000"
+          android:versionName="T-next"
+          coreApp="true">
 </manifest>
diff --git a/OWNERS b/OWNERS
index 0e1e65d..8cb7492 100644
--- a/OWNERS
+++ b/OWNERS
@@ -2,5 +2,7 @@
 [email protected]
 [email protected]
 [email protected]
[email protected]
 [email protected]
 [email protected]
[email protected]
diff --git a/TEST_MAPPING b/TEST_MAPPING
index a2ed850..ed6a1a8 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -5,11 +5,31 @@
     },
     {
       "name": "NetworkStackNextTests"
+    },
+    {
+      "name": "NetworkStackIntegrationTests"
     }
   ],
   "postsubmit": [
     {
       "name": "NetworkStackHostTests"
+    }
+  ],
+  "auto-postsubmit": [
+    // Test tag for automotive targets. These are only running in postsubmit so as to harden the
+    // automotive targets to avoid introducing additional test flake and build time. The plan for
+    // presubmit testing for auto is to augment the existing tests to cover auto use cases as well.
+    // Additionally, this tag is used in targeted test suites to limit resource usage on the test
+    // infra during the hardening phase.
+    // TODO: this tag to be removed once the above is no longer an issue.
+    {
+      "name": "NetworkStackTests"
+    },
+    {
+      "name": "NetworkStackNextTests"
+    },
+    {
+      "name": "NetworkStackHostTests"
     },
     {
       "name": "NetworkStackIntegrationTests"
@@ -23,6 +43,11 @@
       "name": "NetworkStackTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
     }
   ],
+  "mainline-postsubmit": [
+    {
+      "name": "NetworkStackIntegrationTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
+    }
+  ],
   "imports": [
     {
       "path": "packages/modules/Connectivity"
diff --git a/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java b/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java
index 42216a9..8719e83 100644
--- a/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java
+++ b/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java
@@ -16,7 +16,7 @@
 
 package com.android.networkstack.apishim.api29;
 
-import android.net.CaptivePortalData;
+import android.net.Uri;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
@@ -28,7 +28,7 @@
 import org.json.JSONObject;
 
 /**
- * Compatibility implementation of {@link CaptivePortalDataShim}.
+ * Compatibility implementation of {@link CaptivePortalData}.
  *
  * <p>Use {@link com.android.networkstack.apishim.CaptivePortalDataShimImpl} instead of this
  * fallback implementation.
@@ -37,7 +37,7 @@
     protected CaptivePortalDataShimImpl() {}
 
     /**
-     * Parse a {@link android.net.CaptivePortalData} from JSON.
+     * Parse a {@link android.net.CaptivePortalDataShim} from JSON.
      *
      * <p>Use
      * {@link com.android.networkstack.apishim.CaptivePortalDataShimImpl#fromJson(JSONObject)}
@@ -51,24 +51,51 @@
     }
 
     @Override
-    public String getVenueFriendlyName() {
+    public CharSequence getVenueFriendlyName() {
         // Not supported in API level 29
         return null;
     }
 
+    @Override
+    public int getUserPortalUrlSource() {
+        // Not supported in API level 29
+        return ConstantsShim.CAPTIVE_PORTAL_DATA_SOURCE_OTHER;
+    }
+
     @VisibleForTesting
     public static boolean isSupported() {
         return false;
     }
 
     /**
-     * Generate a {@link CaptivePortalData} object with a friendly name set
+     * Generate a {@link CaptivePortalDataShim} object with a friendly name set
      *
      * @param friendlyName The friendly name to set
      * @return a {@link CaptivePortalData} object with a friendly name set
      */
-    public CaptivePortalData withVenueFriendlyName(String friendlyName) {
+    @Override
+    public CaptivePortalDataShim withVenueFriendlyName(String friendlyName)
+            throws UnsupportedApiLevelException {
         // Not supported in API level 29
-        return null;
+        throw new UnsupportedApiLevelException("CaptivePortalData not supported on API 29");
+    }
+
+    /**
+     * Generate a {@link CaptivePortalDataShim} object with a friendly name and Passpoint external
+     * URLs set
+     *
+     * @param friendlyName The friendly name to set
+     * @param venueInfoUrl Venue information URL
+     * @param termsAndConditionsUrl Terms and conditions URL
+     *
+     * @return a {@link CaptivePortalDataShim} object with friendly name, venue info URL and terms
+     * and conditions URL set
+     */
+    @Override
+    public CaptivePortalDataShim withPasspointInfo(@NonNull String friendlyName,
+            @NonNull Uri venueInfoUrl, @NonNull Uri termsAndConditionsUrl)
+            throws UnsupportedApiLevelException {
+        // Not supported in API level 29
+        throw new UnsupportedApiLevelException("CaptivePortalData not supported on API 29");
     }
 }
diff --git a/apishim/29/com/android/networkstack/apishim/api29/ConnectivityManagerShimImpl.java b/apishim/29/com/android/networkstack/apishim/api29/ConnectivityManagerShimImpl.java
new file mode 100644
index 0000000..07327be
--- /dev/null
+++ b/apishim/29/com/android/networkstack/apishim/api29/ConnectivityManagerShimImpl.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim.api29;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.Handler;
+
+import androidx.annotation.NonNull;
+
+import com.android.networkstack.apishim.common.ConnectivityManagerShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+
+/**
+ * Implementation of {@link ConnectivityManagerShim} for API 29.
+ */
+public class ConnectivityManagerShimImpl implements ConnectivityManagerShim {
+    protected final ConnectivityManager mCm;
+    protected ConnectivityManagerShimImpl(Context context) {
+        mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+    }
+
+    /**
+     * Get a new instance of {@link ConnectivityManagerShim}.
+     */
+    public static ConnectivityManagerShim newInstance(Context context) {
+        return new ConnectivityManagerShimImpl(context);
+    }
+    /**
+     * See android.net.ConnectivityManager#requestBackgroundNetwork
+     * @throws UnsupportedApiLevelException if API is not available in this API level.
+     */
+    @Override
+    public void requestBackgroundNetwork(@NonNull NetworkRequest request,
+            @NonNull NetworkCallback networkCallback, @NonNull Handler handler)
+            throws UnsupportedApiLevelException {
+        // Not supported for API 29.
+        throw new UnsupportedApiLevelException("Not supported in API 29.");
+    }
+
+    /**
+     * See android.net.ConnectivityManager#registerSystemDefaultNetworkCallback
+     */
+    @Override
+    public void registerSystemDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
+            @NonNull Handler handler) {
+        // defaultNetworkRequest is not really a "request", just a way of tracking the system
+        // default network. It's guaranteed not to actually bring up any networks because it
+        // should be the same request as the ConnectivityService default request, and thus
+        // shares fate with it.  In API <= R, registerSystemDefaultNetworkCallback is not
+        // available, and registerDefaultNetworkCallback will not track the system default when
+        // a VPN applies to the UID of this process.
+        final NetworkRequest defaultNetworkRequest = makeEmptyCapabilitiesRequest()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .build();
+        mCm.requestNetwork(defaultNetworkRequest, networkCallback, handler);
+    }
+
+    @NonNull
+    protected NetworkRequest.Builder makeEmptyCapabilitiesRequest() {
+        // Q does not have clearCapabilities(), so assume the default capabilities are as below
+        return new NetworkRequest.Builder()
+                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                .removeCapability(NET_CAPABILITY_TRUSTED)
+                .removeCapability(NET_CAPABILITY_NOT_VPN);
+    }
+}
diff --git a/apishim/29/com/android/networkstack/apishim/api29/ConstantsShim.java b/apishim/29/com/android/networkstack/apishim/api29/ConstantsShim.java
index b655858..0b000a9 100644
--- a/apishim/29/com/android/networkstack/apishim/api29/ConstantsShim.java
+++ b/apishim/29/com/android/networkstack/apishim/api29/ConstantsShim.java
@@ -34,4 +34,12 @@
     // Constants defined in android.net.ConnectivityDiagnosticsManager.
     public static final int DETECTION_METHOD_DNS_EVENTS = 1;
     public static final int DETECTION_METHOD_TCP_METRICS = 2;
+
+    // Constants defined in android.net.CaptivePortalData.
+    public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0;
+    public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1;
+
+    // Constants defined in android.net.NetworkCapabilities.
+    public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28;
+
 }
diff --git a/apishim/29/com/android/networkstack/apishim/api29/NetworkInformationShimImpl.java b/apishim/29/com/android/networkstack/apishim/api29/NetworkInformationShimImpl.java
index 8dc7b5c..e68020b 100644
--- a/apishim/29/com/android/networkstack/apishim/api29/NetworkInformationShimImpl.java
+++ b/apishim/29/com/android/networkstack/apishim/api29/NetworkInformationShimImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.networkstack.apishim.api29;
 
-import android.net.CaptivePortalData;
 import android.net.IpPrefix;
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
@@ -121,10 +120,7 @@
      * @param captivePortalData Captive portal data to be used
      */
     public void setCaptivePortalData(@NonNull LinkProperties lp,
-            @Nullable CaptivePortalData captivePortalData) {
-        if (lp == null) {
-            return;
-        }
-        lp.setCaptivePortalData(captivePortalData);
+            @Nullable CaptivePortalDataShim captivePortalData) {
+        // Not supported on this API level: no-op
     }
 }
diff --git a/apishim/29/com/android/networkstack/apishim/api29/NetworkRequestShimImpl.java b/apishim/29/com/android/networkstack/apishim/api29/NetworkRequestShimImpl.java
new file mode 100644
index 0000000..0c1d837
--- /dev/null
+++ b/apishim/29/com/android/networkstack/apishim/api29/NetworkRequestShimImpl.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim.api29;
+
+import android.net.NetworkRequest;
+import android.util.Range;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.networkstack.apishim.common.NetworkRequestShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+
+import java.util.Set;
+
+/**
+ * Implementation of {@link NetworkRequestShim} for API 29.
+ */
+public class NetworkRequestShimImpl implements NetworkRequestShim {
+    protected NetworkRequestShimImpl() {}
+
+    /**
+     * Get a new instance of {@link NetworkRequestShim}.
+     */
+    public static NetworkRequestShim newInstance() {
+        return new NetworkRequestShimImpl();
+    }
+
+    @Override
+    public void setUids(@NonNull NetworkRequest.Builder builder,
+            @Nullable Set<Range<Integer>> uids) throws UnsupportedApiLevelException {
+        // Not supported before API 31.
+        throw new UnsupportedApiLevelException("Not supported before API 31.");
+    }
+}
diff --git a/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java b/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java
index 19a41db..5825021 100644
--- a/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java
+++ b/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java
@@ -39,10 +39,14 @@
     @NonNull
     protected final CaptivePortalData mData;
 
-    protected CaptivePortalDataShimImpl(@NonNull CaptivePortalData data) {
+    public CaptivePortalDataShimImpl(@NonNull CaptivePortalData data) {
         mData = data;
     }
 
+    public CaptivePortalData getData() {
+        return mData;
+    }
+
     /**
      * Parse a {@link CaptivePortalDataShim} from a JSON object.
      * @throws JSONException The JSON is not a representation of correct captive portal data.
@@ -116,4 +120,36 @@
     public void notifyChanged(INetworkMonitorCallbacks cb) throws RemoteException {
         cb.notifyCaptivePortalDataChanged(mData);
     }
+
+    /**
+     * Generate a {@link CaptivePortalDataShim} object with a friendly name set
+     *
+     * @param friendlyName The friendly name to set
+     * @return a {@link CaptivePortalDataShim} object with a friendly name set
+     */
+    @Override
+    public CaptivePortalDataShim withVenueFriendlyName(String friendlyName)
+            throws UnsupportedApiLevelException {
+        // Not supported in API level 29
+        throw new UnsupportedApiLevelException("FriendlyName not supported on API 30");
+    }
+
+    /**
+     * Generate a {@link CaptivePortalDataShim} object with a friendly name and Passpoint external
+     * URLs set
+     *
+     * @param friendlyName The friendly name to set
+     * @param venueInfoUrl Venue information URL
+     * @param termsAndConditionsUrl Terms and conditions URL
+     *
+     * @return a {@link CaptivePortalDataShim} object with friendly name, venue info URL and terms
+     * and conditions URL set
+     */
+    @Override
+    public CaptivePortalDataShim withPasspointInfo(@NonNull String friendlyName,
+            @NonNull Uri venueInfoUrl, @NonNull Uri termsAndConditionsUrl)
+            throws UnsupportedApiLevelException {
+        // Not supported in API level 29
+        throw new UnsupportedApiLevelException("PasspointInfo not supported on API 30");
+    }
 }
diff --git a/apishim/30/com/android/networkstack/apishim/api30/ConnectivityManagerShimImpl.java b/apishim/30/com/android/networkstack/apishim/api30/ConnectivityManagerShimImpl.java
new file mode 100644
index 0000000..7c1d786
--- /dev/null
+++ b/apishim/30/com/android/networkstack/apishim/api30/ConnectivityManagerShimImpl.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim.api30;
+
+import static com.android.modules.utils.build.SdkLevel.isAtLeastR;
+
+import android.content.Context;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.NetworkRequest;
+import android.os.Build;
+import android.os.Handler;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import com.android.networkstack.apishim.common.ConnectivityManagerShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+
+/**
+ * Implementation of {@link ConnectivityManagerShim} for API 30.
+ */
+@RequiresApi(Build.VERSION_CODES.R)
+public class ConnectivityManagerShimImpl
+        extends com.android.networkstack.apishim.api29.ConnectivityManagerShimImpl {
+    protected ConnectivityManagerShimImpl(Context context) {
+        super(context);
+    }
+
+    /**
+     * Get a new instance of {@link ConnectivityManagerShim}.
+     */
+    @RequiresApi(Build.VERSION_CODES.Q)
+    public static ConnectivityManagerShim newInstance(Context context) {
+        if (!isAtLeastR()) {
+            return com.android.networkstack.apishim.api29.ConnectivityManagerShimImpl
+                    .newInstance(context);
+        }
+        return new ConnectivityManagerShimImpl(context);
+    }
+
+    /**
+     * See android.net.ConnectivityManager#requestBackgroundNetwork
+     * @throws UnsupportedApiLevelException if API is not available in this API level.
+     */
+    @Override
+    public void requestBackgroundNetwork(@NonNull NetworkRequest request,
+            @NonNull NetworkCallback networkCallback, @NonNull Handler handler)
+            throws UnsupportedApiLevelException {
+        // Not supported for API 30.
+        throw new UnsupportedApiLevelException("Not supported in API 30.");
+    }
+
+    @NonNull
+    @Override
+    protected NetworkRequest.Builder makeEmptyCapabilitiesRequest() {
+        return new NetworkRequest.Builder().clearCapabilities();
+    }
+}
diff --git a/apishim/30/com/android/networkstack/apishim/api30/ConstantsShim.java b/apishim/30/com/android/networkstack/apishim/api30/ConstantsShim.java
index 27fd745..19ff9d3 100644
--- a/apishim/30/com/android/networkstack/apishim/api30/ConstantsShim.java
+++ b/apishim/30/com/android/networkstack/apishim/api30/ConstantsShim.java
@@ -38,8 +38,12 @@
     public static final int DETECTION_METHOD_TCP_METRICS =
             DataStallReport.DETECTION_METHOD_TCP_METRICS;
 
-    /**
-     * @see android.net.NetworkCapabilities
-     */
+    // Constants defined in android.net.ConnectivityManager.
+    public static final int BLOCKED_REASON_NONE = 0;
+    public static final int BLOCKED_REASON_LOCKDOWN_VPN = 16;
+
+    // Constants defined in android.net.NetworkCapabilities.
+    public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28;
+    public static final int NET_CAPABILITY_ENTERPRISE = 29;
     public static final int TRANSPORT_TEST = 7;
 }
diff --git a/apishim/30/com/android/networkstack/apishim/api30/NetworkInformationShimImpl.java b/apishim/30/com/android/networkstack/apishim/api30/NetworkInformationShimImpl.java
index 5d9b013..477dd42a 100644
--- a/apishim/30/com/android/networkstack/apishim/api30/NetworkInformationShimImpl.java
+++ b/apishim/30/com/android/networkstack/apishim/api30/NetworkInformationShimImpl.java
@@ -21,6 +21,7 @@
 import android.net.NetworkCapabilities;
 import android.net.Uri;
 import android.os.Build;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -37,6 +38,8 @@
  */
 public class NetworkInformationShimImpl extends
         com.android.networkstack.apishim.api29.NetworkInformationShimImpl {
+    private static final String TAG = "api30.NetworkInformationShimImpl";
+
     protected NetworkInformationShimImpl() {}
 
     /**
@@ -105,4 +108,20 @@
             @NonNull Inet4Address serverAddress) {
         lp.setDhcpServerAddress(serverAddress);
     }
+
+    @Override
+    public void setCaptivePortalData(@NonNull LinkProperties lp,
+            @Nullable CaptivePortalDataShim captivePortalData) {
+        if (lp == null) {
+            return;
+        }
+        if (!(captivePortalData instanceof CaptivePortalDataShimImpl)) {
+            // The caller passed in a subclass that is not a CaptivePortalDataShimImpl.
+            // This is a programming error, but don't crash with ClassCastException.
+            Log.wtf(TAG, "Expected CaptivePortalDataShimImpl, but got "
+                    + captivePortalData.getClass().getName());
+            return;
+        }
+        lp.setCaptivePortalData(((CaptivePortalDataShimImpl) captivePortalData).getData());
+    }
 }
diff --git a/apishim/30/com/android/networkstack/apishim/api30/NetworkRequestShimImpl.java b/apishim/30/com/android/networkstack/apishim/api30/NetworkRequestShimImpl.java
new file mode 100644
index 0000000..b65a556
--- /dev/null
+++ b/apishim/30/com/android/networkstack/apishim/api30/NetworkRequestShimImpl.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim.api30;
+
+import android.os.Build;
+
+import com.android.networkstack.apishim.common.NetworkRequestShim;
+import com.android.networkstack.apishim.common.ShimUtils;
+
+/**
+ * Implementation of {@link NetworkRequestShim} for API 30.
+ */
+public class NetworkRequestShimImpl
+        extends com.android.networkstack.apishim.api29.NetworkRequestShimImpl {
+    protected NetworkRequestShimImpl() {
+        super();
+    }
+
+    /**
+     * Get a new instance of {@link NetworkRequestShim}.
+     */
+    public static NetworkRequestShim newInstance() {
+        if (!ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)) {
+            return com.android.networkstack.apishim.api29.NetworkRequestShimImpl
+                    .newInstance();
+        }
+        return new NetworkRequestShimImpl();
+    }
+}
diff --git a/apishim/30/com/android/networkstack/apishim/api30/SettingsShimImpl.java b/apishim/30/com/android/networkstack/apishim/api30/SettingsShimImpl.java
new file mode 100644
index 0000000..b8188c6
--- /dev/null
+++ b/apishim/30/com/android/networkstack/apishim/api30/SettingsShimImpl.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim.api30;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.networkstack.apishim.common.SettingsShim;
+
+/**
+ * Implementation of {@link SettingsShim} for API 30.
+ */
+public class SettingsShimImpl implements SettingsShim {
+    protected SettingsShimImpl() { }
+
+    /**
+     * Get a new instance of {@link SettingsShim}.
+     *
+     * Use com.android.networkstack.apishim.SeetingsShim#newInstance()
+     * (non-API30 version) instead, to use the correct shims depending on build SDK.
+     */
+    public static SettingsShim newInstance() {
+        return new SettingsShimImpl();
+    }
+
+    @Override
+    public boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid,
+            @NonNull String callingPackage, @Nullable String callingAttributionTag,
+            boolean throwException) {
+        return Settings.checkAndNoteWriteSettingsOperation(context, uid, callingPackage,
+                throwException);
+    }
+}
diff --git a/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java b/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java
deleted file mode 100644
index 955167d..0000000
--- a/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2020 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.net.CaptivePortalData;
-
-import androidx.annotation.NonNull;
-
-import com.android.networkstack.apishim.common.CaptivePortalDataShim;
-
-/**
- * Compatibility implementation of {@link CaptivePortalDataShim}.
- */
-public class CaptivePortalDataShimImpl
-        extends com.android.networkstack.apishim.api30.CaptivePortalDataShimImpl {
-    protected CaptivePortalDataShimImpl(@NonNull CaptivePortalData data) {
-        super(data);
-    }
-
-    @Override
-    public String getVenueFriendlyName() {
-        return mData.getVenueFriendlyName();
-    }
-
-    /**
-     * Generate a {@link CaptivePortalData} object with a friendly name set
-     *
-     * @param friendlyName The friendly name to set
-     * @return a {@link CaptivePortalData} object with a friendly name set
-     */
-    public CaptivePortalData withVenueFriendlyName(String friendlyName) {
-        return new CaptivePortalData.Builder(mData)
-                .setVenueFriendlyName(friendlyName)
-                .build();
-    }
-}
diff --git a/apishim/31/com/android/networkstack/apishim/api31/CaptivePortalDataShimImpl.java b/apishim/31/com/android/networkstack/apishim/api31/CaptivePortalDataShimImpl.java
new file mode 100644
index 0000000..5ae006b
--- /dev/null
+++ b/apishim/31/com/android/networkstack/apishim/api31/CaptivePortalDataShimImpl.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2020 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.api31;
+
+import android.net.CaptivePortalData;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import com.android.networkstack.apishim.common.CaptivePortalDataShim;
+
+/**
+ * Compatibility implementation of {@link CaptivePortalDataShim}.
+ */
+public class CaptivePortalDataShimImpl
+        extends com.android.networkstack.apishim.api30.CaptivePortalDataShimImpl {
+    public CaptivePortalDataShimImpl(@NonNull CaptivePortalData data) {
+        super(data);
+    }
+
+    @Override
+    public CharSequence getVenueFriendlyName() {
+        return mData.getVenueFriendlyName();
+    }
+
+    /**
+     * Get the information source of the User portal
+     * @return The source that the User portal was obtained from
+     */
+    @Override
+    public int getUserPortalUrlSource() {
+        return mData.getUserPortalUrlSource();
+    }
+
+    /**
+     * Generate a {@link CaptivePortalDataShim} object with a friendly name set
+     *
+     * @param friendlyName The friendly name to set
+     * @return a {@link CaptivePortalDataShim} object with a friendly name set
+     */
+    @Override
+    public CaptivePortalDataShim withVenueFriendlyName(String friendlyName) {
+        return new CaptivePortalDataShimImpl(new CaptivePortalData.Builder(mData)
+                .setVenueFriendlyName(friendlyName)
+                .build());
+    }
+
+    /**
+     * Generate a {@link CaptivePortalDataShim} object with a friendly name and Passpoint external
+     * URLs set
+     *
+     * @param friendlyName The friendly name to set
+     * @param venueInfoUrl Venue information URL
+     * @param termsAndConditionsUrl Terms and conditions URL
+     *
+     * @return a {@link CaptivePortalDataShim} object with friendly name, venue info URL and terms
+     * and conditions URL set
+     */
+    @Override
+    public CaptivePortalDataShim withPasspointInfo(@NonNull String friendlyName,
+            @NonNull Uri venueInfoUrl, @NonNull Uri termsAndConditionsUrl) {
+        return new CaptivePortalDataShimImpl(new CaptivePortalData.Builder(mData)
+                .setVenueFriendlyName(friendlyName)
+                .setVenueInfoUrl(venueInfoUrl, ConstantsShim.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
+                .setUserPortalUrl(termsAndConditionsUrl,
+                        ConstantsShim.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
+                .build());
+    }
+}
diff --git a/apishim/31/com/android/networkstack/apishim/api31/ConnectivityManagerShimImpl.java b/apishim/31/com/android/networkstack/apishim/api31/ConnectivityManagerShimImpl.java
new file mode 100644
index 0000000..46de698
--- /dev/null
+++ b/apishim/31/com/android/networkstack/apishim/api31/ConnectivityManagerShimImpl.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim.api31;
+
+import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
+
+import android.content.Context;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.NetworkRequest;
+import android.os.Build;
+import android.os.Handler;
+import android.util.Range;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import com.android.networkstack.apishim.common.ConnectivityManagerShim;
+
+import java.util.Collection;
+
+/**
+ * Implementation of {@link ConnectivityManagerShim} for API 31.
+ */
+@RequiresApi(Build.VERSION_CODES.S)
+public class ConnectivityManagerShimImpl
+        extends com.android.networkstack.apishim.api30.ConnectivityManagerShimImpl  {
+
+    protected ConnectivityManagerShimImpl(Context context) {
+        super(context);
+    }
+
+    /**
+     * Get a new instance of {@link ConnectivityManagerShim}.
+     */
+    @RequiresApi(Build.VERSION_CODES.Q)
+    public static ConnectivityManagerShim newInstance(Context context) {
+        if (!isAtLeastS()) {
+            return com.android.networkstack.apishim.api30.ConnectivityManagerShimImpl
+                    .newInstance(context);
+        }
+        return new ConnectivityManagerShimImpl(context);
+    }
+
+    /**
+     * See android.net.ConnectivityManager#requestBackgroundNetwork
+     */
+    @Override
+    public void requestBackgroundNetwork(@NonNull NetworkRequest request,
+            @NonNull NetworkCallback networkCallback, @NonNull Handler handler) {
+        mCm.requestBackgroundNetwork(request, networkCallback, handler);
+    }
+
+    /**
+     * See android.net.ConnectivityManager#registerSystemDefaultNetworkCallback
+     */
+    @Override
+    public void registerSystemDefaultNetworkCallback(
+            @NonNull NetworkCallback networkCallback, @NonNull Handler handler) {
+        mCm.registerSystemDefaultNetworkCallback(networkCallback, handler);
+    }
+
+    /**
+     * See android.net.ConnectivityManager#registerDefaultNetworkCallbackAsUid
+     */
+    @Override
+    public void registerDefaultNetworkCallbackForUid(
+            int uid, @NonNull NetworkCallback networkCallback, @NonNull Handler handler) {
+        mCm.registerDefaultNetworkCallbackForUid(uid, networkCallback, handler);
+    }
+
+    /**
+     * See android.net.ConnectivityManager#setLegacyLockdownVpnEnabled
+     */
+    @Override
+    public void setLegacyLockdownVpnEnabled(boolean enabled) {
+        mCm.setLegacyLockdownVpnEnabled(enabled);
+    }
+
+    /**
+     * See android.net.ConnectivityManager#setRequireVpnForUids
+     */
+    @Override
+    public void setRequireVpnForUids(boolean requireVpn, Collection<Range<Integer>> ranges) {
+        mCm.setRequireVpnForUids(requireVpn, ranges);
+    }
+}
diff --git a/apishim/31/com/android/networkstack/apishim/ConstantsShim.java b/apishim/31/com/android/networkstack/apishim/api31/ConstantsShim.java
similarity index 96%
rename from apishim/31/com/android/networkstack/apishim/ConstantsShim.java
rename to apishim/31/com/android/networkstack/apishim/api31/ConstantsShim.java
index 0184845..95ff072 100644
--- a/apishim/31/com/android/networkstack/apishim/ConstantsShim.java
+++ b/apishim/31/com/android/networkstack/apishim/api31/ConstantsShim.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.networkstack.apishim;
+package com.android.networkstack.apishim.api31;
 
 import androidx.annotation.VisibleForTesting;
 
diff --git a/apishim/31/com/android/networkstack/apishim/NetworkInformationShimImpl.java b/apishim/31/com/android/networkstack/apishim/api31/NetworkInformationShimImpl.java
similarity index 83%
rename from apishim/31/com/android/networkstack/apishim/NetworkInformationShimImpl.java
rename to apishim/31/com/android/networkstack/apishim/api31/NetworkInformationShimImpl.java
index d668d7e..a5c9a71 100644
--- a/apishim/31/com/android/networkstack/apishim/NetworkInformationShimImpl.java
+++ b/apishim/31/com/android/networkstack/apishim/api31/NetworkInformationShimImpl.java
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.networkstack.apishim;
+package com.android.networkstack.apishim.api31;
 
 import android.net.LinkProperties;
+import android.net.NetworkCapabilities;
 import android.os.Build;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.networkstack.apishim.common.CaptivePortalDataShim;
@@ -48,7 +50,7 @@
         if (!useApiAboveR()) {
             return com.android.networkstack.apishim.api30.NetworkInformationShimImpl.newInstance();
         }
-        return new com.android.networkstack.apishim.NetworkInformationShimImpl();
+        return new NetworkInformationShimImpl();
     }
 
     @Nullable
@@ -57,4 +59,11 @@
         if (lp == null || lp.getCaptivePortalData() == null) return null;
         return new CaptivePortalDataShimImpl(lp.getCaptivePortalData());
     }
+
+    @RequiresApi(Build.VERSION_CODES.S)
+    @Nullable
+    @Override
+    public String getCapabilityCarrierName(int capability) {
+        return NetworkCapabilities.getCapabilityCarrierName(capability);
+    }
 }
diff --git a/apishim/31/com/android/networkstack/apishim/api31/NetworkRequestShimImpl.java b/apishim/31/com/android/networkstack/apishim/api31/NetworkRequestShimImpl.java
new file mode 100644
index 0000000..2dc5d72
--- /dev/null
+++ b/apishim/31/com/android/networkstack/apishim/api31/NetworkRequestShimImpl.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim.api31;
+
+import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
+
+import android.net.NetworkRequest;
+import android.os.Build;
+import android.util.Range;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+import com.android.networkstack.apishim.common.NetworkRequestShim;
+
+import java.util.Set;
+
+/**
+ * Implementation of {@link NetworkRequestShim} for API 31.
+ */
+@RequiresApi(Build.VERSION_CODES.S)
+public class NetworkRequestShimImpl
+        extends com.android.networkstack.apishim.api30.NetworkRequestShimImpl {
+    protected NetworkRequestShimImpl() {
+        super();
+    }
+
+    /**
+     * Get a new instance of {@link NetworkRequestShim}.
+     */
+    @RequiresApi(Build.VERSION_CODES.Q)
+    public static NetworkRequestShim newInstance() {
+        if (!isAtLeastS()) {
+            return com.android.networkstack.apishim.api30.NetworkRequestShimImpl.newInstance();
+        }
+        return new NetworkRequestShimImpl();
+    }
+
+    @Override
+    public void setUids(@NonNull NetworkRequest.Builder builder,
+            @Nullable Set<Range<Integer>> uids) {
+        builder.setUids(uids);
+    }
+
+    @Override
+    public NetworkRequest.Builder setIncludeOtherUidNetworks(NetworkRequest.Builder builder,
+            boolean include) {
+        builder.setIncludeOtherUidNetworks(include);
+        return builder;
+    }
+
+    @Override
+    public NetworkRequest.Builder newBuilder(@NonNull NetworkRequest request) {
+        return new NetworkRequest.Builder(request);
+    }
+}
diff --git a/apishim/31/com/android/networkstack/apishim/NetworkShimImpl.java b/apishim/31/com/android/networkstack/apishim/api31/NetworkShimImpl.java
similarity index 95%
rename from apishim/31/com/android/networkstack/apishim/NetworkShimImpl.java
rename to apishim/31/com/android/networkstack/apishim/api31/NetworkShimImpl.java
index 0c92391..eda8e27 100644
--- a/apishim/31/com/android/networkstack/apishim/NetworkShimImpl.java
+++ b/apishim/31/com/android/networkstack/apishim/api31/NetworkShimImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.networkstack.apishim;
+package com.android.networkstack.apishim.api31;
 
 import android.net.Network;
 
diff --git a/apishim/31/com/android/networkstack/apishim/api31/SettingsShimImpl.java b/apishim/31/com/android/networkstack/apishim/api31/SettingsShimImpl.java
new file mode 100644
index 0000000..1b5cbae
--- /dev/null
+++ b/apishim/31/com/android/networkstack/apishim/api31/SettingsShimImpl.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim.api31;
+
+import android.content.Context;
+import android.os.Build;
+import android.provider.Settings;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.networkstack.apishim.common.SettingsShim;
+import com.android.networkstack.apishim.common.ShimUtils;
+
+/**
+ * Implementation of {@link SettingsShim} for API 31.
+ */
+public class SettingsShimImpl
+        extends com.android.networkstack.apishim.api30.SettingsShimImpl {
+    protected SettingsShimImpl() { }
+
+    /**
+     * Get a new instance of {@link SettingsShim}.
+     */
+    public static SettingsShim newInstance() {
+        if (!ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.R)) {
+            return com.android.networkstack.apishim.api30.SettingsShimImpl
+                    .newInstance();
+        }
+        return new SettingsShimImpl();
+    }
+
+    @Override
+    public boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid,
+            @NonNull String callingPackage, @Nullable String callingAttributionTag,
+            boolean throwException) {
+        return Settings.checkAndNoteWriteSettingsOperation(context, uid, callingPackage,
+                callingAttributionTag, throwException);
+    }
+}
diff --git a/apishim/31/com/android/networkstack/apishim/SocketUtilsShimImpl.java b/apishim/31/com/android/networkstack/apishim/api31/SocketUtilsShimImpl.java
similarity index 94%
rename from apishim/31/com/android/networkstack/apishim/SocketUtilsShimImpl.java
rename to apishim/31/com/android/networkstack/apishim/api31/SocketUtilsShimImpl.java
index 483bde0..f5aa80b 100644
--- a/apishim/31/com/android/networkstack/apishim/SocketUtilsShimImpl.java
+++ b/apishim/31/com/android/networkstack/apishim/api31/SocketUtilsShimImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.networkstack.apishim;
+package com.android.networkstack.apishim.api31;
 
 /**
  * Implementation of {@link NetworkShim} for API 30.
diff --git a/apishim/32/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java b/apishim/32/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java
new file mode 100644
index 0000000..2056b1b
--- /dev/null
+++ b/apishim/32/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim;
+
+import android.net.CaptivePortalData;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import com.android.networkstack.apishim.common.CaptivePortalDataShim;
+
+/**
+ * Compatibility implementation of {@link CaptivePortalDataShim}.
+ */
+@RequiresApi(Build.VERSION_CODES.S) // Change to T when version code available, and adding T methods
+public class CaptivePortalDataShimImpl
+        extends com.android.networkstack.apishim.api31.CaptivePortalDataShimImpl {
+    // Currently identical to the API 31 shim, so inherit everything
+    public CaptivePortalDataShimImpl(@NonNull CaptivePortalData data) {
+        super(data);
+    }
+}
diff --git a/apishim/32/com/android/networkstack/apishim/ConnectivityManagerShimImpl.java b/apishim/32/com/android/networkstack/apishim/ConnectivityManagerShimImpl.java
new file mode 100644
index 0000000..a7aa0c8
--- /dev/null
+++ b/apishim/32/com/android/networkstack/apishim/ConnectivityManagerShimImpl.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim;
+
+import android.content.Context;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.networkstack.apishim.common.ConnectivityManagerShim;
+
+/**
+ * Compatibility implementation of {@link ConnectivityManagerShim}.
+ */
+@RequiresApi(Build.VERSION_CODES.S) // Change to T when version code available, and adding T methods
+public class ConnectivityManagerShimImpl
+        extends com.android.networkstack.apishim.api31.ConnectivityManagerShimImpl  {
+    // Currently identical to the API 31 shim, so inherit everything
+    protected ConnectivityManagerShimImpl(Context context) {
+        super(context);
+    }
+}
diff --git a/apishim/31/com/android/networkstack/apishim/ConstantsShim.java b/apishim/32/com/android/networkstack/apishim/ConstantsShim.java
similarity index 76%
copy from apishim/31/com/android/networkstack/apishim/ConstantsShim.java
copy to apishim/32/com/android/networkstack/apishim/ConstantsShim.java
index 0184845..0a5b555 100644
--- a/apishim/31/com/android/networkstack/apishim/ConstantsShim.java
+++ b/apishim/32/com/android/networkstack/apishim/ConstantsShim.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@
 /**
  * Utility class for defining and importing constants from the Android platform.
  */
-public class ConstantsShim extends com.android.networkstack.apishim.api30.ConstantsShim {
+public class ConstantsShim extends com.android.networkstack.apishim.api31.ConstantsShim {
     /**
      * Constant that callers can use to determine what version of the shim they are using.
      * Must be the same as the version of the shims.
@@ -29,9 +29,5 @@
      * the shimmed objects and methods themselves.
      */
     @VisibleForTesting
-    public static final int VERSION = 31;
-
-    // When removing this shim, the version in NetworkMonitorUtils should be removed too.
-    // TODO: add TRANSPORT_TEST to system API in API 31 (it is only a test API as of R)
-    public static final int TRANSPORT_TEST = 7;
+    public static final int VERSION = 32;
 }
diff --git a/apishim/32/com/android/networkstack/apishim/NetworkInformationShimImpl.java b/apishim/32/com/android/networkstack/apishim/NetworkInformationShimImpl.java
new file mode 100644
index 0000000..28aa75c
--- /dev/null
+++ b/apishim/32/com/android/networkstack/apishim/NetworkInformationShimImpl.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim;
+
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.networkstack.apishim.common.NetworkInformationShim;
+
+/**
+ * Compatibility implementation of {@link NetworkInformationShim}.
+ */
+@RequiresApi(Build.VERSION_CODES.S) // Change to T when version code available, and adding T methods
+public class NetworkInformationShimImpl
+        extends com.android.networkstack.apishim.api31.NetworkInformationShimImpl {
+    // Currently identical to the API 31 shim, so inherit everything
+    protected NetworkInformationShimImpl() {}
+}
diff --git a/apishim/32/com/android/networkstack/apishim/NetworkRequestShimImpl.java b/apishim/32/com/android/networkstack/apishim/NetworkRequestShimImpl.java
new file mode 100644
index 0000000..95ae5ba
--- /dev/null
+++ b/apishim/32/com/android/networkstack/apishim/NetworkRequestShimImpl.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim;
+
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.networkstack.apishim.common.NetworkRequestShim;
+
+/**
+ * Implementation of {@link NetworkRequestShim} for API 31.
+ */
+@RequiresApi(Build.VERSION_CODES.S) // Change to T when version code available, and adding T methods
+public class NetworkRequestShimImpl
+        extends com.android.networkstack.apishim.api31.NetworkRequestShimImpl {
+    // Currently identical to the API 31 shim, so inherit everything
+    protected NetworkRequestShimImpl() {
+        super();
+    }
+}
diff --git a/apishim/31/com/android/networkstack/apishim/NetworkShimImpl.java b/apishim/32/com/android/networkstack/apishim/NetworkShimImpl.java
similarity index 70%
copy from apishim/31/com/android/networkstack/apishim/NetworkShimImpl.java
copy to apishim/32/com/android/networkstack/apishim/NetworkShimImpl.java
index 0c92391..2e31a78 100644
--- a/apishim/31/com/android/networkstack/apishim/NetworkShimImpl.java
+++ b/apishim/32/com/android/networkstack/apishim/NetworkShimImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,14 +17,17 @@
 package com.android.networkstack.apishim;
 
 import android.net.Network;
+import android.os.Build;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
 
 /**
- * Implementation of {@link NetworkShim} for API 30.
+ * Compatibility implementation of {@link com.android.networkstack.apishim.common.NetworkShim}.
  */
+@RequiresApi(Build.VERSION_CODES.S) // Change to T when version code available, and adding T methods
 public class NetworkShimImpl extends com.android.networkstack.apishim.api30.NetworkShimImpl {
-    // Currently, this is the same as the API 30 shim, so inherit everything from that.
+    // Currently, this is the same as the API 31 shim, so inherit everything from that.
     protected NetworkShimImpl(@NonNull Network network) {
         super(network);
     }
diff --git a/apishim/32/com/android/networkstack/apishim/SettingsShimImpl.java b/apishim/32/com/android/networkstack/apishim/SettingsShimImpl.java
new file mode 100644
index 0000000..46d2102
--- /dev/null
+++ b/apishim/32/com/android/networkstack/apishim/SettingsShimImpl.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim;
+
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.networkstack.apishim.common.SettingsShim;
+
+/**
+ * Compatibility implementation of {@link SettingsShim} for API 31.
+ */
+@RequiresApi(Build.VERSION_CODES.S) // Change to T when version code available, and adding T methods
+public class SettingsShimImpl
+        extends com.android.networkstack.apishim.api30.SettingsShimImpl {
+    // Currently identical to the API 31 shim, so inherit everything
+    protected SettingsShimImpl() { }
+}
diff --git a/apishim/31/com/android/networkstack/apishim/SocketUtilsShimImpl.java b/apishim/32/com/android/networkstack/apishim/SocketUtilsShimImpl.java
similarity index 68%
copy from apishim/31/com/android/networkstack/apishim/SocketUtilsShimImpl.java
copy to apishim/32/com/android/networkstack/apishim/SocketUtilsShimImpl.java
index 483bde0..2f4e500 100644
--- a/apishim/31/com/android/networkstack/apishim/SocketUtilsShimImpl.java
+++ b/apishim/32/com/android/networkstack/apishim/SocketUtilsShimImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,11 +16,16 @@
 
 package com.android.networkstack.apishim;
 
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
 /**
- * Implementation of {@link NetworkShim} for API 30.
+ * Implementation of {@link com.android.networkstack.apishim.common.SocketUtilsShim}.
  */
+@RequiresApi(Build.VERSION_CODES.S) // Change to T when version code available, and adding T methods
 public class SocketUtilsShimImpl
         extends com.android.networkstack.apishim.api30.SocketUtilsShimImpl {
-    // Currently, this is the same as the API 30 shim, so inherit everything from that.
+    // Currently, this is the same as the API 31 shim, so inherit everything from that.
     protected SocketUtilsShimImpl() {}
 }
diff --git a/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java b/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java
index 4bd5532..13bf257 100644
--- a/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java
+++ b/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java
@@ -16,12 +16,12 @@
 
 package com.android.networkstack.apishim.common;
 
-import android.annotation.NonNull;
-import android.net.CaptivePortalData;
 import android.net.INetworkMonitorCallbacks;
 import android.net.Uri;
 import android.os.RemoteException;
 
+import androidx.annotation.NonNull;
+
 /**
  * Compatibility interface for {@link android.net.CaptivePortalData}.
  */
@@ -54,7 +54,12 @@
     /**
      * @see CaptivePortalData#getVenueFriendlyName()
      */
-    String getVenueFriendlyName();
+    CharSequence getVenueFriendlyName();
+
+    /**
+     * @see CaptivePortalData#getUserPortalUrlSource()
+     */
+    int getUserPortalUrlSource();
 
     /**
      * @see INetworkMonitorCallbacks#notifyCaptivePortalDataChanged(android.net.CaptivePortalData)
@@ -65,7 +70,25 @@
      * Generate a {@link CaptivePortalData} object with a friendly name set
      *
      * @param friendlyName The friendly name to set
+     * @throws UnsupportedApiLevelException when used with API level lower than 31
      * @return a {@link CaptivePortalData} object with a friendly name set
      */
-    CaptivePortalData withVenueFriendlyName(@NonNull String friendlyName);
+    CaptivePortalDataShim withVenueFriendlyName(@NonNull String friendlyName)
+            throws UnsupportedApiLevelException;
+
+    /**
+     * Generate a {@link CaptivePortalData} object with a friendly name and Passpoint external URLs
+     * set
+     *
+     * @param friendlyName The friendly name to set
+     * @param venueInfoUrl Venue information URL
+     * @param termsAndConditionsUrl Terms and conditions URL
+     *
+     * @throws UnsupportedApiLevelException when used with API level lower than 31
+     * @return a {@link CaptivePortalData} object with friendly name, venue info URL and terms
+     * and conditions URL set
+     */
+    CaptivePortalDataShim withPasspointInfo(@NonNull String friendlyName,
+            @NonNull Uri venueInfoUrl, @NonNull Uri termsAndConditionsUrl)
+            throws UnsupportedApiLevelException;
 }
diff --git a/apishim/common/com/android/networkstack/apishim/common/ConnectivityManagerShim.java b/apishim/common/com/android/networkstack/apishim/common/ConnectivityManagerShim.java
new file mode 100644
index 0000000..86d785e
--- /dev/null
+++ b/apishim/common/com/android/networkstack/apishim/common/ConnectivityManagerShim.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim.common;
+
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.NetworkRequest;
+import android.os.Handler;
+import android.util.Range;
+
+import androidx.annotation.NonNull;
+
+import java.util.Collection;
+
+/**
+ * Interface used to access API methods in {@link android.net.ConnectivityManager}, with
+ * appropriate fallbacks if the methods are not yet part of the released API.
+ *
+ * <p>This interface makes it easier for callers to use ConnectivityManagerShimImpl, as it's more
+ * obvious what methods must be implemented on each API level, and it abstracts from callers the
+ * need to reference classes that have different implementations (which also does not work well
+ * with IDEs).
+ */
+public interface ConnectivityManagerShim {
+    /** See android.net.ConnectivityManager#requestBackgroundNetwork */
+    void requestBackgroundNetwork(@NonNull NetworkRequest request,
+            @NonNull NetworkCallback networkCallback, @NonNull Handler handler)
+            throws UnsupportedApiLevelException;
+
+    /** See android.net.ConnectivityManager#registerSystemDefaultNetworkCallback */
+    void registerSystemDefaultNetworkCallback(
+            @NonNull NetworkCallback networkCallback, @NonNull Handler handler);
+
+    /** See android.net.ConnectivityManager#registerDefaultNetworkCallbackForUid */
+    default void registerDefaultNetworkCallbackForUid(
+            int uid, @NonNull NetworkCallback networkCallback, @NonNull Handler handler)
+            throws UnsupportedApiLevelException {
+        throw new UnsupportedApiLevelException("Only supported starting from API 31");
+    }
+
+    /** See android.net.ConnectivityManager#setLegacyLockdownVpnEnabled */
+    default void setLegacyLockdownVpnEnabled(boolean enabled) throws UnsupportedApiLevelException {
+        throw new UnsupportedApiLevelException("Only supported starting from API 31");
+    }
+
+    /** See android.net.ConnectivityManager#setRequireVpnForUids */
+    default void setRequireVpnForUids(boolean requireVpn, Collection<Range<Integer>> ranges)
+            throws UnsupportedApiLevelException {
+        throw new UnsupportedApiLevelException("Only supported starting from API 31");
+    }
+}
diff --git a/apishim/common/com/android/networkstack/apishim/common/NetworkInformationShim.java b/apishim/common/com/android/networkstack/apishim/common/NetworkInformationShim.java
index 6cdcf8c..7fa1777 100644
--- a/apishim/common/com/android/networkstack/apishim/common/NetworkInformationShim.java
+++ b/apishim/common/com/android/networkstack/apishim/common/NetworkInformationShim.java
@@ -16,7 +16,6 @@
 
 package com.android.networkstack.apishim.common;
 
-import android.net.CaptivePortalData;
 import android.net.IpPrefix;
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
@@ -83,5 +82,18 @@
      * @param captivePortalData Captive portal data to be used
      */
     void setCaptivePortalData(@NonNull LinkProperties lp,
-            @Nullable CaptivePortalData captivePortalData);
+            @Nullable CaptivePortalDataShim captivePortalData);
+
+    /**
+     * Get the name of the given capability that carriers use.
+     * If the capability does not have a carrier-name, returns null.
+     *
+     * @param capability The capability to get the carrier-name of.
+     * @return The carrier-name of the capability, or null if it doesn't exist.
+     * @hide
+     */
+    @Nullable
+    default String getCapabilityCarrierName(int capability) {
+        return null;
+    }
 }
diff --git a/apishim/common/com/android/networkstack/apishim/common/NetworkRequestShim.java b/apishim/common/com/android/networkstack/apishim/common/NetworkRequestShim.java
new file mode 100644
index 0000000..d07d1ae
--- /dev/null
+++ b/apishim/common/com/android/networkstack/apishim/common/NetworkRequestShim.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim.common;
+
+import android.net.NetworkRequest;
+import android.util.Range;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Set;
+
+/**
+ * Interface used to access API methods in {@link android.net.NetworkRequest}, with
+ * appropriate fallbacks if the methods are not yet part of the released API.
+ */
+public interface NetworkRequestShim {
+    /**
+     * See android.net.NetworkRequest.Builder#setUids.
+     * Set the {@code uids} into {@code builder}.
+     */
+    void setUids(@NonNull NetworkRequest.Builder builder,
+            @Nullable Set<Range<Integer>> uids) throws UnsupportedApiLevelException;
+
+    /**
+     * See android.net.NetworkRequest.Builder#setIncludeOtherUidNetworks.
+     */
+    default NetworkRequest.Builder setIncludeOtherUidNetworks(NetworkRequest.Builder builder,
+            boolean include) throws UnsupportedApiLevelException {
+        throw new UnsupportedApiLevelException("Not supported before API 31.");
+    }
+
+    /**
+     * See android.net.NetworkRequest.Builder(NetworkRequest).
+     * @throws UnsupportedApiLevelException if API is not available in the API level.
+     */
+    default NetworkRequest.Builder newBuilder(@NonNull NetworkRequest request)
+            throws UnsupportedApiLevelException {
+        // Not supported before API 31.
+        throw new UnsupportedApiLevelException("Not supported before API 31.");
+    }
+}
diff --git a/apishim/common/com/android/networkstack/apishim/common/SettingsShim.java b/apishim/common/com/android/networkstack/apishim/common/SettingsShim.java
new file mode 100644
index 0000000..2453084
--- /dev/null
+++ b/apishim/common/com/android/networkstack/apishim/common/SettingsShim.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim.common;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Interce for accessing API methods in {@link android.provider.Settings} by different API level.
+ */
+public interface SettingsShim {
+    /**
+     * @see android.provider.Settings#checkAndNoteWriteSettingsOperation(Context, int, String,
+     * String, boolean)
+     */
+    boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid,
+            @NonNull String callingPackage, @Nullable String callingAttributionTag,
+            boolean throwException);
+}
diff --git a/apishim/jarjar-rules-compat.txt b/apishim/jarjar-rules-compat.txt
index dba2b49..4f34ccb 100644
--- a/apishim/jarjar-rules-compat.txt
+++ b/apishim/jarjar-rules-compat.txt
@@ -1,7 +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 31 is stable, apishim/31/com.android.networkstack.apishim should be moved to the
-# com.android.networkstack.apishim.api31 package, a new apishim/32/com.android.networkstack.apishim
-# package should be created, and this rule should reference api31.
-rule com.android.networkstack.apishim.api30.** com.android.networkstack.apishim.@1
\ No newline at end of file
+# Once API 32 is stable, apishim/32/com.android.networkstack.apishim should be moved to the
+# com.android.networkstack.apishim.api32 package, a new apishim/33/com.android.networkstack.apishim
+# package should be created, and this rule should reference api32.
+rule com.android.networkstack.apishim.api31.** com.android.networkstack.apishim.@1
\ No newline at end of file
diff --git a/common/captiveportal/Android.bp b/common/captiveportal/Android.bp
index 0b49eb2..876e733 100644
--- a/common/captiveportal/Android.bp
+++ b/common/captiveportal/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 java_library {
     name: "captiveportal-lib",
     srcs: ["src/**/*.java"],
@@ -23,4 +27,4 @@
     sdk_version: "system_current",
     // this is part of updatable modules(NetworkStack) which targets 29(Q)
     min_sdk_version: "29",
-}
\ No newline at end of file
+}
diff --git a/common/captiveportal/src/android/net/captiveportal/CaptivePortalProbeResult.java b/common/captiveportal/src/android/net/captiveportal/CaptivePortalProbeResult.java
index 2ba1dcc..8b388ad 100755
--- a/common/captiveportal/src/android/net/captiveportal/CaptivePortalProbeResult.java
+++ b/common/captiveportal/src/android/net/captiveportal/CaptivePortalProbeResult.java
@@ -103,11 +103,22 @@
     }
 
     public boolean isSuccessful() {
-        return mHttpResponseCode == SUCCESS_CODE;
+        return isSuccessCode(mHttpResponseCode);
     }
 
     public boolean isPortal() {
-        return !isSuccessful() && (mHttpResponseCode >= 200) && (mHttpResponseCode <= 399);
+        return isPortalCode(mHttpResponseCode);
+    }
+
+    private static boolean isSuccessCode(int responseCode) {
+        return responseCode == SUCCESS_CODE;
+    }
+
+    /**
+     * @return Whether the specified HTTP return code indicates a captive portal.
+     */
+    public static boolean isPortalCode(int responseCode) {
+        return !isSuccessCode(responseCode) && (responseCode >= 200) && (responseCode <= 399);
     }
 
     public boolean isFailed() {
diff --git a/common/moduleutils/Android.bp b/common/moduleutils/Android.bp
index 644b0a4..d90c33d 100644
--- a/common/moduleutils/Android.bp
+++ b/common/moduleutils/Android.bp
@@ -17,21 +17,18 @@
 // Shared utility sources to be used by multiple network modules
 // TODO: remove all frameworks/base dependencies on packages/modules/NetworkStack and
 // frameworks/base/packages/Tethering by moving these files to frameworks/libs/net.
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// TODO: remove this filegroup together with services.net
 filegroup {
     name: "net-module-utils-srcs",
     srcs: [
-        "src/android/net/util/SharedLog.java",
-        "src/android/net/shared/InitialConfiguration.java",
-        "src/android/net/shared/Layer2Information.java",
-        "src/android/net/shared/LinkPropertiesParcelableUtil.java",
-        "src/android/net/shared/ParcelableUtil.java",
         "src/android/net/shared/NetdUtils.java",
-        "src/android/net/shared/NetworkMonitorUtils.java",
-        "src/android/net/shared/ParcelableUtil.java",
-        "src/android/net/shared/PrivateDnsConfig.java",
-        "src/android/net/shared/ProvisioningConfiguration.java",
         "src/android/net/shared/RouteUtils.java",
         "src/android/net/util/InterfaceParams.java",
+        "src/android/net/util/SharedLog.java",
     ],
     visibility: [
         "//frameworks/base/services/net",
@@ -39,6 +36,19 @@
 }
 
 filegroup {
+    name: "connectivity-module-utils-srcs",
+    srcs: [
+        "src/android/net/util/SharedLog.java",
+        "src/android/net/shared/NetdUtils.java",
+        "src/android/net/shared/NetworkMonitorUtils.java",
+        "src/android/net/shared/RouteUtils.java",
+    ],
+    visibility: [
+        "//packages/modules/Connectivity/service",
+    ]
+}
+
+filegroup {
     name: "networkstack-module-utils-srcs",
     srcs: ["src/**/*.java"],
     visibility: [
@@ -54,7 +64,6 @@
         "src/android/net/ip/InterfaceController.java",
         "src/android/net/ip/IpNeighborMonitor.java",
         "src/android/net/ip/NetlinkMonitor.java",
-        "src/android/net/netlink/*.java",
         "src/android/net/shared/NetdUtils.java",
         "src/android/net/shared/RouteUtils.java",
         "src/android/net/util/InterfaceParams.java",
diff --git a/common/moduleutils/src/android/net/ip/ConntrackMonitor.java b/common/moduleutils/src/android/net/ip/ConntrackMonitor.java
index 9189002..43005cd 100644
--- a/common/moduleutils/src/android/net/ip/ConntrackMonitor.java
+++ b/common/moduleutils/src/android/net/ip/ConntrackMonitor.java
@@ -16,12 +16,9 @@
 
 package android.net.ip;
 
-import static android.net.netlink.ConntrackMessage.DYING_MASK;
-import static android.net.netlink.ConntrackMessage.ESTABLISHED_MASK;
+import static com.android.net.module.util.netlink.ConntrackMessage.DYING_MASK;
+import static com.android.net.module.util.netlink.ConntrackMessage.ESTABLISHED_MASK;
 
-import android.net.netlink.ConntrackMessage;
-import android.net.netlink.NetlinkConstants;
-import android.net.netlink.NetlinkMessage;
 import android.net.util.SharedLog;
 import android.os.Handler;
 import android.system.OsConstants;
@@ -29,6 +26,9 @@
 import androidx.annotation.NonNull;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.netlink.ConntrackMessage;
+import com.android.net.module.util.netlink.NetlinkConstants;
+import com.android.net.module.util.netlink.NetlinkMessage;
 
 import java.util.Objects;
 
@@ -51,6 +51,12 @@
     public static final int NF_NETLINK_CONNTRACK_UPDATE = 2;
     public static final int NF_NETLINK_CONNTRACK_DESTROY = 4;
 
+    // The socket receive buffer size in bytes. If too many conntrack messages are sent too
+    // quickly, the conntrack messages can overflow the socket receive buffer. This can happen
+    // if too many connections are disconnected by losing network and so on. Use a large-enough
+    // buffer to avoid the error ENOBUFS while listening to the conntrack messages.
+    private static final int SOCKET_RECV_BUFSIZE = 6 * 1024 * 1024;
+
     /**
      * A class for describing parsed netfilter conntrack events.
      */
@@ -176,7 +182,7 @@
     public ConntrackMonitor(@NonNull Handler h, @NonNull SharedLog log,
             @NonNull ConntrackEventConsumer cb) {
         super(h, log, TAG, OsConstants.NETLINK_NETFILTER, NF_NETLINK_CONNTRACK_NEW
-                | NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY);
+                | NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY, SOCKET_RECV_BUFSIZE);
         mConsumer = cb;
     }
 
diff --git a/common/moduleutils/src/android/net/ip/IpNeighborMonitor.java b/common/moduleutils/src/android/net/ip/IpNeighborMonitor.java
index b45c061..a16fdf2 100644
--- a/common/moduleutils/src/android/net/ip/IpNeighborMonitor.java
+++ b/common/moduleutils/src/android/net/ip/IpNeighborMonitor.java
@@ -16,22 +16,24 @@
 
 package android.net.ip;
 
-import static android.net.netlink.NetlinkConstants.RTM_DELNEIGH;
-import static android.net.netlink.NetlinkConstants.hexify;
-import static android.net.netlink.NetlinkConstants.stringForNlMsgType;
 import static android.system.OsConstants.NETLINK_ROUTE;
 
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELNEIGH;
+import static com.android.net.module.util.netlink.NetlinkConstants.hexify;
+import static com.android.net.module.util.netlink.NetlinkConstants.stringForNlMsgType;
+
 import android.net.MacAddress;
-import android.net.netlink.NetlinkMessage;
-import android.net.netlink.NetlinkSocket;
-import android.net.netlink.RtNetlinkNeighborMessage;
-import android.net.netlink.StructNdMsg;
 import android.net.util.SharedLog;
 import android.os.Handler;
 import android.system.ErrnoException;
 import android.system.OsConstants;
 import android.util.Log;
 
+import com.android.net.module.util.netlink.NetlinkMessage;
+import com.android.net.module.util.netlink.NetlinkSocket;
+import com.android.net.module.util.netlink.RtNetlinkNeighborMessage;
+import com.android.net.module.util.netlink.StructNdMsg;
+
 import java.net.InetAddress;
 import java.util.StringJoiner;
 
diff --git a/common/moduleutils/src/android/net/ip/NetlinkMonitor.java b/common/moduleutils/src/android/net/ip/NetlinkMonitor.java
index 3d314f1..17157d8 100644
--- a/common/moduleutils/src/android/net/ip/NetlinkMonitor.java
+++ b/common/moduleutils/src/android/net/ip/NetlinkMonitor.java
@@ -16,16 +16,16 @@
 
 package android.net.ip;
 
-import static android.net.netlink.NetlinkConstants.hexify;
 import static android.net.util.SocketUtils.makeNetlinkSocketAddress;
 import static android.system.OsConstants.AF_NETLINK;
 import static android.system.OsConstants.SOCK_DGRAM;
 import static android.system.OsConstants.SOCK_NONBLOCK;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_RCVBUF;
+
+import static com.android.net.module.util.netlink.NetlinkConstants.hexify;
 
 import android.annotation.NonNull;
-import android.net.netlink.NetlinkErrorMessage;
-import android.net.netlink.NetlinkMessage;
-import android.net.netlink.NetlinkSocket;
 import android.net.util.SharedLog;
 import android.net.util.SocketUtils;
 import android.os.Handler;
@@ -35,6 +35,9 @@
 import android.util.Log;
 
 import com.android.net.module.util.PacketReader;
+import com.android.net.module.util.netlink.NetlinkErrorMessage;
+import com.android.net.module.util.netlink.NetlinkMessage;
+import com.android.net.module.util.netlink.NetlinkSocket;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -56,9 +59,13 @@
     protected final String mTag;
     private final int mFamily;
     private final int mBindGroups;
+    private final int mSockRcvbufSize;
 
     private static final boolean DBG = false;
 
+    // Default socket receive buffer size. This means the specific buffer size is not set.
+    private static final int DEFAULT_SOCKET_RECV_BUFSIZE = -1;
+
     /**
      * Constructs a new {@code NetlinkMonitor} instance.
      *
@@ -68,14 +75,23 @@
      * @param tag The log tag to use for log messages.
      * @param family the Netlink socket family to, e.g., {@code NETLINK_ROUTE}.
      * @param bindGroups the netlink groups to bind to.
+     * @param sockRcvbufSize the specific socket receive buffer size in bytes. -1 means that don't
+     *        set the specific socket receive buffer size in #createFd and use the default value in
+     *        /proc/sys/net/core/rmem_default file. See SO_RCVBUF in man-pages/socket.
      */
     public NetlinkMonitor(@NonNull Handler h, @NonNull SharedLog log, @NonNull String tag,
-            int family, int bindGroups) {
+            int family, int bindGroups, int sockRcvbufSize) {
         super(h, NetlinkSocket.DEFAULT_RECV_BUFSIZE);
         mLog = log.forSubComponent(tag);
         mTag = tag;
         mFamily = family;
         mBindGroups = bindGroups;
+        mSockRcvbufSize = sockRcvbufSize;
+    }
+
+    public NetlinkMonitor(@NonNull Handler h, @NonNull SharedLog log, @NonNull String tag,
+            int family, int bindGroups) {
+        this(h, log, tag, family, bindGroups, DEFAULT_SOCKET_RECV_BUFSIZE);
     }
 
     @Override
@@ -84,6 +100,9 @@
 
         try {
             fd = Os.socket(AF_NETLINK, SOCK_DGRAM | SOCK_NONBLOCK, mFamily);
+            if (mSockRcvbufSize != DEFAULT_SOCKET_RECV_BUFSIZE) {
+                Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, mSockRcvbufSize);
+            }
             Os.bind(fd, makeNetlinkSocketAddress(0, mBindGroups));
             NetlinkSocket.connectToKernel(fd);
 
diff --git a/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java b/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java
index 981a576..b151cb9 100644
--- a/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java
+++ b/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java
@@ -19,6 +19,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
 import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -27,6 +28,8 @@
 
 import android.net.NetworkCapabilities;
 
+import com.android.modules.utils.build.SdkLevel;
+
 /** @hide */
 public class NetworkMonitorUtils {
     // This class is used by both NetworkMonitor and ConnectivityService, so it cannot use
@@ -36,6 +39,14 @@
     // TODO: use NetworkCapabilities.TRANSPORT_TEST once NetworkStack builds against API 31.
     private static final int TRANSPORT_TEST = 7;
 
+    // This class is used by both NetworkMonitor and ConnectivityService, so it cannot use
+    // NetworkStack shims, but at the same time cannot use non-system APIs.
+    // NET_CAPABILITY_NOT_VCN_MANAGED is system API as of S (so it is enforced to always be 28 and
+    // can't be changed).
+    // TODO: use NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED once NetworkStack builds against
+    //       API 31.
+    public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28;
+
     // Network conditions broadcast constants
     public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
             "android.net.conn.NETWORK_CONDITIONS_MEASURED";
@@ -59,18 +70,19 @@
     public static boolean isPrivateDnsValidationRequired(NetworkCapabilities nc) {
         if (nc == null) return false;
 
+        final boolean isVcnManaged = SdkLevel.isAtLeastS()
+                && !nc.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+        final boolean isOemPaid = nc.hasCapability(NET_CAPABILITY_OEM_PAID)
+                && nc.hasCapability(NET_CAPABILITY_TRUSTED);
+        final boolean isDefaultCapable = nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                && nc.hasCapability(NET_CAPABILITY_TRUSTED);
+
         // TODO: Consider requiring validation for DUN networks.
         if (nc.hasCapability(NET_CAPABILITY_INTERNET)
-                && nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
-                && nc.hasCapability(NET_CAPABILITY_TRUSTED)) {
-            // Real networks
+                && (isVcnManaged || isOemPaid || isDefaultCapable)) {
             return true;
         }
 
-        // TODO: once TRANSPORT_TEST is @SystemApi in S and S SDK is stable (so constant shims can
-        // be replaced with the SDK constant that will be inlined), replace isTestNetwork with
-        // hasTransport(TRANSPORT_TEST)
-
         // Test networks that also have one of the major transport types are attempting to replicate
         // that transport on a test interface (for example, test ethernet networks with
         // EthernetManager#setIncludeTestInterfaces). Run validation on them for realistic tests.
diff --git a/common/netlinkclient/Android.bp b/common/netlinkclient/Android.bp
deleted file mode 100644
index 2b4a2d6..0000000
--- a/common/netlinkclient/Android.bp
+++ /dev/null
@@ -1,29 +0,0 @@
-//
-// Copyright (C) 2018 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.
-//
-
-java_library {
-    name: "netlink-client",
-    srcs: [
-        "src/**/*.java",
-        ":framework-annotations",
-    ],
-    libs: [
-        "androidx.annotation_annotation",
-    ],
-    sdk_version: "system_current",
-    // this is part of updatable modules(NetworkStack) which targets 29(Q)
-    min_sdk_version: "29",
-}
diff --git a/common/netlinkclient/src/android/net/netlink/ConntrackMessage.java b/common/netlinkclient/src/android/net/netlink/ConntrackMessage.java
deleted file mode 100644
index cc8bb7e..0000000
--- a/common/netlinkclient/src/android/net/netlink/ConntrackMessage.java
+++ /dev/null
@@ -1,552 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.net.netlink;
-
-import static android.net.netlink.StructNlAttr.findNextAttrOfType;
-import static android.net.netlink.StructNlAttr.makeNestedType;
-import static android.net.netlink.StructNlMsgHdr.NLM_F_ACK;
-import static android.net.netlink.StructNlMsgHdr.NLM_F_REPLACE;
-import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
-import static android.system.OsConstants.IPPROTO_TCP;
-import static android.system.OsConstants.IPPROTO_UDP;
-
-import static java.nio.ByteOrder.BIG_ENDIAN;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.system.OsConstants;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.net.Inet4Address;
-import java.net.InetAddress;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Objects;
-
-
-/**
- * A NetlinkMessage subclass for netlink conntrack messages.
- *
- * see also: &lt;linux_src&gt;/include/uapi/linux/netfilter/nfnetlink_conntrack.h
- *
- * @hide
- */
-public class ConntrackMessage extends NetlinkMessage {
-    public static final int STRUCT_SIZE = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE;
-
-    // enum ctattr_type
-    public static final short CTA_TUPLE_ORIG  = 1;
-    public static final short CTA_TUPLE_REPLY = 2;
-    public static final short CTA_STATUS      = 3;
-    public static final short CTA_TIMEOUT     = 7;
-
-    // enum ctattr_tuple
-    public static final short CTA_TUPLE_IP    = 1;
-    public static final short CTA_TUPLE_PROTO = 2;
-
-    // enum ctattr_ip
-    public static final short CTA_IP_V4_SRC = 1;
-    public static final short CTA_IP_V4_DST = 2;
-
-    // enum ctattr_l4proto
-    public static final short CTA_PROTO_NUM      = 1;
-    public static final short CTA_PROTO_SRC_PORT = 2;
-    public static final short CTA_PROTO_DST_PORT = 3;
-
-    // enum ip_conntrack_status
-    public static final int IPS_EXPECTED      = 0x00000001;
-    public static final int IPS_SEEN_REPLY    = 0x00000002;
-    public static final int IPS_ASSURED       = 0x00000004;
-    public static final int IPS_CONFIRMED     = 0x00000008;
-    public static final int IPS_SRC_NAT       = 0x00000010;
-    public static final int IPS_DST_NAT       = 0x00000020;
-    public static final int IPS_SEQ_ADJUST    = 0x00000040;
-    public static final int IPS_SRC_NAT_DONE  = 0x00000080;
-    public static final int IPS_DST_NAT_DONE  = 0x00000100;
-    public static final int IPS_DYING         = 0x00000200;
-    public static final int IPS_FIXED_TIMEOUT = 0x00000400;
-    public static final int IPS_TEMPLATE      = 0x00000800;
-    public static final int IPS_UNTRACKED     = 0x00001000;
-    public static final int IPS_HELPER        = 0x00002000;
-    public static final int IPS_OFFLOAD       = 0x00004000;
-    public static final int IPS_HW_OFFLOAD    = 0x00008000;
-
-    // ip_conntrack_status mask
-    // Interesting on the NAT conntrack session which has already seen two direction traffic.
-    // TODO: Probably IPS_{SRC, DST}_NAT_DONE are also interesting.
-    public static final int ESTABLISHED_MASK = IPS_CONFIRMED | IPS_ASSURED | IPS_SEEN_REPLY
-            | IPS_SRC_NAT;
-    // Interesting on the established NAT conntrack session which is dying.
-    public static final int DYING_MASK = ESTABLISHED_MASK | IPS_DYING;
-
-    /**
-     * A tuple for the conntrack connection information.
-     *
-     * see also CTA_TUPLE_ORIG and CTA_TUPLE_REPLY.
-     */
-    public static class Tuple {
-        public final Inet4Address srcIp;
-        public final Inet4Address dstIp;
-
-        // Both port and protocol number are unsigned numbers stored in signed integers, and that
-        // callers that want to compare them to integers should either cast those integers, or
-        // convert them to unsigned using Byte.toUnsignedInt() and Short.toUnsignedInt().
-        public final short srcPort;
-        public final short dstPort;
-        public final byte protoNum;
-
-        public Tuple(TupleIpv4 ip, TupleProto proto) {
-            this.srcIp = ip.src;
-            this.dstIp = ip.dst;
-            this.srcPort = proto.srcPort;
-            this.dstPort = proto.dstPort;
-            this.protoNum = proto.protoNum;
-        }
-
-        @Override
-        @VisibleForTesting
-        public boolean equals(Object o) {
-            if (!(o instanceof Tuple)) return false;
-            Tuple that = (Tuple) o;
-            return Objects.equals(this.srcIp, that.srcIp)
-                    && Objects.equals(this.dstIp, that.dstIp)
-                    && this.srcPort == that.srcPort
-                    && this.dstPort == that.dstPort
-                    && this.protoNum == that.protoNum;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(srcIp, dstIp, srcPort, dstPort, protoNum);
-        }
-
-        @Override
-        public String toString() {
-            final String srcIpStr = (srcIp == null) ? "null" : srcIp.getHostAddress();
-            final String dstIpStr = (dstIp == null) ? "null" : dstIp.getHostAddress();
-            final String protoStr = NetlinkConstants.stringForProtocol(protoNum);
-
-            return "Tuple{"
-                    + protoStr + ": "
-                    + srcIpStr + ":" + Short.toUnsignedInt(srcPort) + " -> "
-                    + dstIpStr + ":" + Short.toUnsignedInt(dstPort)
-                    + "}";
-        }
-    }
-
-    /**
-     * A tuple for the conntrack connection address.
-     *
-     * see also CTA_TUPLE_IP.
-     */
-    public static class TupleIpv4 {
-        public final Inet4Address src;
-        public final Inet4Address dst;
-
-        public TupleIpv4(Inet4Address src, Inet4Address dst) {
-            this.src = src;
-            this.dst = dst;
-        }
-    }
-
-    /**
-     * A tuple for the conntrack connection protocol.
-     *
-     * see also CTA_TUPLE_PROTO.
-     */
-    public static class TupleProto {
-        public final byte protoNum;
-        public final short srcPort;
-        public final short dstPort;
-
-        public TupleProto(byte protoNum, short srcPort, short dstPort) {
-            this.protoNum = protoNum;
-            this.srcPort = srcPort;
-            this.dstPort = dstPort;
-        }
-    }
-
-    public static byte[] newIPv4TimeoutUpdateRequest(
-            int proto, Inet4Address src, int sport, Inet4Address dst, int dport, int timeoutSec) {
-        // *** STYLE WARNING ***
-        //
-        // Code below this point uses extra block indentation to highlight the
-        // packing of nested tuple netlink attribute types.
-        final StructNlAttr ctaTupleOrig = new StructNlAttr(CTA_TUPLE_ORIG,
-                new StructNlAttr(CTA_TUPLE_IP,
-                        new StructNlAttr(CTA_IP_V4_SRC, src),
-                        new StructNlAttr(CTA_IP_V4_DST, dst)),
-                new StructNlAttr(CTA_TUPLE_PROTO,
-                        new StructNlAttr(CTA_PROTO_NUM, (byte) proto),
-                        new StructNlAttr(CTA_PROTO_SRC_PORT, (short) sport, BIG_ENDIAN),
-                        new StructNlAttr(CTA_PROTO_DST_PORT, (short) dport, BIG_ENDIAN)));
-
-        final StructNlAttr ctaTimeout = new StructNlAttr(CTA_TIMEOUT, timeoutSec, BIG_ENDIAN);
-
-        final int payloadLength = ctaTupleOrig.getAlignedLength() + ctaTimeout.getAlignedLength();
-        final byte[] bytes = new byte[STRUCT_SIZE + payloadLength];
-        final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
-        byteBuffer.order(ByteOrder.nativeOrder());
-
-        final ConntrackMessage ctmsg = new ConntrackMessage();
-        ctmsg.mHeader.nlmsg_len = bytes.length;
-        ctmsg.mHeader.nlmsg_type = (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8)
-                | NetlinkConstants.IPCTNL_MSG_CT_NEW;
-        ctmsg.mHeader.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE;
-        ctmsg.mHeader.nlmsg_seq = 1;
-        ctmsg.pack(byteBuffer);
-
-        ctaTupleOrig.pack(byteBuffer);
-        ctaTimeout.pack(byteBuffer);
-
-        return bytes;
-    }
-
-    /**
-     * Parses a netfilter conntrack message from a {@link ByteBuffer}.
-     *
-     * @param header the netlink message header.
-     * @param byteBuffer The buffer from which to parse the netfilter conntrack message.
-     * @return the parsed netfilter conntrack message, or {@code null} if the netfilter conntrack
-     *         message could not be parsed successfully (for example, if it was truncated).
-     */
-    public static ConntrackMessage parse(StructNlMsgHdr header, ByteBuffer byteBuffer) {
-        // Just build the netlink header and netfilter header for now and pretend the whole message
-        // was consumed.
-        // TODO: Parse the conntrack attributes.
-        final StructNfGenMsg nfGenMsg = StructNfGenMsg.parse(byteBuffer);
-        if (nfGenMsg == null) {
-            return null;
-        }
-
-        final int baseOffset = byteBuffer.position();
-        StructNlAttr nlAttr = findNextAttrOfType(CTA_STATUS, byteBuffer);
-        int status = 0;
-        if (nlAttr != null) {
-            status = nlAttr.getValueAsBe32(0);
-        }
-
-        byteBuffer.position(baseOffset);
-        nlAttr = findNextAttrOfType(CTA_TIMEOUT, byteBuffer);
-        int timeoutSec = 0;
-        if (nlAttr != null) {
-            timeoutSec = nlAttr.getValueAsBe32(0);
-        }
-
-        byteBuffer.position(baseOffset);
-        nlAttr = findNextAttrOfType(makeNestedType(CTA_TUPLE_ORIG), byteBuffer);
-        Tuple tupleOrig = null;
-        if (nlAttr != null) {
-            tupleOrig = parseTuple(nlAttr.getValueAsByteBuffer());
-        }
-
-        byteBuffer.position(baseOffset);
-        nlAttr = findNextAttrOfType(makeNestedType(CTA_TUPLE_REPLY), byteBuffer);
-        Tuple tupleReply = null;
-        if (nlAttr != null) {
-            tupleReply = parseTuple(nlAttr.getValueAsByteBuffer());
-        }
-
-        // Advance to the end of the message.
-        byteBuffer.position(baseOffset);
-        final int kMinConsumed = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE;
-        final int kAdditionalSpace = NetlinkConstants.alignedLengthOf(
-                header.nlmsg_len - kMinConsumed);
-        if (byteBuffer.remaining() < kAdditionalSpace) {
-            return null;
-        }
-        byteBuffer.position(baseOffset + kAdditionalSpace);
-
-        return new ConntrackMessage(header, nfGenMsg, tupleOrig, tupleReply, status, timeoutSec);
-    }
-
-    /**
-     * Parses a conntrack tuple from a {@link ByteBuffer}.
-     *
-     * The attribute parsing is interesting on:
-     * - CTA_TUPLE_IP
-     *     CTA_IP_V4_SRC
-     *     CTA_IP_V4_DST
-     * - CTA_TUPLE_PROTO
-     *     CTA_PROTO_NUM
-     *     CTA_PROTO_SRC_PORT
-     *     CTA_PROTO_DST_PORT
-     *
-     * Assume that the minimum size is the sum of CTA_TUPLE_IP (size: 20) and CTA_TUPLE_PROTO
-     * (size: 28). Here is an example for an expected CTA_TUPLE_ORIG message in raw data:
-     * +--------------------------------------------------------------------------------------+
-     * | CTA_TUPLE_ORIG                                                                       |
-     * +--------------------------+-----------------------------------------------------------+
-     * | 1400                     | nla_len = 20                                              |
-     * | 0180                     | nla_type = nested CTA_TUPLE_IP                            |
-     * |     0800 0100 C0A8500C   |     nla_type=CTA_IP_V4_SRC, ip=192.168.80.12              |
-     * |     0800 0200 8C700874   |     nla_type=CTA_IP_V4_DST, ip=140.112.8.116              |
-     * | 1C00                     | nla_len = 28                                              |
-     * | 0280                     | nla_type = nested CTA_TUPLE_PROTO                         |
-     * |     0500 0100 06 000000  |     nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)         |
-     * |     0600 0200 F3F1 0000  |     nla_type=CTA_PROTO_SRC_PORT, port=62449 (big endian)  |
-     * |     0600 0300 01BB 0000  |     nla_type=CTA_PROTO_DST_PORT, port=433 (big endian)    |
-     * +--------------------------+-----------------------------------------------------------+
-     *
-     * The position of the byte buffer doesn't set to the end when the function returns. It is okay
-     * because the caller ConntrackMessage#parse has passed a copy which is used for this parser
-     * only. Moreover, the parser behavior is the same as other existing netlink struct class
-     * parser. Ex: StructInetDiagMsg#parse.
-     */
-    @Nullable
-    private static Tuple parseTuple(@Nullable ByteBuffer byteBuffer) {
-        if (byteBuffer == null) return null;
-
-        TupleIpv4 tupleIpv4 = null;
-        TupleProto tupleProto = null;
-
-        final int baseOffset = byteBuffer.position();
-        StructNlAttr nlAttr = findNextAttrOfType(makeNestedType(CTA_TUPLE_IP), byteBuffer);
-        if (nlAttr != null) {
-            tupleIpv4 = parseTupleIpv4(nlAttr.getValueAsByteBuffer());
-        }
-        if (tupleIpv4 == null) return null;
-
-        byteBuffer.position(baseOffset);
-        nlAttr = findNextAttrOfType(makeNestedType(CTA_TUPLE_PROTO), byteBuffer);
-        if (nlAttr != null) {
-            tupleProto = parseTupleProto(nlAttr.getValueAsByteBuffer());
-        }
-        if (tupleProto == null) return null;
-
-        return new Tuple(tupleIpv4, tupleProto);
-    }
-
-    @Nullable
-    private static Inet4Address castToInet4Address(@Nullable InetAddress address) {
-        if (address == null || !(address instanceof Inet4Address)) return null;
-        return (Inet4Address) address;
-    }
-
-    @Nullable
-    private static TupleIpv4 parseTupleIpv4(@Nullable ByteBuffer byteBuffer) {
-        if (byteBuffer == null) return null;
-
-        Inet4Address src = null;
-        Inet4Address dst = null;
-
-        final int baseOffset = byteBuffer.position();
-        StructNlAttr nlAttr = findNextAttrOfType(CTA_IP_V4_SRC, byteBuffer);
-        if (nlAttr != null) {
-            src = castToInet4Address(nlAttr.getValueAsInetAddress());
-        }
-        if (src == null) return null;
-
-        byteBuffer.position(baseOffset);
-        nlAttr = findNextAttrOfType(CTA_IP_V4_DST, byteBuffer);
-        if (nlAttr != null) {
-            dst = castToInet4Address(nlAttr.getValueAsInetAddress());
-        }
-        if (dst == null) return null;
-
-        return new TupleIpv4(src, dst);
-    }
-
-    @Nullable
-    private static TupleProto parseTupleProto(@Nullable ByteBuffer byteBuffer) {
-        if (byteBuffer == null) return null;
-
-        byte protoNum = 0;
-        short srcPort = 0;
-        short dstPort = 0;
-
-        final int baseOffset = byteBuffer.position();
-        StructNlAttr nlAttr = findNextAttrOfType(CTA_PROTO_NUM, byteBuffer);
-        if (nlAttr != null) {
-            protoNum = nlAttr.getValueAsByte((byte) 0);
-        }
-        if (!(protoNum == IPPROTO_TCP || protoNum == IPPROTO_UDP)) return null;
-
-        byteBuffer.position(baseOffset);
-        nlAttr = StructNlAttr.findNextAttrOfType(CTA_PROTO_SRC_PORT, byteBuffer);
-        if (nlAttr != null) {
-            srcPort = nlAttr.getValueAsBe16((short) 0);
-        }
-        if (srcPort == 0) return null;
-
-        byteBuffer.position(baseOffset);
-        nlAttr = StructNlAttr.findNextAttrOfType(CTA_PROTO_DST_PORT, byteBuffer);
-        if (nlAttr != null) {
-            dstPort = nlAttr.getValueAsBe16((short) 0);
-        }
-        if (dstPort == 0) return null;
-
-        return new TupleProto(protoNum, srcPort, dstPort);
-    }
-
-    /**
-     * Netfilter header.
-     */
-    public final StructNfGenMsg nfGenMsg;
-    /**
-     * Original direction conntrack tuple.
-     *
-     * The tuple is determined by the parsed attribute value CTA_TUPLE_ORIG, or null if the
-     * tuple could not be parsed successfully (for example, if it was truncated or absent).
-     */
-    @Nullable
-    public final Tuple tupleOrig;
-    /**
-     * Reply direction conntrack tuple.
-     *
-     * The tuple is determined by the parsed attribute value CTA_TUPLE_REPLY, or null if the
-     * tuple could not be parsed successfully (for example, if it was truncated or absent).
-     */
-    @Nullable
-    public final Tuple tupleReply;
-    /**
-     * Connection status. A bitmask of ip_conntrack_status enum flags.
-     *
-     * The status is determined by the parsed attribute value CTA_STATUS, or 0 if the status could
-     * not be parsed successfully (for example, if it was truncated or absent). For the message
-     * from kernel, the valid status is non-zero. For the message from user space, the status may
-     * be 0 (absent).
-     */
-    public final int status;
-    /**
-     * Conntrack timeout.
-     *
-     * The timeout is determined by the parsed attribute value CTA_TIMEOUT, or 0 if the timeout
-     * could not be parsed successfully (for example, if it was truncated or absent). For
-     * IPCTNL_MSG_CT_NEW event, the valid timeout is non-zero. For IPCTNL_MSG_CT_DELETE event, the
-     * timeout is 0 (absent).
-     */
-    public final int timeoutSec;
-
-    private ConntrackMessage() {
-        super(new StructNlMsgHdr());
-        nfGenMsg = new StructNfGenMsg((byte) OsConstants.AF_INET);
-
-        // This constructor is only used by #newIPv4TimeoutUpdateRequest which doesn't use these
-        // data member for packing message. Simply fill them to null or 0.
-        tupleOrig = null;
-        tupleReply = null;
-        status = 0;
-        timeoutSec = 0;
-    }
-
-    private ConntrackMessage(@NonNull StructNlMsgHdr header, @NonNull StructNfGenMsg nfGenMsg,
-            @Nullable Tuple tupleOrig, @Nullable Tuple tupleReply, int status, int timeoutSec) {
-        super(header);
-        this.nfGenMsg = nfGenMsg;
-        this.tupleOrig = tupleOrig;
-        this.tupleReply = tupleReply;
-        this.status = status;
-        this.timeoutSec = timeoutSec;
-    }
-
-    public void pack(ByteBuffer byteBuffer) {
-        mHeader.pack(byteBuffer);
-        nfGenMsg.pack(byteBuffer);
-    }
-
-    public short getMessageType() {
-        return (short) (getHeader().nlmsg_type & ~(NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8));
-    }
-
-    /**
-     * Convert an ip conntrack status to a string.
-     */
-    public static String stringForIpConntrackStatus(int flags) {
-        final StringBuilder sb = new StringBuilder();
-
-        if ((flags & IPS_EXPECTED) != 0) {
-            sb.append("IPS_EXPECTED");
-        }
-        if ((flags & IPS_SEEN_REPLY) != 0) {
-            if (sb.length() > 0) sb.append("|");
-            sb.append("IPS_SEEN_REPLY");
-        }
-        if ((flags & IPS_ASSURED) != 0) {
-            if (sb.length() > 0) sb.append("|");
-            sb.append("IPS_ASSURED");
-        }
-        if ((flags & IPS_CONFIRMED) != 0) {
-            if (sb.length() > 0) sb.append("|");
-            sb.append("IPS_CONFIRMED");
-        }
-        if ((flags & IPS_SRC_NAT) != 0) {
-            if (sb.length() > 0) sb.append("|");
-            sb.append("IPS_SRC_NAT");
-        }
-        if ((flags & IPS_DST_NAT) != 0) {
-            if (sb.length() > 0) sb.append("|");
-            sb.append("IPS_DST_NAT");
-        }
-        if ((flags & IPS_SEQ_ADJUST) != 0) {
-            if (sb.length() > 0) sb.append("|");
-            sb.append("IPS_SEQ_ADJUST");
-        }
-        if ((flags & IPS_SRC_NAT_DONE) != 0) {
-            if (sb.length() > 0) sb.append("|");
-            sb.append("IPS_SRC_NAT_DONE");
-        }
-        if ((flags & IPS_DST_NAT_DONE) != 0) {
-            if (sb.length() > 0) sb.append("|");
-            sb.append("IPS_DST_NAT_DONE");
-        }
-        if ((flags & IPS_DYING) != 0) {
-            if (sb.length() > 0) sb.append("|");
-            sb.append("IPS_DYING");
-        }
-        if ((flags & IPS_FIXED_TIMEOUT) != 0) {
-            if (sb.length() > 0) sb.append("|");
-            sb.append("IPS_FIXED_TIMEOUT");
-        }
-        if ((flags & IPS_TEMPLATE) != 0) {
-            if (sb.length() > 0) sb.append("|");
-            sb.append("IPS_TEMPLATE");
-        }
-        if ((flags & IPS_UNTRACKED) != 0) {
-            if (sb.length() > 0) sb.append("|");
-            sb.append("IPS_UNTRACKED");
-        }
-        if ((flags & IPS_HELPER) != 0) {
-            if (sb.length() > 0) sb.append("|");
-            sb.append("IPS_HELPER");
-        }
-        if ((flags & IPS_OFFLOAD) != 0) {
-            if (sb.length() > 0) sb.append("|");
-            sb.append("IPS_OFFLOAD");
-        }
-        if ((flags & IPS_HW_OFFLOAD) != 0) {
-            if (sb.length() > 0) sb.append("|");
-            sb.append("IPS_HW_OFFLOAD");
-        }
-        return sb.toString();
-    }
-
-    @Override
-    public String toString() {
-        return "ConntrackMessage{"
-                + "nlmsghdr{"
-                + (mHeader == null ? "" : mHeader.toString(OsConstants.NETLINK_NETFILTER))
-                + "}, "
-                + "nfgenmsg{" + nfGenMsg + "}, "
-                + "tuple_orig{" + tupleOrig + "}, "
-                + "tuple_reply{" + tupleReply + "}, "
-                + "status{" + status + "(" + stringForIpConntrackStatus(status) + ")" + "}, "
-                + "timeout_sec{" + Integer.toUnsignedLong(timeoutSec) + "}"
-                + "}";
-    }
-}
diff --git a/common/netlinkclient/src/android/net/netlink/InetDiagMessage.java b/common/netlinkclient/src/android/net/netlink/InetDiagMessage.java
deleted file mode 100644
index c085123..0000000
--- a/common/netlinkclient/src/android/net/netlink/InetDiagMessage.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.net.netlink;
-
-import static android.net.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY;
-import static android.net.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE;
-import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
-import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
-import static android.os.Process.INVALID_UID;
-import static android.system.OsConstants.AF_INET;
-import static android.system.OsConstants.AF_INET6;
-import static android.system.OsConstants.IPPROTO_UDP;
-import static android.system.OsConstants.NETLINK_INET_DIAG;
-
-import android.annotation.Nullable;
-import android.net.util.SocketUtils;
-import android.system.ErrnoException;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetSocketAddress;
-import java.net.SocketException;
-import java.net.UnknownHostException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-/**
- * A NetlinkMessage subclass for netlink inet_diag messages.
- *
- * see also: &lt;linux_src&gt;/include/uapi/linux/inet_diag.h
- *
- * @hide
- */
-public class InetDiagMessage extends NetlinkMessage {
-    public static final String TAG = "InetDiagMessage";
-    private static final int TIMEOUT_MS = 500;
-
-    public static byte[] InetDiagReqV2(int protocol, InetSocketAddress local,
-            InetSocketAddress remote, int family, short flags) {
-        return InetDiagReqV2(protocol, local, remote, family, flags, 0 /* pad */,
-                0 /* idiagExt */, StructInetDiagReqV2.INET_DIAG_REQ_V2_ALL_STATES);
-    }
-
-    /**
-     * Construct an inet_diag_req_v2 message. This method will throw {@code NullPointerException}
-     * if local and remote are not both null or both non-null.
-     *
-     * @param protocol the request protocol type. This should be set to one of IPPROTO_TCP,
-     *                 IPPROTO_UDP, or IPPROTO_UDPLITE.
-     * @param local local socket address of the target socket. This will be packed into a
-     *              {@Code StructInetDiagSockId}. Request to diagnose for all sockets if both of
-     *              local or remote address is null.
-     * @param remote remote socket address of the target socket. This will be packed into a
-     *              {@Code StructInetDiagSockId}. Request to diagnose for all sockets if both of
-     *              local or remote address is null.
-     * @param family the ip family of the request message. This should be set to either AF_INET or
-     *               AF_INET6 for IPv4 or IPv6 sockets respectively.
-     * @param flags message flags. See &lt;linux_src&gt;/include/uapi/linux/netlink.h.
-     * @param pad for raw socket protocol specification.
-     * @param idiagExt a set of flags defining what kind of extended information to report.
-     * @param state a bit mask that defines a filter of socket states.
-     *
-     * @return bytes array representation of the message
-     **/
-    public static byte[] InetDiagReqV2(int protocol, @Nullable InetSocketAddress local,
-            @Nullable InetSocketAddress remote, int family, short flags, int pad, int idiagExt,
-            int state) throws NullPointerException {
-        final byte[] bytes = new byte[StructNlMsgHdr.STRUCT_SIZE + StructInetDiagReqV2.STRUCT_SIZE];
-        final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
-        byteBuffer.order(ByteOrder.nativeOrder());
-
-        final StructNlMsgHdr nlMsgHdr = new StructNlMsgHdr();
-        nlMsgHdr.nlmsg_len = bytes.length;
-        nlMsgHdr.nlmsg_type = SOCK_DIAG_BY_FAMILY;
-        nlMsgHdr.nlmsg_flags = flags;
-        nlMsgHdr.pack(byteBuffer);
-        final StructInetDiagReqV2 inetDiagReqV2 =
-                new StructInetDiagReqV2(protocol, local, remote, family, pad, idiagExt, state);
-
-        inetDiagReqV2.pack(byteBuffer);
-        return bytes;
-    }
-
-    public StructInetDiagMsg mStructInetDiagMsg;
-
-    private InetDiagMessage(StructNlMsgHdr header) {
-        super(header);
-        mStructInetDiagMsg = new StructInetDiagMsg();
-    }
-
-    public static InetDiagMessage parse(StructNlMsgHdr header, ByteBuffer byteBuffer) {
-        final InetDiagMessage msg = new InetDiagMessage(header);
-        msg.mStructInetDiagMsg = StructInetDiagMsg.parse(byteBuffer);
-        return msg;
-    }
-
-    private static int lookupUidByFamily(int protocol, InetSocketAddress local,
-                                         InetSocketAddress remote, int family, short flags,
-                                         FileDescriptor fd)
-            throws ErrnoException, InterruptedIOException {
-        byte[] msg = InetDiagReqV2(protocol, local, remote, family, flags);
-        NetlinkSocket.sendMessage(fd, msg, 0, msg.length, TIMEOUT_MS);
-        ByteBuffer response = NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT_MS);
-
-        final NetlinkMessage nlMsg = NetlinkMessage.parse(response, NETLINK_INET_DIAG);
-        final StructNlMsgHdr hdr = nlMsg.getHeader();
-        if (hdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) {
-            return INVALID_UID;
-        }
-        if (nlMsg instanceof InetDiagMessage) {
-            return ((InetDiagMessage) nlMsg).mStructInetDiagMsg.idiag_uid;
-        }
-        return INVALID_UID;
-    }
-
-    private static final int FAMILY[] = {AF_INET6, AF_INET};
-
-    private static int lookupUid(int protocol, InetSocketAddress local,
-                                 InetSocketAddress remote, FileDescriptor fd)
-            throws ErrnoException, InterruptedIOException {
-        int uid;
-
-        for (int family : FAMILY) {
-            /**
-             * For exact match lookup, swap local and remote for UDP lookups due to kernel
-             * bug which will not be fixed. See aosp/755889 and
-             * https://www.mail-archive.com/[email protected]/msg248638.html
-             */
-            if (protocol == IPPROTO_UDP) {
-                uid = lookupUidByFamily(protocol, remote, local, family, NLM_F_REQUEST, fd);
-            } else {
-                uid = lookupUidByFamily(protocol, local, remote, family, NLM_F_REQUEST, fd);
-            }
-            if (uid != INVALID_UID) {
-                return uid;
-            }
-        }
-
-        /**
-         * For UDP it's possible for a socket to send packets to arbitrary destinations, even if the
-         * socket is not connected (and even if the socket is connected to a different destination).
-         * If we want this API to work for such packets, then on miss we need to do a second lookup
-         * with only the local address and port filled in.
-         * Always use flags == NLM_F_REQUEST | NLM_F_DUMP for wildcard.
-         */
-        if (protocol == IPPROTO_UDP) {
-            try {
-                InetSocketAddress wildcard = new InetSocketAddress(
-                        Inet6Address.getByName("::"), 0);
-                uid = lookupUidByFamily(protocol, local, wildcard, AF_INET6,
-                        (short) (NLM_F_REQUEST | NLM_F_DUMP), fd);
-                if (uid != INVALID_UID) {
-                    return uid;
-                }
-                wildcard = new InetSocketAddress(Inet4Address.getByName("0.0.0.0"), 0);
-                uid = lookupUidByFamily(protocol, local, wildcard, AF_INET,
-                        (short) (NLM_F_REQUEST | NLM_F_DUMP), fd);
-                if (uid != INVALID_UID) {
-                    return uid;
-                }
-            } catch (UnknownHostException e) {
-                Log.e(TAG, e.toString());
-            }
-        }
-        return INVALID_UID;
-    }
-
-    /**
-     * Use an inet_diag socket to look up the UID associated with the input local and remote
-     * address/port and protocol of a connection.
-     */
-    public static int getConnectionOwnerUid(int protocol, InetSocketAddress local,
-                                            InetSocketAddress remote) {
-        int uid = INVALID_UID;
-        FileDescriptor fd = null;
-        try {
-            fd = NetlinkSocket.forProto(NETLINK_INET_DIAG);
-            NetlinkSocket.connectToKernel(fd);
-            uid = lookupUid(protocol, local, remote, fd);
-        } catch (ErrnoException | SocketException | IllegalArgumentException
-                | InterruptedIOException e) {
-            Log.e(TAG, e.toString());
-        } finally {
-            if (fd != null) {
-                try {
-                    SocketUtils.closeSocket(fd);
-                } catch (IOException e) {
-                    Log.e(TAG, e.toString());
-                }
-            }
-        }
-        return uid;
-    }
-
-    @Override
-    public String toString() {
-        return "InetDiagMessage{ "
-                + "nlmsghdr{"
-                + (mHeader == null ? "" : mHeader.toString(NETLINK_INET_DIAG)) + "}, "
-                + "inet_diag_msg{"
-                + (mStructInetDiagMsg == null ? "" : mStructInetDiagMsg.toString()) + "} "
-                + "}";
-    }
-}
diff --git a/common/netlinkclient/src/android/net/netlink/NdOption.java b/common/netlinkclient/src/android/net/netlink/NdOption.java
deleted file mode 100644
index ab9d2e6..0000000
--- a/common/netlinkclient/src/android/net/netlink/NdOption.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.net.netlink;
-
-import java.nio.ByteBuffer;
-
-/**
- * Base class for IPv6 neighbour discovery options.
- */
-public class NdOption {
-    public static final int STRUCT_SIZE = 2;
-
-    /** The option type. */
-    public final byte type;
-    /** The length of the option in 8-byte units. Actually an unsigned 8-bit integer */
-    public final int length;
-
-    /** Constructs a new NdOption. */
-    NdOption(byte type, int length) {
-        this.type = type;
-        this.length = length;
-    }
-
-    /**
-     * Parses a neighbour discovery option.
-     *
-     * Parses (and consumes) the option if it is of a known type. If the option is of an unknown
-     * type, advances the buffer (so the caller can continue parsing if desired) and returns
-     * {@link #UNKNOWN}. If the option claims a length of 0, returns null because parsing cannot
-     * continue.
-     *
-     * No checks are performed on the length other than ensuring it is not 0, so if a caller wants
-     * to deal with options that might overflow the structure that contains them, it must explicitly
-     * set the buffer's limit to the position at which that structure ends.
-     *
-     * @param buf the buffer to parse.
-     * @return a subclass of {@link NdOption}, or {@code null} for an unknown or malformed option.
-     */
-    public static NdOption parse(ByteBuffer buf) {
-        if (buf == null || buf.remaining() < STRUCT_SIZE) return null;
-
-        // Peek the type without advancing the buffer.
-        byte type = buf.get(buf.position());
-        int length = Byte.toUnsignedInt(buf.get(buf.position() + 1));
-        if (length == 0) return null;
-
-        switch (type) {
-            case StructNdOptPref64.TYPE:
-                return StructNdOptPref64.parse(buf);
-
-            default:
-                int newPosition = Math.min(buf.limit(), buf.position() + length * 8);
-                buf.position(newPosition);
-                return UNKNOWN;
-        }
-    }
-
-    void writeToByteBuffer(ByteBuffer buf) {
-        buf.put(type);
-        buf.put((byte) length);
-    }
-
-    @Override
-    public String toString() {
-        return String.format("NdOption(%d, %d)", Byte.toUnsignedInt(type), length);
-    }
-
-    public static final NdOption UNKNOWN = new NdOption((byte) 0, 0);
-}
diff --git a/common/netlinkclient/src/android/net/netlink/NduseroptMessage.java b/common/netlinkclient/src/android/net/netlink/NduseroptMessage.java
deleted file mode 100644
index 7b976f1..0000000
--- a/common/netlinkclient/src/android/net/netlink/NduseroptMessage.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.net.netlink;
-
-import static android.system.OsConstants.AF_INET6;
-
-import androidx.annotation.NonNull;
-
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-/**
- * A NetlinkMessage subclass for RTM_NEWNDUSEROPT messages.
- */
-public class NduseroptMessage extends NetlinkMessage {
-    public static final int STRUCT_SIZE = 16;
-
-    static final int NDUSEROPT_SRCADDR = 1;
-
-    /** The address family. Presumably always AF_INET6. */
-    public final byte family;
-    /**
-     * The total length in bytes of the options that follow this structure.
-     * Actually a 16-bit unsigned integer.
-     */
-    public final int opts_len;
-    /** The interface index on which the options were received. */
-    public final int ifindex;
-    /** The ICMP type of the packet that contained the options. */
-    public final byte icmp_type;
-    /** The ICMP code of the packet that contained the options. */
-    public final byte icmp_code;
-
-    /**
-     * ND option that was in this message.
-     * Even though the length field is called "opts_len", the kernel only ever sends one option per
-     * message. It is unlikely that this will ever change as it would break existing userspace code.
-     * But if it does, we can simply update this code, since userspace is typically newer than the
-     * kernel.
-     */
-    public final NdOption option;
-
-    /** The IP address that sent the packet containing the option. */
-    public final InetAddress srcaddr;
-
-    NduseroptMessage(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf)
-            throws UnknownHostException {
-        super(header);
-
-        // The structure itself.
-        buf.order(ByteOrder.nativeOrder());  // Restored in the finally clause inside parse().
-        final int start = buf.position();
-        family = buf.get();
-        buf.get();  // Skip 1 byte of padding.
-        opts_len = Short.toUnsignedInt(buf.getShort());
-        ifindex = buf.getInt();
-        icmp_type = buf.get();
-        icmp_code = buf.get();
-        buf.position(buf.position() + 6);  // Skip 6 bytes of padding.
-
-        // The ND option.
-        // Ensure we don't read past opts_len even if the option length is invalid.
-        // Note that this check is not really necessary since if the option length is not valid,
-        // this struct won't be very useful to the caller.
-        buf.order(ByteOrder.BIG_ENDIAN);
-        int oldLimit = buf.limit();
-        buf.limit(start + STRUCT_SIZE + opts_len);
-        try {
-            option = NdOption.parse(buf);
-        } finally {
-            buf.limit(oldLimit);
-        }
-
-        // The source address.
-        int newPosition = start + STRUCT_SIZE + opts_len;
-        if (newPosition >= buf.limit()) {
-            throw new IllegalArgumentException("ND options extend past end of buffer");
-        }
-        buf.position(newPosition);
-
-        StructNlAttr nla = StructNlAttr.parse(buf);
-        if (nla == null || nla.nla_type != NDUSEROPT_SRCADDR || nla.nla_value == null) {
-            throw new IllegalArgumentException("Invalid source address in ND useropt");
-        }
-        if (family == AF_INET6) {
-            // InetAddress.getByAddress only looks at the ifindex if the address type needs one.
-            srcaddr = Inet6Address.getByAddress(null /* hostname */, nla.nla_value, ifindex);
-        } else {
-            srcaddr = InetAddress.getByAddress(nla.nla_value);
-        }
-    }
-
-    /**
-     * Parses a StructNduseroptmsg from a {@link ByteBuffer}.
-     *
-     * @param header the netlink message header.
-     * @param buf The buffer from which to parse the option. The buffer's byte order must be
-     *            {@link java.nio.ByteOrder#BIG_ENDIAN}.
-     * @return the parsed option, or {@code null} if the option could not be parsed successfully
-     *         (for example, if it was truncated, or if the prefix length code was wrong).
-     */
-    public static NduseroptMessage parse(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf) {
-        if (buf == null || buf.remaining() < STRUCT_SIZE) return null;
-        ByteOrder oldOrder = buf.order();
-        try {
-            return new NduseroptMessage(header, buf);
-        } catch (IllegalArgumentException | UnknownHostException | BufferUnderflowException e) {
-            // Not great, but better than throwing an exception that might crash the caller.
-            // Convention in this package is that null indicates that the option was truncated, so
-            // callers must already handle it.
-            return null;
-        } finally {
-            buf.order(oldOrder);
-        }
-    }
-
-    @Override
-    public String toString() {
-        return String.format("Nduseroptmsg(%d, %d, %d, %d, %d, %s)",
-                family, opts_len, ifindex, Byte.toUnsignedInt(icmp_type),
-                Byte.toUnsignedInt(icmp_code), srcaddr.getHostAddress());
-    }
-}
diff --git a/common/netlinkclient/src/android/net/netlink/NetlinkConstants.java b/common/netlinkclient/src/android/net/netlink/NetlinkConstants.java
deleted file mode 100644
index b4d9cc0..0000000
--- a/common/netlinkclient/src/android/net/netlink/NetlinkConstants.java
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Copyright (C) 2015 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 android.net.netlink;
-
-import android.annotation.NonNull;
-import android.system.OsConstants;
-
-import java.nio.ByteBuffer;
-
-
-/**
- * Various constants and static helper methods for netlink communications.
- *
- * Values taken from:
- *
- *     include/uapi/linux/netfilter/nfnetlink.h
- *     include/uapi/linux/netfilter/nfnetlink_conntrack.h
- *     include/uapi/linux/netlink.h
- *     include/uapi/linux/rtnetlink.h
- *
- * @hide
- */
-public class NetlinkConstants {
-    private NetlinkConstants() {}
-
-    public static final int NLA_ALIGNTO = 4;
-    /**
-     * Flag for dumping struct tcp_info.
-     * Corresponding to enum definition in external/strace/linux/inet_diag.h.
-     */
-    public static final int INET_DIAG_MEMINFO = 1;
-
-    public static final int SOCKDIAG_MSG_HEADER_SIZE =
-            StructNlMsgHdr.STRUCT_SIZE + StructInetDiagMsg.STRUCT_SIZE;
-
-    public static final int alignedLengthOf(short length) {
-        final int intLength = (int) length & 0xffff;
-        return alignedLengthOf(intLength);
-    }
-
-    public static final int alignedLengthOf(int length) {
-        if (length <= 0) { return 0; }
-        return (((length + NLA_ALIGNTO - 1) / NLA_ALIGNTO) * NLA_ALIGNTO);
-    }
-
-    public static String stringForAddressFamily(int family) {
-        if (family == OsConstants.AF_INET) { return "AF_INET"; }
-        if (family == OsConstants.AF_INET6) { return "AF_INET6"; }
-        if (family == OsConstants.AF_NETLINK) { return "AF_NETLINK"; }
-        return String.valueOf(family);
-    }
-
-    public static String stringForProtocol(int protocol) {
-        if (protocol == OsConstants.IPPROTO_TCP) { return "IPPROTO_TCP"; }
-        if (protocol == OsConstants.IPPROTO_UDP) { return "IPPROTO_UDP"; }
-        return String.valueOf(protocol);
-    }
-
-    public static String hexify(byte[] bytes) {
-        if (bytes == null) { return "(null)"; }
-        return toHexString(bytes, 0, bytes.length);
-    }
-
-    public static String hexify(ByteBuffer buffer) {
-        if (buffer == null) { return "(null)"; }
-        return toHexString(
-                buffer.array(), buffer.position(), buffer.remaining());
-    }
-
-    // Known values for struct nlmsghdr nlm_type.
-    public static final short NLMSG_NOOP                    = 1;   // Nothing
-    public static final short NLMSG_ERROR                   = 2;   // Error
-    public static final short NLMSG_DONE                    = 3;   // End of a dump
-    public static final short NLMSG_OVERRUN                 = 4;   // Data lost
-    public static final short NLMSG_MAX_RESERVED            = 15;  // Max reserved value
-
-    public static final short RTM_NEWLINK                   = 16;
-    public static final short RTM_DELLINK                   = 17;
-    public static final short RTM_GETLINK                   = 18;
-    public static final short RTM_SETLINK                   = 19;
-    public static final short RTM_NEWADDR                   = 20;
-    public static final short RTM_DELADDR                   = 21;
-    public static final short RTM_GETADDR                   = 22;
-    public static final short RTM_NEWROUTE                  = 24;
-    public static final short RTM_DELROUTE                  = 25;
-    public static final short RTM_GETROUTE                  = 26;
-    public static final short RTM_NEWNEIGH                  = 28;
-    public static final short RTM_DELNEIGH                  = 29;
-    public static final short RTM_GETNEIGH                  = 30;
-    public static final short RTM_NEWRULE                   = 32;
-    public static final short RTM_DELRULE                   = 33;
-    public static final short RTM_GETRULE                   = 34;
-    public static final short RTM_NEWNDUSEROPT              = 68;
-
-    // Netfilter netlink message types are presented by two bytes: high byte subsystem and
-    // low byte operation. See the macro NFNL_SUBSYS_ID and NFNL_MSG_TYPE in
-    // include/uapi/linux/netfilter/nfnetlink.h
-    public static final short NFNL_SUBSYS_CTNETLINK         = 1;
-
-    public static final short IPCTNL_MSG_CT_NEW             = 0;
-    public static final short IPCTNL_MSG_CT_GET             = 1;
-    public static final short IPCTNL_MSG_CT_DELETE          = 2;
-    public static final short IPCTNL_MSG_CT_GET_CTRZERO     = 3;
-    public static final short IPCTNL_MSG_CT_GET_STATS_CPU   = 4;
-    public static final short IPCTNL_MSG_CT_GET_STATS       = 5;
-    public static final short IPCTNL_MSG_CT_GET_DYING       = 6;
-    public static final short IPCTNL_MSG_CT_GET_UNCONFIRMED = 7;
-
-    /* see include/uapi/linux/sock_diag.h */
-    public static final short SOCK_DIAG_BY_FAMILY = 20;
-
-    // Netlink groups.
-    public static final int RTNLGRP_ND_USEROPT = 20;
-    public static final int RTMGRP_ND_USEROPT = 1 << (RTNLGRP_ND_USEROPT - 1);
-
-    /**
-     * Convert a netlink message type to a string for control message.
-     */
-    @NonNull
-    private static String stringForCtlMsgType(short nlmType) {
-        switch (nlmType) {
-            case NLMSG_NOOP: return "NLMSG_NOOP";
-            case NLMSG_ERROR: return "NLMSG_ERROR";
-            case NLMSG_DONE: return "NLMSG_DONE";
-            case NLMSG_OVERRUN: return "NLMSG_OVERRUN";
-            default: return "unknown control message type: " + String.valueOf(nlmType);
-        }
-    }
-
-    /**
-     * Convert a netlink message type to a string for NETLINK_ROUTE.
-     */
-    @NonNull
-    private static String stringForRtMsgType(short nlmType) {
-        switch (nlmType) {
-            case RTM_NEWLINK: return "RTM_NEWLINK";
-            case RTM_DELLINK: return "RTM_DELLINK";
-            case RTM_GETLINK: return "RTM_GETLINK";
-            case RTM_SETLINK: return "RTM_SETLINK";
-            case RTM_NEWADDR: return "RTM_NEWADDR";
-            case RTM_DELADDR: return "RTM_DELADDR";
-            case RTM_GETADDR: return "RTM_GETADDR";
-            case RTM_NEWROUTE: return "RTM_NEWROUTE";
-            case RTM_DELROUTE: return "RTM_DELROUTE";
-            case RTM_GETROUTE: return "RTM_GETROUTE";
-            case RTM_NEWNEIGH: return "RTM_NEWNEIGH";
-            case RTM_DELNEIGH: return "RTM_DELNEIGH";
-            case RTM_GETNEIGH: return "RTM_GETNEIGH";
-            case RTM_NEWRULE: return "RTM_NEWRULE";
-            case RTM_DELRULE: return "RTM_DELRULE";
-            case RTM_GETRULE: return "RTM_GETRULE";
-            case RTM_NEWNDUSEROPT: return "RTM_NEWNDUSEROPT";
-            default: return "unknown RTM type: " + String.valueOf(nlmType);
-        }
-    }
-
-    /**
-     * Convert a netlink message type to a string for NETLINK_INET_DIAG.
-     */
-    @NonNull
-    private static String stringForInetDiagMsgType(short nlmType) {
-        switch (nlmType) {
-            case SOCK_DIAG_BY_FAMILY: return "SOCK_DIAG_BY_FAMILY";
-            default: return "unknown SOCK_DIAG type: " + String.valueOf(nlmType);
-        }
-    }
-
-    /**
-     * Convert a netlink message type to a string for NETLINK_NETFILTER.
-     */
-    @NonNull
-    private static String stringForNfMsgType(short nlmType) {
-        final byte subsysId = (byte) (nlmType >> 8);
-        final byte msgType = (byte) nlmType;
-        switch (subsysId) {
-            case NFNL_SUBSYS_CTNETLINK:
-                switch (msgType) {
-                    case IPCTNL_MSG_CT_NEW: return "IPCTNL_MSG_CT_NEW";
-                    case IPCTNL_MSG_CT_GET: return "IPCTNL_MSG_CT_GET";
-                    case IPCTNL_MSG_CT_DELETE: return "IPCTNL_MSG_CT_DELETE";
-                    case IPCTNL_MSG_CT_GET_CTRZERO: return "IPCTNL_MSG_CT_GET_CTRZERO";
-                    case IPCTNL_MSG_CT_GET_STATS_CPU: return "IPCTNL_MSG_CT_GET_STATS_CPU";
-                    case IPCTNL_MSG_CT_GET_STATS: return "IPCTNL_MSG_CT_GET_STATS";
-                    case IPCTNL_MSG_CT_GET_DYING: return "IPCTNL_MSG_CT_GET_DYING";
-                    case IPCTNL_MSG_CT_GET_UNCONFIRMED: return "IPCTNL_MSG_CT_GET_UNCONFIRMED";
-                }
-                break;
-        }
-        return "unknown NETFILTER type: " + String.valueOf(nlmType);
-    }
-
-    /**
-     * Convert a netlink message type to a string by netlink family.
-     */
-    @NonNull
-    public static String stringForNlMsgType(short nlmType, int nlFamily) {
-        // Reserved control messages. The netlink family is ignored.
-        // See NLMSG_MIN_TYPE in include/uapi/linux/netlink.h.
-        if (nlmType <= NLMSG_MAX_RESERVED) return stringForCtlMsgType(nlmType);
-
-        // Netlink family messages. The netlink family is required. Note that the reason for using
-        // if-statement is that switch-case can't be used because the OsConstants.NETLINK_* are
-        // not constant.
-        if (nlFamily == OsConstants.NETLINK_ROUTE) return stringForRtMsgType(nlmType);
-        if (nlFamily == OsConstants.NETLINK_INET_DIAG) return stringForInetDiagMsgType(nlmType);
-        if (nlFamily == OsConstants.NETLINK_NETFILTER) return stringForNfMsgType(nlmType);
-
-        return "unknown type: " + String.valueOf(nlmType);
-    }
-
-    private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
-            'A', 'B', 'C', 'D', 'E', 'F' };
-    /**
-     * Convert a byte array to a hexadecimal string.
-     */
-    public static String toHexString(byte[] array, int offset, int length) {
-        char[] buf = new char[length * 2];
-
-        int bufIndex = 0;
-        for (int i = offset; i < offset + length; i++) {
-            byte b = array[i];
-            buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
-            buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
-        }
-
-        return new String(buf);
-    }
-}
diff --git a/common/netlinkclient/src/android/net/netlink/NetlinkErrorMessage.java b/common/netlinkclient/src/android/net/netlink/NetlinkErrorMessage.java
deleted file mode 100644
index 36b02c6..0000000
--- a/common/netlinkclient/src/android/net/netlink/NetlinkErrorMessage.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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 android.net.netlink;
-
-import java.nio.ByteBuffer;
-
-
-/**
- * A NetlinkMessage subclass for netlink error messages.
- *
- * @hide
- */
-public class NetlinkErrorMessage extends NetlinkMessage {
-
-    public static NetlinkErrorMessage parse(StructNlMsgHdr header, ByteBuffer byteBuffer) {
-        final NetlinkErrorMessage errorMsg = new NetlinkErrorMessage(header);
-
-        errorMsg.mNlMsgErr = StructNlMsgErr.parse(byteBuffer);
-        if (errorMsg.mNlMsgErr == null) {
-            return null;
-        }
-
-        return errorMsg;
-    }
-
-    private StructNlMsgErr mNlMsgErr;
-
-    NetlinkErrorMessage(StructNlMsgHdr header) {
-        super(header);
-        mNlMsgErr = null;
-    }
-
-    public StructNlMsgErr getNlMsgError() {
-        return mNlMsgErr;
-    }
-
-    @Override
-    public String toString() {
-        return "NetlinkErrorMessage{ "
-                + "nlmsghdr{" + (mHeader == null ? "" : mHeader.toString()) + "}, "
-                + "nlmsgerr{" + (mNlMsgErr == null ? "" : mNlMsgErr.toString()) + "} "
-                + "}";
-    }
-}
diff --git a/common/netlinkclient/src/android/net/netlink/NetlinkMessage.java b/common/netlinkclient/src/android/net/netlink/NetlinkMessage.java
deleted file mode 100644
index ab2c223..0000000
--- a/common/netlinkclient/src/android/net/netlink/NetlinkMessage.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * 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 android.net.netlink;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.system.OsConstants;
-
-import java.nio.ByteBuffer;
-
-
-/**
- * NetlinkMessage base class for other, more specific netlink message types.
- *
- * Classes that extend NetlinkMessage should:
- *     - implement a public static parse(StructNlMsgHdr, ByteBuffer) method
- *     - returning either null (parse errors) or a new object of the subclass
- *       type (cast-able to NetlinkMessage)
- *
- * NetlinkMessage.parse() should be updated to know which nlmsg_type values
- * correspond with which message subclasses.
- *
- * @hide
- */
-public class NetlinkMessage {
-    private final static String TAG = "NetlinkMessage";
-
-    /**
-     * Parsing netlink messages for reserved control message or specific netlink message. The
-     * netlink family is required for parsing specific netlink message. See man-pages/netlink.
-     */
-    @Nullable
-    public static NetlinkMessage parse(@NonNull ByteBuffer byteBuffer, int nlFamily) {
-        final int startPosition = (byteBuffer != null) ? byteBuffer.position() : -1;
-        final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(byteBuffer);
-        if (nlmsghdr == null) {
-            return null;
-        }
-
-        int payloadLength = NetlinkConstants.alignedLengthOf(nlmsghdr.nlmsg_len);
-        payloadLength -= StructNlMsgHdr.STRUCT_SIZE;
-        if (payloadLength < 0 || payloadLength > byteBuffer.remaining()) {
-            // Malformed message or runt buffer.  Pretend the buffer was consumed.
-            byteBuffer.position(byteBuffer.limit());
-            return null;
-        }
-
-        // Reserved control messages. The netlink family is ignored.
-        // See NLMSG_MIN_TYPE in include/uapi/linux/netlink.h.
-        if (nlmsghdr.nlmsg_type <= NetlinkConstants.NLMSG_MAX_RESERVED) {
-            return parseCtlMessage(nlmsghdr, byteBuffer, payloadLength);
-        }
-
-        // Netlink family messages. The netlink family is required. Note that the reason for using
-        // if-statement is that switch-case can't be used because the OsConstants.NETLINK_* are
-        // not constant.
-        if (nlFamily == OsConstants.NETLINK_ROUTE) {
-            return parseRtMessage(nlmsghdr, byteBuffer);
-        } else if (nlFamily == OsConstants.NETLINK_INET_DIAG) {
-            return parseInetDiagMessage(nlmsghdr, byteBuffer);
-        } else if (nlFamily == OsConstants.NETLINK_NETFILTER) {
-            return parseNfMessage(nlmsghdr, byteBuffer);
-        }
-
-        return null;
-    }
-
-    protected StructNlMsgHdr mHeader;
-
-    public NetlinkMessage(StructNlMsgHdr nlmsghdr) {
-        mHeader = nlmsghdr;
-    }
-
-    public StructNlMsgHdr getHeader() {
-        return mHeader;
-    }
-
-    @Override
-    public String toString() {
-        // The netlink family is not provided to StructNlMsgHdr#toString because NetlinkMessage
-        // doesn't store the information. So the netlink message type can't be transformed into
-        // a string by StructNlMsgHdr#toString and just keep as an integer. The specific message
-        // which inherits NetlinkMessage could override NetlinkMessage#toString and provide the
-        // specific netlink family to StructNlMsgHdr#toString.
-        return "NetlinkMessage{" + (mHeader == null ? "" : mHeader.toString()) + "}";
-    }
-
-    @NonNull
-    private static NetlinkMessage parseCtlMessage(@NonNull StructNlMsgHdr nlmsghdr,
-            @NonNull ByteBuffer byteBuffer, int payloadLength) {
-        switch (nlmsghdr.nlmsg_type) {
-            case NetlinkConstants.NLMSG_ERROR:
-                return (NetlinkMessage) NetlinkErrorMessage.parse(nlmsghdr, byteBuffer);
-            default: {
-                // Other netlink control messages. Just parse the header for now,
-                // pretending the whole message was consumed.
-                byteBuffer.position(byteBuffer.position() + payloadLength);
-                return new NetlinkMessage(nlmsghdr);
-            }
-        }
-    }
-
-    @Nullable
-    private static NetlinkMessage parseRtMessage(@NonNull StructNlMsgHdr nlmsghdr,
-            @NonNull ByteBuffer byteBuffer) {
-        switch (nlmsghdr.nlmsg_type) {
-            case NetlinkConstants.RTM_NEWNEIGH:
-            case NetlinkConstants.RTM_DELNEIGH:
-            case NetlinkConstants.RTM_GETNEIGH:
-                return (NetlinkMessage) RtNetlinkNeighborMessage.parse(nlmsghdr, byteBuffer);
-            case NetlinkConstants.RTM_NEWNDUSEROPT:
-                return (NetlinkMessage) NduseroptMessage.parse(nlmsghdr, byteBuffer);
-            default: return null;
-        }
-    }
-
-    @Nullable
-    private static NetlinkMessage parseInetDiagMessage(@NonNull StructNlMsgHdr nlmsghdr,
-            @NonNull ByteBuffer byteBuffer) {
-        switch (nlmsghdr.nlmsg_type) {
-            case NetlinkConstants.SOCK_DIAG_BY_FAMILY:
-                return (NetlinkMessage) InetDiagMessage.parse(nlmsghdr, byteBuffer);
-            default: return null;
-        }
-    }
-
-    @Nullable
-    private static NetlinkMessage parseNfMessage(@NonNull StructNlMsgHdr nlmsghdr,
-            @NonNull ByteBuffer byteBuffer) {
-        switch (nlmsghdr.nlmsg_type) {
-            case NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8
-                    | NetlinkConstants.IPCTNL_MSG_CT_NEW:
-            case NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8
-                    | NetlinkConstants.IPCTNL_MSG_CT_DELETE:
-                return (NetlinkMessage) ConntrackMessage.parse(nlmsghdr, byteBuffer);
-            default: return null;
-        }
-    }
-}
diff --git a/common/netlinkclient/src/android/net/netlink/NetlinkSocket.java b/common/netlinkclient/src/android/net/netlink/NetlinkSocket.java
deleted file mode 100644
index ab4c052..0000000
--- a/common/netlinkclient/src/android/net/netlink/NetlinkSocket.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2015 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 android.net.netlink;
-
-import static android.net.util.SocketUtils.makeNetlinkSocketAddress;
-import static android.system.OsConstants.AF_NETLINK;
-import static android.system.OsConstants.EIO;
-import static android.system.OsConstants.EPROTO;
-import static android.system.OsConstants.ETIMEDOUT;
-import static android.system.OsConstants.SOCK_DGRAM;
-import static android.system.OsConstants.SOL_SOCKET;
-import static android.system.OsConstants.SO_RCVBUF;
-import static android.system.OsConstants.SO_RCVTIMEO;
-import static android.system.OsConstants.SO_SNDTIMEO;
-
-import android.net.util.SocketUtils;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.StructTimeval;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.net.SocketException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-
-/**
- * NetlinkSocket
- *
- * A small static class to assist with AF_NETLINK socket operations.
- *
- * @hide
- */
-public class NetlinkSocket {
-    private static final String TAG = "NetlinkSocket";
-
-    public static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
-    public static final int SOCKET_RECV_BUFSIZE = 64 * 1024;
-
-    public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException {
-        final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage";
-        final long IO_TIMEOUT = 300L;
-
-        final FileDescriptor fd = forProto(nlProto);
-
-        try {
-            connectToKernel(fd);
-            sendMessage(fd, msg, 0, msg.length, IO_TIMEOUT);
-            final ByteBuffer bytes = recvMessage(fd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT);
-            // recvMessage() guaranteed to not return null if it did not throw.
-            final NetlinkMessage response = NetlinkMessage.parse(bytes, nlProto);
-            if (response != null && response instanceof NetlinkErrorMessage &&
-                    (((NetlinkErrorMessage) response).getNlMsgError() != null)) {
-                final int errno = ((NetlinkErrorMessage) response).getNlMsgError().error;
-                if (errno != 0) {
-                    // TODO: consider ignoring EINVAL (-22), which appears to be
-                    // normal when probing a neighbor for which the kernel does
-                    // not already have / no longer has a link layer address.
-                    Log.e(TAG, errPrefix + ", errmsg=" + response.toString());
-                    // Note: convert kernel errnos (negative) into userspace errnos (positive).
-                    throw new ErrnoException(response.toString(), Math.abs(errno));
-                }
-            } else {
-                final String errmsg;
-                if (response == null) {
-                    bytes.position(0);
-                    errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes);
-                } else {
-                    errmsg = response.toString();
-                }
-                Log.e(TAG, errPrefix + ", errmsg=" + errmsg);
-                throw new ErrnoException(errmsg, EPROTO);
-            }
-        } catch (InterruptedIOException e) {
-            Log.e(TAG, errPrefix, e);
-            throw new ErrnoException(errPrefix, ETIMEDOUT, e);
-        } catch (SocketException e) {
-            Log.e(TAG, errPrefix, e);
-            throw new ErrnoException(errPrefix, EIO, e);
-        } finally {
-            try {
-                SocketUtils.closeSocket(fd);
-            } catch (IOException e) {
-                // Nothing we can do here
-            }
-        }
-    }
-
-    public static FileDescriptor forProto(int nlProto) throws ErrnoException {
-        final FileDescriptor fd = Os.socket(AF_NETLINK, SOCK_DGRAM, nlProto);
-        Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, SOCKET_RECV_BUFSIZE);
-        return fd;
-    }
-
-    public static void connectToKernel(FileDescriptor fd) throws ErrnoException, SocketException {
-        Os.connect(fd, makeNetlinkSocketAddress(0, 0));
-    }
-
-    private static void checkTimeout(long timeoutMs) {
-        if (timeoutMs < 0) {
-            throw new IllegalArgumentException("Negative timeouts not permitted");
-        }
-    }
-
-    /**
-     * Wait up to |timeoutMs| (or until underlying socket error) for a
-     * netlink message of at most |bufsize| size.
-     *
-     * Multi-threaded calls with different timeouts will cause unexpected results.
-     */
-    public static ByteBuffer recvMessage(FileDescriptor fd, int bufsize, long timeoutMs)
-            throws ErrnoException, IllegalArgumentException, InterruptedIOException {
-        checkTimeout(timeoutMs);
-
-        Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(timeoutMs));
-
-        ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize);
-        int length = Os.read(fd, byteBuffer);
-        if (length == bufsize) {
-            Log.w(TAG, "maximum read");
-        }
-        byteBuffer.position(0);
-        byteBuffer.limit(length);
-        byteBuffer.order(ByteOrder.nativeOrder());
-        return byteBuffer;
-    }
-
-    /**
-     * Send a message to a peer to which this socket has previously connected,
-     * waiting at most |timeoutMs| milliseconds for the send to complete.
-     *
-     * Multi-threaded calls with different timeouts will cause unexpected results.
-     */
-    public static int sendMessage(
-            FileDescriptor fd, byte[] bytes, int offset, int count, long timeoutMs)
-            throws ErrnoException, IllegalArgumentException, InterruptedIOException {
-        checkTimeout(timeoutMs);
-        Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(timeoutMs));
-        return Os.write(fd, bytes, offset, count);
-    }
-}
diff --git a/common/netlinkclient/src/android/net/netlink/RtNetlinkNeighborMessage.java b/common/netlinkclient/src/android/net/netlink/RtNetlinkNeighborMessage.java
deleted file mode 100644
index 099ff07..0000000
--- a/common/netlinkclient/src/android/net/netlink/RtNetlinkNeighborMessage.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * 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 android.net.netlink;
-
-import static android.net.netlink.StructNlMsgHdr.NLM_F_ACK;
-import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
-import static android.net.netlink.StructNlMsgHdr.NLM_F_REPLACE;
-import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
-
-import android.system.OsConstants;
-
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-
-/**
- * A NetlinkMessage subclass for rtnetlink neighbor messages.
- *
- * see also: &lt;linux_src&gt;/include/uapi/linux/neighbour.h
- *
- * @hide
- */
-public class RtNetlinkNeighborMessage extends NetlinkMessage {
-    public static final short NDA_UNSPEC    = 0;
-    public static final short NDA_DST       = 1;
-    public static final short NDA_LLADDR    = 2;
-    public static final short NDA_CACHEINFO = 3;
-    public static final short NDA_PROBES    = 4;
-    public static final short NDA_VLAN      = 5;
-    public static final short NDA_PORT      = 6;
-    public static final short NDA_VNI       = 7;
-    public static final short NDA_IFINDEX   = 8;
-    public static final short NDA_MASTER    = 9;
-
-    public static RtNetlinkNeighborMessage parse(StructNlMsgHdr header, ByteBuffer byteBuffer) {
-        final RtNetlinkNeighborMessage neighMsg = new RtNetlinkNeighborMessage(header);
-
-        neighMsg.mNdmsg = StructNdMsg.parse(byteBuffer);
-        if (neighMsg.mNdmsg == null) {
-            return null;
-        }
-
-        // Some of these are message-type dependent, and not always present.
-        final int baseOffset = byteBuffer.position();
-        StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(NDA_DST, byteBuffer);
-        if (nlAttr != null) {
-            neighMsg.mDestination = nlAttr.getValueAsInetAddress();
-        }
-
-        byteBuffer.position(baseOffset);
-        nlAttr = StructNlAttr.findNextAttrOfType(NDA_LLADDR, byteBuffer);
-        if (nlAttr != null) {
-            neighMsg.mLinkLayerAddr = nlAttr.nla_value;
-        }
-
-        byteBuffer.position(baseOffset);
-        nlAttr = StructNlAttr.findNextAttrOfType(NDA_PROBES, byteBuffer);
-        if (nlAttr != null) {
-            neighMsg.mNumProbes = nlAttr.getValueAsInt(0);
-        }
-
-        byteBuffer.position(baseOffset);
-        nlAttr = StructNlAttr.findNextAttrOfType(NDA_CACHEINFO, byteBuffer);
-        if (nlAttr != null) {
-            neighMsg.mCacheInfo = StructNdaCacheInfo.parse(nlAttr.getValueAsByteBuffer());
-        }
-
-        final int kMinConsumed = StructNlMsgHdr.STRUCT_SIZE + StructNdMsg.STRUCT_SIZE;
-        final int kAdditionalSpace = NetlinkConstants.alignedLengthOf(
-                neighMsg.mHeader.nlmsg_len - kMinConsumed);
-        if (byteBuffer.remaining() < kAdditionalSpace) {
-            byteBuffer.position(byteBuffer.limit());
-        } else {
-            byteBuffer.position(baseOffset + kAdditionalSpace);
-        }
-
-        return neighMsg;
-    }
-
-    /**
-     * A convenience method to create an RTM_GETNEIGH request message.
-     */
-    public static byte[] newGetNeighborsRequest(int seqNo) {
-        final int length = StructNlMsgHdr.STRUCT_SIZE + StructNdMsg.STRUCT_SIZE;
-        final byte[] bytes = new byte[length];
-        final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
-        byteBuffer.order(ByteOrder.nativeOrder());
-
-        final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
-        nlmsghdr.nlmsg_len = length;
-        nlmsghdr.nlmsg_type = NetlinkConstants.RTM_GETNEIGH;
-        nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
-        nlmsghdr.nlmsg_seq = seqNo;
-        nlmsghdr.pack(byteBuffer);
-
-        final StructNdMsg ndmsg = new StructNdMsg();
-        ndmsg.pack(byteBuffer);
-
-        return bytes;
-    }
-
-    /**
-     * A convenience method to create an RTM_NEWNEIGH message, to modify
-     * the kernel's state information for a specific neighbor.
-     */
-    public static byte[] newNewNeighborMessage(
-            int seqNo, InetAddress ip, short nudState, int ifIndex, byte[] llAddr) {
-        final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
-        nlmsghdr.nlmsg_type = NetlinkConstants.RTM_NEWNEIGH;
-        nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE;
-        nlmsghdr.nlmsg_seq = seqNo;
-
-        final RtNetlinkNeighborMessage msg = new RtNetlinkNeighborMessage(nlmsghdr);
-        msg.mNdmsg = new StructNdMsg();
-        msg.mNdmsg.ndm_family =
-                (byte) ((ip instanceof Inet6Address) ? OsConstants.AF_INET6 : OsConstants.AF_INET);
-        msg.mNdmsg.ndm_ifindex = ifIndex;
-        msg.mNdmsg.ndm_state = nudState;
-        msg.mDestination = ip;
-        msg.mLinkLayerAddr = llAddr;  // might be null
-
-        final byte[] bytes = new byte[msg.getRequiredSpace()];
-        nlmsghdr.nlmsg_len = bytes.length;
-        final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
-        byteBuffer.order(ByteOrder.nativeOrder());
-        msg.pack(byteBuffer);
-        return bytes;
-    }
-
-    private StructNdMsg mNdmsg;
-    private InetAddress mDestination;
-    private byte[] mLinkLayerAddr;
-    private int mNumProbes;
-    private StructNdaCacheInfo mCacheInfo;
-
-    private RtNetlinkNeighborMessage(StructNlMsgHdr header) {
-        super(header);
-        mNdmsg = null;
-        mDestination = null;
-        mLinkLayerAddr = null;
-        mNumProbes = 0;
-        mCacheInfo = null;
-    }
-
-    public StructNdMsg getNdHeader() {
-        return mNdmsg;
-    }
-
-    public InetAddress getDestination() {
-        return mDestination;
-    }
-
-    public byte[] getLinkLayerAddress() {
-        return mLinkLayerAddr;
-    }
-
-    public int getProbes() {
-        return mNumProbes;
-    }
-
-    public StructNdaCacheInfo getCacheInfo() {
-        return mCacheInfo;
-    }
-
-    public int getRequiredSpace() {
-        int spaceRequired = StructNlMsgHdr.STRUCT_SIZE + StructNdMsg.STRUCT_SIZE;
-        if (mDestination != null) {
-            spaceRequired += NetlinkConstants.alignedLengthOf(
-                    StructNlAttr.NLA_HEADERLEN + mDestination.getAddress().length);
-        }
-        if (mLinkLayerAddr != null) {
-            spaceRequired += NetlinkConstants.alignedLengthOf(
-                    StructNlAttr.NLA_HEADERLEN + mLinkLayerAddr.length);
-        }
-        // Currently we don't write messages with NDA_PROBES nor NDA_CACHEINFO
-        // attributes appended.  Fix later, if necessary.
-        return spaceRequired;
-    }
-
-    private static void packNlAttr(short nlType, byte[] nlValue, ByteBuffer byteBuffer) {
-        final StructNlAttr nlAttr = new StructNlAttr();
-        nlAttr.nla_type = nlType;
-        nlAttr.nla_value = nlValue;
-        nlAttr.nla_len = (short) (StructNlAttr.NLA_HEADERLEN + nlAttr.nla_value.length);
-        nlAttr.pack(byteBuffer);
-    }
-
-    public void pack(ByteBuffer byteBuffer) {
-        getHeader().pack(byteBuffer) ;
-        mNdmsg.pack(byteBuffer);
-
-        if (mDestination != null) {
-            packNlAttr(NDA_DST, mDestination.getAddress(), byteBuffer);
-        }
-        if (mLinkLayerAddr != null) {
-            packNlAttr(NDA_LLADDR, mLinkLayerAddr, byteBuffer);
-        }
-    }
-
-    @Override
-    public String toString() {
-        final String ipLiteral = (mDestination == null) ? "" : mDestination.getHostAddress();
-        return "RtNetlinkNeighborMessage{ "
-                + "nlmsghdr{"
-                + (mHeader == null ? "" : mHeader.toString(OsConstants.NETLINK_ROUTE)) + "}, "
-                + "ndmsg{" + (mNdmsg == null ? "" : mNdmsg.toString()) + "}, "
-                + "destination{" + ipLiteral + "} "
-                + "linklayeraddr{" + NetlinkConstants.hexify(mLinkLayerAddr) + "} "
-                + "probes{" + mNumProbes + "} "
-                + "cacheinfo{" + (mCacheInfo == null ? "" : mCacheInfo.toString()) + "} "
-                + "}";
-    }
-}
diff --git a/common/netlinkclient/src/android/net/netlink/StructInetDiagMsg.java b/common/netlinkclient/src/android/net/netlink/StructInetDiagMsg.java
deleted file mode 100644
index 5772a94..0000000
--- a/common/netlinkclient/src/android/net/netlink/StructInetDiagMsg.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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 android.net.netlink;
-
-import java.nio.ByteBuffer;
-
-/**
- * struct inet_diag_msg
- *
- * see &lt;linux_src&gt;/include/uapi/linux/inet_diag.h
- *
- * struct inet_diag_msg {
- *      __u8    idiag_family;
- *      __u8    idiag_state;
- *      __u8    idiag_timer;
- *      __u8    idiag_retrans;
- *      struct  inet_diag_sockid id;
- *      __u32   idiag_expires;
- *      __u32   idiag_rqueue;
- *      __u32   idiag_wqueue;
- *      __u32   idiag_uid;
- *      __u32   idiag_inode;
- * };
- *
- * @hide
- */
-public class StructInetDiagMsg {
-    public static final int STRUCT_SIZE = 4 + StructInetDiagSockId.STRUCT_SIZE + 20;
-    private static final int IDIAG_UID_OFFSET = StructNlMsgHdr.STRUCT_SIZE + 4 +
-            StructInetDiagSockId.STRUCT_SIZE + 12;
-    public int idiag_uid;
-
-    public static StructInetDiagMsg parse(ByteBuffer byteBuffer) {
-        StructInetDiagMsg struct = new StructInetDiagMsg();
-        struct.idiag_uid = byteBuffer.getInt(IDIAG_UID_OFFSET);
-        return struct;
-    }
-
-    @Override
-    public String toString() {
-        return "StructInetDiagMsg{ "
-                + "idiag_uid{" + idiag_uid + "}, "
-                + "}";
-    }
-}
diff --git a/common/netlinkclient/src/android/net/netlink/StructInetDiagReqV2.java b/common/netlinkclient/src/android/net/netlink/StructInetDiagReqV2.java
deleted file mode 100644
index 520f0ef..0000000
--- a/common/netlinkclient/src/android/net/netlink/StructInetDiagReqV2.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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 android.net.netlink;
-
-import android.annotation.Nullable;
-
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-
-/**
- * struct inet_diag_req_v2
- *
- * see &lt;linux_src&gt;/include/uapi/linux/inet_diag.h
- *
- *      struct inet_diag_req_v2 {
- *          __u8    sdiag_family;
- *          __u8    sdiag_protocol;
- *          __u8    idiag_ext;
- *          __u8    pad;
- *          __u32   idiag_states;
- *          struct  inet_diag_sockid id;
- *      };
- *
- * @hide
- */
-public class StructInetDiagReqV2 {
-    public static final int STRUCT_SIZE = 8 + StructInetDiagSockId.STRUCT_SIZE;
-
-    private final byte mSdiagFamily;
-    private final byte mSdiagProtocol;
-    private final byte mIdiagExt;
-    private final byte mPad;
-    private final StructInetDiagSockId mId;
-    private final int mState;
-    public static final int INET_DIAG_REQ_V2_ALL_STATES = (int) 0xffffffff;
-
-    public StructInetDiagReqV2(int protocol, InetSocketAddress local, InetSocketAddress remote,
-            int family) {
-        this(protocol, local, remote, family, 0 /* pad */, 0 /* extension */,
-                INET_DIAG_REQ_V2_ALL_STATES);
-    }
-
-    public StructInetDiagReqV2(int protocol, @Nullable InetSocketAddress local,
-            @Nullable InetSocketAddress remote, int family, int pad, int extension, int state)
-            throws NullPointerException {
-        mSdiagFamily = (byte) family;
-        mSdiagProtocol = (byte) protocol;
-        // Request for all sockets if no specific socket is requested. Specify the local and remote
-        // socket address information for target request socket.
-        if ((local == null) != (remote == null)) {
-            throw new NullPointerException("Local and remote must be both null or both non-null");
-        }
-        mId = ((local != null && remote != null) ? new StructInetDiagSockId(local, remote) : null);
-        mPad = (byte) pad;
-        mIdiagExt = (byte) extension;
-        mState = state;
-    }
-
-    public void pack(ByteBuffer byteBuffer) {
-        // The ByteOrder must have already been set by the caller.
-        byteBuffer.put((byte) mSdiagFamily);
-        byteBuffer.put((byte) mSdiagProtocol);
-        byteBuffer.put((byte) mIdiagExt);
-        byteBuffer.put((byte) mPad);
-        byteBuffer.putInt(mState);
-        if (mId != null) mId.pack(byteBuffer);
-    }
-
-    @Override
-    public String toString() {
-        final String familyStr = NetlinkConstants.stringForAddressFamily(mSdiagFamily);
-        final String protocolStr = NetlinkConstants.stringForAddressFamily(mSdiagProtocol);
-
-        return "StructInetDiagReqV2{ "
-                + "sdiag_family{" + familyStr + "}, "
-                + "sdiag_protocol{" + protocolStr + "}, "
-                + "idiag_ext{" + mIdiagExt + ")}, "
-                + "pad{" + mPad + "}, "
-                + "idiag_states{" + Integer.toHexString(mState) + "}, "
-                + ((mId != null) ? mId.toString() : "inet_diag_sockid=null")
-                + "}";
-    }
-}
diff --git a/common/netlinkclient/src/android/net/netlink/StructInetDiagSockId.java b/common/netlinkclient/src/android/net/netlink/StructInetDiagSockId.java
deleted file mode 100644
index 2e9fa25..0000000
--- a/common/netlinkclient/src/android/net/netlink/StructInetDiagSockId.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.net.netlink;
-
-import static java.nio.ByteOrder.BIG_ENDIAN;
-
-import java.net.Inet4Address;
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-/**
- * struct inet_diag_req_v2
- *
- * see &lt;linux_src&gt;/include/uapi/linux/inet_diag.h
- *
- * struct inet_diag_sockid {
- *        __be16    idiag_sport;
- *        __be16    idiag_dport;
- *        __be32    idiag_src[4];
- *        __be32    idiag_dst[4];
- *        __u32     idiag_if;
- *        __u32     idiag_cookie[2];
- * #define INET_DIAG_NOCOOKIE (~0U)
- * };
- *
- * @hide
- */
-public class StructInetDiagSockId {
-    public static final int STRUCT_SIZE = 48;
-
-    private final InetSocketAddress mLocSocketAddress;
-    private final InetSocketAddress mRemSocketAddress;
-    private final byte[] INET_DIAG_NOCOOKIE = new byte[]{
-            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
-            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff};
-    private final byte[] IPV4_PADDING = new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-
-    public StructInetDiagSockId(InetSocketAddress loc, InetSocketAddress rem) {
-        mLocSocketAddress = loc;
-        mRemSocketAddress = rem;
-    }
-
-    public void pack(ByteBuffer byteBuffer) {
-        byteBuffer.order(BIG_ENDIAN);
-        byteBuffer.putShort((short) mLocSocketAddress.getPort());
-        byteBuffer.putShort((short) mRemSocketAddress.getPort());
-        byteBuffer.put(mLocSocketAddress.getAddress().getAddress());
-        if (mLocSocketAddress.getAddress() instanceof Inet4Address) {
-            byteBuffer.put(IPV4_PADDING);
-        }
-        byteBuffer.put(mRemSocketAddress.getAddress().getAddress());
-        if (mRemSocketAddress.getAddress() instanceof Inet4Address) {
-            byteBuffer.put(IPV4_PADDING);
-        }
-        byteBuffer.order(ByteOrder.nativeOrder());
-        byteBuffer.putInt(0);
-        byteBuffer.put(INET_DIAG_NOCOOKIE);
-    }
-
-    @Override
-    public String toString() {
-        return "StructInetDiagSockId{ "
-                + "idiag_sport{" + mLocSocketAddress.getPort() + "}, "
-                + "idiag_dport{" + mRemSocketAddress.getPort() + "}, "
-                + "idiag_src{" + mLocSocketAddress.getAddress().getHostAddress() + "}, "
-                + "idiag_dst{" + mRemSocketAddress.getAddress().getHostAddress() + "}, "
-                + "idiag_if{" + 0 + "} "
-                + "idiag_cookie{INET_DIAG_NOCOOKIE}"
-                + "}";
-    }
-}
diff --git a/common/netlinkclient/src/android/net/netlink/StructNdMsg.java b/common/netlinkclient/src/android/net/netlink/StructNdMsg.java
deleted file mode 100644
index 64364df..0000000
--- a/common/netlinkclient/src/android/net/netlink/StructNdMsg.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * 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 android.net.netlink;
-
-import android.system.OsConstants;
-import java.nio.ByteBuffer;
-
-
-/**
- * struct ndmsg
- *
- * see: &lt;linux_src&gt;/include/uapi/linux/neighbour.h
- *
- * @hide
- */
-public class StructNdMsg {
-    // Already aligned.
-    public static final int STRUCT_SIZE = 12;
-
-    // Neighbor Cache Entry States
-    public static final short NUD_NONE        = 0x00;
-    public static final short NUD_INCOMPLETE  = 0x01;
-    public static final short NUD_REACHABLE   = 0x02;
-    public static final short NUD_STALE       = 0x04;
-    public static final short NUD_DELAY       = 0x08;
-    public static final short NUD_PROBE       = 0x10;
-    public static final short NUD_FAILED      = 0x20;
-    public static final short NUD_NOARP       = 0x40;
-    public static final short NUD_PERMANENT   = 0x80;
-
-    public static String stringForNudState(short nudState) {
-        switch (nudState) {
-            case NUD_NONE: return "NUD_NONE";
-            case NUD_INCOMPLETE: return "NUD_INCOMPLETE";
-            case NUD_REACHABLE: return "NUD_REACHABLE";
-            case NUD_STALE: return "NUD_STALE";
-            case NUD_DELAY: return "NUD_DELAY";
-            case NUD_PROBE: return "NUD_PROBE";
-            case NUD_FAILED: return "NUD_FAILED";
-            case NUD_NOARP: return "NUD_NOARP";
-            case NUD_PERMANENT: return "NUD_PERMANENT";
-            default:
-                return "unknown NUD state: " + String.valueOf(nudState);
-        }
-    }
-
-    public static boolean isNudStateConnected(short nudState) {
-        return ((nudState & (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE)) != 0);
-    }
-
-    public static boolean isNudStateValid(short nudState) {
-        return (isNudStateConnected(nudState) ||
-                ((nudState & (NUD_PROBE|NUD_STALE|NUD_DELAY)) != 0));
-    }
-
-    // Neighbor Cache Entry Flags
-    public static byte NTF_USE       = (byte) 0x01;
-    public static byte NTF_SELF      = (byte) 0x02;
-    public static byte NTF_MASTER    = (byte) 0x04;
-    public static byte NTF_PROXY     = (byte) 0x08;
-    public static byte NTF_ROUTER    = (byte) 0x80;
-
-    public static String stringForNudFlags(byte flags) {
-        final StringBuilder sb = new StringBuilder();
-        if ((flags & NTF_USE) != 0) {
-            sb.append("NTF_USE");
-        }
-        if ((flags & NTF_SELF) != 0) {
-            if (sb.length() > 0) { sb.append("|"); }
-            sb.append("NTF_SELF");
-        }
-        if ((flags & NTF_MASTER) != 0) {
-            if (sb.length() > 0) { sb.append("|"); }
-            sb.append("NTF_MASTER");
-        }
-        if ((flags & NTF_PROXY) != 0) {
-            if (sb.length() > 0) { sb.append("|");
-        }
-            sb.append("NTF_PROXY"); }
-        if ((flags & NTF_ROUTER) != 0) {
-            if (sb.length() > 0) { sb.append("|"); }
-            sb.append("NTF_ROUTER");
-        }
-        return sb.toString();
-    }
-
-    private static boolean hasAvailableSpace(ByteBuffer byteBuffer) {
-        return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE;
-    }
-
-    public static StructNdMsg parse(ByteBuffer byteBuffer) {
-        if (!hasAvailableSpace(byteBuffer)) { return null; }
-
-        // The ByteOrder must have already been set by the caller.  In most
-        // cases ByteOrder.nativeOrder() is correct, with the possible
-        // exception of usage within unittests.
-        final StructNdMsg struct = new StructNdMsg();
-        struct.ndm_family = byteBuffer.get();
-        final byte pad1 = byteBuffer.get();
-        final short pad2 = byteBuffer.getShort();
-        struct.ndm_ifindex = byteBuffer.getInt();
-        struct.ndm_state = byteBuffer.getShort();
-        struct.ndm_flags = byteBuffer.get();
-        struct.ndm_type = byteBuffer.get();
-        return struct;
-    }
-
-    public byte ndm_family;
-    public int ndm_ifindex;
-    public short ndm_state;
-    public byte ndm_flags;
-    public byte ndm_type;
-
-    public StructNdMsg() {
-        ndm_family = (byte) OsConstants.AF_UNSPEC;
-    }
-
-    public void pack(ByteBuffer byteBuffer) {
-        // The ByteOrder must have already been set by the caller.  In most
-        // cases ByteOrder.nativeOrder() is correct, with the exception
-        // of usage within unittests.
-        byteBuffer.put(ndm_family);
-        byteBuffer.put((byte) 0);         // pad1
-        byteBuffer.putShort((short) 0);   // pad2
-        byteBuffer.putInt(ndm_ifindex);
-        byteBuffer.putShort(ndm_state);
-        byteBuffer.put(ndm_flags);
-        byteBuffer.put(ndm_type);
-    }
-
-    public boolean nudConnected() {
-        return isNudStateConnected(ndm_state);
-    }
-
-    public boolean nudValid() {
-        return isNudStateValid(ndm_state);
-    }
-
-    @Override
-    public String toString() {
-        final String stateStr = "" + ndm_state + " (" + stringForNudState(ndm_state) + ")";
-        final String flagsStr = "" + ndm_flags + " (" + stringForNudFlags(ndm_flags) + ")";
-        return "StructNdMsg{ "
-                + "family{" + NetlinkConstants.stringForAddressFamily((int) ndm_family) + "}, "
-                + "ifindex{" + ndm_ifindex + "}, "
-                + "state{" + stateStr + "}, "
-                + "flags{" + flagsStr + "}, "
-                + "type{" + ndm_type + "} "
-                + "}";
-    }
-}
diff --git a/common/netlinkclient/src/android/net/netlink/StructNdOptPref64.java b/common/netlinkclient/src/android/net/netlink/StructNdOptPref64.java
deleted file mode 100644
index 607aaba..0000000
--- a/common/netlinkclient/src/android/net/netlink/StructNdOptPref64.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.net.netlink;
-
-import android.net.IpPrefix;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.nio.ByteBuffer;
-import java.util.Objects;
-
-/**
- * The PREF64 router advertisement option. RFC 8781.
- *
- * 0                   1                   2                   3
- * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * |     Type      |    Length     |     Scaled Lifetime     | PLC |
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * |                                                               |
- * +                                                               +
- * |              Highest 96 bits of the Prefix                    |
- * +                                                               +
- * |                                                               |
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- *
- */
-public class StructNdOptPref64 extends NdOption {
-    public static final int STRUCT_SIZE = 16;
-    public static final int TYPE = 38;
-    public static final byte LENGTH = 2;
-
-    private static final String TAG = StructNdOptPref64.class.getSimpleName();
-
-    /**
-     * How many seconds the prefix is expected to remain valid.
-     * Valid values are from 0 to 65528 in multiples of 8.
-     */
-    public final int lifetime;
-    /** The NAT64 prefix. */
-    @NonNull public final IpPrefix prefix;
-
-    static int plcToPrefixLength(int plc) {
-        switch (plc) {
-            case 0: return 96;
-            case 1: return 64;
-            case 2: return 56;
-            case 3: return 48;
-            case 4: return 40;
-            case 5: return 32;
-            default:
-                throw new IllegalArgumentException("Invalid prefix length code " + plc);
-        }
-    }
-
-    static int prefixLengthToPlc(int prefixLength) {
-        switch (prefixLength) {
-            case 96: return 0;
-            case 64: return 1;
-            case 56: return 2;
-            case 48: return 3;
-            case 40: return 4;
-            case 32: return 5;
-            default:
-                throw new IllegalArgumentException("Invalid prefix length " + prefixLength);
-        }
-    }
-
-    /**
-     * Returns the 2-byte "scaled lifetime and prefix length code" field: 13-bit lifetime, 3-bit PLC
-     */
-    static short getScaledLifetimePlc(int lifetime, int prefixLengthCode) {
-        return (short) ((lifetime & 0xfff8) | (prefixLengthCode & 0x7));
-    }
-
-    public StructNdOptPref64(@NonNull IpPrefix prefix, int lifetime) {
-        super((byte) TYPE, LENGTH);
-
-        Objects.requireNonNull(prefix, "prefix must not be null");
-        if (!(prefix.getAddress() instanceof Inet6Address)) {
-            throw new IllegalArgumentException("Must be an IPv6 prefix: " + prefix);
-        }
-        prefixLengthToPlc(prefix.getPrefixLength());  // Throw if the prefix length is invalid.
-        this.prefix = prefix;
-
-        if (lifetime < 0 || lifetime > 0xfff8) {
-            throw new IllegalArgumentException("Invalid lifetime " + lifetime);
-        }
-        this.lifetime = lifetime & 0xfff8;
-    }
-
-    private StructNdOptPref64(@NonNull ByteBuffer buf) {
-        super(buf.get(), Byte.toUnsignedInt(buf.get()));
-        if (type != TYPE) throw new IllegalArgumentException("Invalid type " + type);
-        if (length != LENGTH) throw new IllegalArgumentException("Invalid length " + length);
-
-        int scaledLifetimePlc = Short.toUnsignedInt(buf.getShort());
-        lifetime = scaledLifetimePlc & 0xfff8;
-
-        byte[] addressBytes = new byte[16];
-        buf.get(addressBytes, 0, 12);
-        InetAddress addr;
-        try {
-            addr = InetAddress.getByAddress(addressBytes);
-        } catch (UnknownHostException e) {
-            throw new AssertionError("16-byte array not valid InetAddress?");
-        }
-        prefix = new IpPrefix(addr, plcToPrefixLength(scaledLifetimePlc & 7));
-    }
-
-    /**
-     * Parses an option from a {@link ByteBuffer}.
-     *
-     * @param buf The buffer from which to parse the option. The buffer's byte order must be
-     *            {@link java.nio.ByteOrder#BIG_ENDIAN}.
-     * @return the parsed option, or {@code null} if the option could not be parsed successfully
-     *         (for example, if it was truncated, or if the prefix length code was wrong).
-     */
-    public static StructNdOptPref64 parse(@NonNull ByteBuffer buf) {
-        if (buf == null || buf.remaining() < STRUCT_SIZE) return null;
-        try {
-            return new StructNdOptPref64(buf);
-        } catch (IllegalArgumentException e) {
-            // Not great, but better than throwing an exception that might crash the caller.
-            // Convention in this package is that null indicates that the option was truncated, so
-            // callers must already handle it.
-            Log.d(TAG, "Invalid PREF64 option: " + e);
-            return null;
-        }
-    }
-
-    protected void writeToByteBuffer(ByteBuffer buf) {
-        super.writeToByteBuffer(buf);
-        buf.putShort(getScaledLifetimePlc(lifetime,  prefixLengthToPlc(prefix.getPrefixLength())));
-        buf.put(prefix.getRawAddress(), 0, 12);
-    }
-
-    /** Outputs the wire format of the option to a new big-endian ByteBuffer. */
-    public ByteBuffer toByteBuffer() {
-        ByteBuffer buf = ByteBuffer.allocate(STRUCT_SIZE);
-        writeToByteBuffer(buf);
-        buf.flip();
-        return buf;
-    }
-
-    @Override
-    @NonNull
-    public String toString() {
-        return String.format("NdOptPref64(%s, %d)", prefix, lifetime);
-    }
-}
diff --git a/common/netlinkclient/src/android/net/netlink/StructNdaCacheInfo.java b/common/netlinkclient/src/android/net/netlink/StructNdaCacheInfo.java
deleted file mode 100644
index 16cf563..0000000
--- a/common/netlinkclient/src/android/net/netlink/StructNdaCacheInfo.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2015 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 android.net.netlink;
-
-import android.system.Os;
-import android.system.OsConstants;
-
-import java.nio.ByteBuffer;
-
-
-/**
- * struct nda_cacheinfo
- *
- * see: &lt;linux_src&gt;/include/uapi/linux/neighbour.h
- *
- * @hide
- */
-public class StructNdaCacheInfo {
-    // Already aligned.
-    public static final int STRUCT_SIZE = 16;
-
-    private static boolean hasAvailableSpace(ByteBuffer byteBuffer) {
-        return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE;
-    }
-
-    public static StructNdaCacheInfo parse(ByteBuffer byteBuffer) {
-        if (!hasAvailableSpace(byteBuffer)) { return null; }
-
-        // The ByteOrder must have already been set by the caller.  In most
-        // cases ByteOrder.nativeOrder() is correct, with the possible
-        // exception of usage within unittests.
-        final StructNdaCacheInfo struct = new StructNdaCacheInfo();
-        struct.ndm_used = byteBuffer.getInt();
-        struct.ndm_confirmed = byteBuffer.getInt();
-        struct.ndm_updated = byteBuffer.getInt();
-        struct.ndm_refcnt = byteBuffer.getInt();
-        return struct;
-    }
-
-    // TODO: investigate whether this can change during device runtime and
-    // decide what (if anything) should be done about that.
-    private static final long CLOCK_TICKS_PER_SECOND = Os.sysconf(OsConstants._SC_CLK_TCK);
-
-    private static long ticksToMilliSeconds(int intClockTicks) {
-        final long longClockTicks = (long) intClockTicks & 0xffffffff;
-        return (longClockTicks * 1000) / CLOCK_TICKS_PER_SECOND;
-    }
-
-    /**
-     * Explanatory notes, for reference.
-     *
-     * Before being returned to user space, the neighbor entry times are
-     * converted to clock_t's like so:
-     *
-     *     ndm_used      = jiffies_to_clock_t(now - neigh->used);
-     *     ndm_confirmed = jiffies_to_clock_t(now - neigh->confirmed);
-     *     ndm_updated   = jiffies_to_clock_t(now - neigh->updated);
-     *
-     * meaning that these values are expressed as "clock ticks ago".  To
-     * convert these clock ticks to seconds divide by sysconf(_SC_CLK_TCK).
-     * When _SC_CLK_TCK is 100, for example, the ndm_* times are expressed
-     * in centiseconds.
-     *
-     * These values are unsigned, but fortunately being expressed as "some
-     * clock ticks ago", these values are typically very small (and 
-     * 2^31 centiseconds = 248 days).
-     *
-     * By observation, it appears that:
-     *     ndm_used: the last time ARP/ND took place for this neighbor
-     *     ndm_confirmed: the last time ARP/ND succeeded for this neighbor OR
-     *                    higher layer confirmation (TCP or MSG_CONFIRM)
-     *                    was received
-     *     ndm_updated: the time when the current NUD state was entered
-     */
-    public int ndm_used;
-    public int ndm_confirmed;
-    public int ndm_updated;
-    public int ndm_refcnt;
-
-    public StructNdaCacheInfo() {}
-
-    public long lastUsed() {
-        return ticksToMilliSeconds(ndm_used);
-    }
-
-    public long lastConfirmed() {
-        return ticksToMilliSeconds(ndm_confirmed);
-    }
-
-    public long lastUpdated() {
-        return ticksToMilliSeconds(ndm_updated);
-    }
-
-    @Override
-    public String toString() {
-        return "NdaCacheInfo{ "
-                + "ndm_used{" + lastUsed() + "}, "
-                + "ndm_confirmed{" + lastConfirmed() + "}, "
-                + "ndm_updated{" + lastUpdated() + "}, "
-                + "ndm_refcnt{" + ndm_refcnt + "} "
-                + "}";
-    }
-}
diff --git a/common/netlinkclient/src/android/net/netlink/StructNfGenMsg.java b/common/netlinkclient/src/android/net/netlink/StructNfGenMsg.java
deleted file mode 100644
index 7f247f5..0000000
--- a/common/netlinkclient/src/android/net/netlink/StructNfGenMsg.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.net.netlink;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Objects;
-
-
-/**
- * struct nfgenmsg
- *
- * see &lt;linux_src&gt;/include/uapi/linux/netfilter/nfnetlink.h
- *
- * @hide
- */
-public class StructNfGenMsg {
-    public static final int STRUCT_SIZE = 2 + Short.BYTES;
-
-    public static final int NFNETLINK_V0 = 0;
-
-    final public byte nfgen_family;
-    final public byte version;
-    final public short res_id;  // N.B.: this is big endian in the kernel
-
-    /**
-     * Parses a netfilter netlink header from a {@link ByteBuffer}.
-     *
-     * @param byteBuffer The buffer from which to parse the netfilter netlink header.
-     * @return the parsed netfilter netlink header, or {@code null} if the netfilter netlink header
-     *         could not be parsed successfully (for example, if it was truncated).
-     */
-    @Nullable
-    public static StructNfGenMsg parse(@NonNull ByteBuffer byteBuffer) {
-        Objects.requireNonNull(byteBuffer);
-
-        if (!hasAvailableSpace(byteBuffer)) return null;
-
-        final byte nfgen_family = byteBuffer.get();
-        final byte version = byteBuffer.get();
-
-        final ByteOrder originalOrder = byteBuffer.order();
-        byteBuffer.order(ByteOrder.BIG_ENDIAN);
-        final short res_id = byteBuffer.getShort();
-        byteBuffer.order(originalOrder);
-
-        return new StructNfGenMsg(nfgen_family, version, res_id);
-    }
-
-    public StructNfGenMsg(byte family, byte ver, short id) {
-        nfgen_family = family;
-        version = ver;
-        res_id = id;
-    }
-
-    public StructNfGenMsg(byte family) {
-        nfgen_family = family;
-        version = (byte) NFNETLINK_V0;
-        res_id = (short) 0;
-    }
-
-    public void pack(ByteBuffer byteBuffer) {
-        byteBuffer.put(nfgen_family);
-        byteBuffer.put(version);
-
-        final ByteOrder originalOrder = byteBuffer.order();
-        byteBuffer.order(ByteOrder.BIG_ENDIAN);
-        byteBuffer.putShort(res_id);
-        byteBuffer.order(originalOrder);
-    }
-
-    private static boolean hasAvailableSpace(@NonNull ByteBuffer byteBuffer) {
-        return byteBuffer.remaining() >= STRUCT_SIZE;
-    }
-
-    @Override
-    public String toString() {
-        final String familyStr = NetlinkConstants.stringForAddressFamily(nfgen_family);
-
-        return "NfGenMsg{ "
-                + "nfgen_family{" + familyStr + "}, "
-                + "version{" + Byte.toUnsignedInt(version) + "}, "
-                + "res_id{" + Short.toUnsignedInt(res_id) + "} "
-                + "}";
-    }
-}
diff --git a/common/netlinkclient/src/android/net/netlink/StructNlAttr.java b/common/netlinkclient/src/android/net/netlink/StructNlAttr.java
deleted file mode 100644
index b6e1d3f..0000000
--- a/common/netlinkclient/src/android/net/netlink/StructNlAttr.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * 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 android.net.netlink;
-
-import androidx.annotation.Nullable;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-
-/**
- * struct nlattr
- *
- * see: &lt;linux_src&gt;/include/uapi/linux/netlink.h
- *
- * @hide
- */
-public class StructNlAttr {
-    // Already aligned.
-    public static final int NLA_HEADERLEN  = 4;
-    public static final int NLA_F_NESTED   = (1 << 15);
-
-    public static short makeNestedType(short type) {
-        return (short) (type | NLA_F_NESTED);
-    }
-
-    // Return a (length, type) object only, without consuming any bytes in
-    // |byteBuffer| and without copying or interpreting any value bytes.
-    // This is used for scanning over a packed set of struct nlattr's,
-    // looking for instances of a particular type.
-    public static StructNlAttr peek(ByteBuffer byteBuffer) {
-        if (byteBuffer == null || byteBuffer.remaining() < NLA_HEADERLEN) {
-            return null;
-        }
-        final int baseOffset = byteBuffer.position();
-
-        final StructNlAttr struct = new StructNlAttr();
-        final ByteOrder originalOrder = byteBuffer.order();
-        byteBuffer.order(ByteOrder.nativeOrder());
-        try {
-            struct.nla_len = byteBuffer.getShort();
-            struct.nla_type = byteBuffer.getShort();
-        } finally {
-            byteBuffer.order(originalOrder);
-        }
-
-        byteBuffer.position(baseOffset);
-        if (struct.nla_len < NLA_HEADERLEN) {
-            // Malformed.
-            return null;
-        }
-        return struct;
-    }
-
-    public static StructNlAttr parse(ByteBuffer byteBuffer) {
-        final StructNlAttr struct = peek(byteBuffer);
-        if (struct == null || byteBuffer.remaining() < struct.getAlignedLength()) {
-            return null;
-        }
-
-        final int baseOffset = byteBuffer.position();
-        byteBuffer.position(baseOffset + NLA_HEADERLEN);
-
-        int valueLen = ((int) struct.nla_len) & 0xffff;
-        valueLen -= NLA_HEADERLEN;
-        if (valueLen > 0) {
-            struct.nla_value = new byte[valueLen];
-            byteBuffer.get(struct.nla_value, 0, valueLen);
-            byteBuffer.position(baseOffset + struct.getAlignedLength());
-        }
-        return struct;
-    }
-
-    /**
-     * Find next netlink attribute with a given type from {@link ByteBuffer}.
-     *
-     * @param attrType The given netlink attribute type is requested for.
-     * @param byteBuffer The buffer from which to find the netlink attribute.
-     * @return the found netlink attribute, or {@code null} if the netlink attribute could not be
-     *         found or parsed successfully (for example, if it was truncated).
-     */
-    @Nullable
-    public static StructNlAttr findNextAttrOfType(short attrType,
-            @Nullable ByteBuffer byteBuffer) {
-        while (byteBuffer != null && byteBuffer.remaining() > 0) {
-            final StructNlAttr nlAttr = StructNlAttr.peek(byteBuffer);
-            if (nlAttr == null) {
-                break;
-            }
-            if (nlAttr.nla_type == attrType) {
-                return StructNlAttr.parse(byteBuffer);
-            }
-            if (byteBuffer.remaining() < nlAttr.getAlignedLength()) {
-                break;
-            }
-            byteBuffer.position(byteBuffer.position() + nlAttr.getAlignedLength());
-        }
-        return null;
-    }
-
-    public short nla_len = (short) NLA_HEADERLEN;
-    public short nla_type;
-    public byte[] nla_value;
-
-    public StructNlAttr() {}
-
-    public StructNlAttr(short type, byte value) {
-        nla_type = type;
-        setValue(new byte[1]);
-        nla_value[0] = value;
-    }
-
-    public StructNlAttr(short type, short value) {
-        this(type, value, ByteOrder.nativeOrder());
-    }
-
-    public StructNlAttr(short type, short value, ByteOrder order) {
-        nla_type = type;
-        setValue(new byte[Short.BYTES]);
-        final ByteBuffer buf = getValueAsByteBuffer();
-        final ByteOrder originalOrder = buf.order();
-        try {
-            buf.order(order);
-            buf.putShort(value);
-        } finally {
-            buf.order(originalOrder);
-        }
-    }
-
-    public StructNlAttr(short type, int value) {
-        this(type, value, ByteOrder.nativeOrder());
-    }
-
-    public StructNlAttr(short type, int value, ByteOrder order) {
-        nla_type = type;
-        setValue(new byte[Integer.BYTES]);
-        final ByteBuffer buf = getValueAsByteBuffer();
-        final ByteOrder originalOrder = buf.order();
-        try {
-            buf.order(order);
-            buf.putInt(value);
-        } finally {
-            buf.order(originalOrder);
-        }
-    }
-
-    public StructNlAttr(short type, InetAddress ip) {
-        nla_type = type;
-        setValue(ip.getAddress());
-    }
-
-    public StructNlAttr(short type, StructNlAttr... nested) {
-        this();
-        nla_type = makeNestedType(type);
-
-        int payloadLength = 0;
-        for (StructNlAttr nla : nested) payloadLength += nla.getAlignedLength();
-        setValue(new byte[payloadLength]);
-
-        final ByteBuffer buf = getValueAsByteBuffer();
-        for (StructNlAttr nla : nested) {
-            nla.pack(buf);
-        }
-    }
-
-    public int getAlignedLength() {
-        return NetlinkConstants.alignedLengthOf(nla_len);
-    }
-
-    /**
-     * Get attribute value as BE16.
-     */
-    public short getValueAsBe16(short defaultValue) {
-        final ByteBuffer byteBuffer = getValueAsByteBuffer();
-        if (byteBuffer == null || byteBuffer.remaining() != Short.BYTES) {
-            return defaultValue;
-        }
-        final ByteOrder originalOrder = byteBuffer.order();
-        try {
-            byteBuffer.order(ByteOrder.BIG_ENDIAN);
-            return byteBuffer.getShort();
-        } finally {
-            byteBuffer.order(originalOrder);
-        }
-    }
-
-    public int getValueAsBe32(int defaultValue) {
-        final ByteBuffer byteBuffer = getValueAsByteBuffer();
-        if (byteBuffer == null || byteBuffer.remaining() != Integer.BYTES) {
-            return defaultValue;
-        }
-        final ByteOrder originalOrder = byteBuffer.order();
-        try {
-            byteBuffer.order(ByteOrder.BIG_ENDIAN);
-            return byteBuffer.getInt();
-        } finally {
-            byteBuffer.order(originalOrder);
-        }
-    }
-
-    public ByteBuffer getValueAsByteBuffer() {
-        if (nla_value == null) { return null; }
-        final ByteBuffer byteBuffer = ByteBuffer.wrap(nla_value);
-        // By convention, all buffers in this library are in native byte order because netlink is in
-        // native byte order. It's the order that is used by NetlinkSocket.recvMessage and the only
-        // order accepted by NetlinkMessage.parse.
-        byteBuffer.order(ByteOrder.nativeOrder());
-        return byteBuffer;
-    }
-
-    /**
-     * Get attribute value as byte.
-     */
-    public byte getValueAsByte(byte defaultValue) {
-        final ByteBuffer byteBuffer = getValueAsByteBuffer();
-        if (byteBuffer == null || byteBuffer.remaining() != Byte.BYTES) {
-            return defaultValue;
-        }
-        return getValueAsByteBuffer().get();
-    }
-
-    public int getValueAsInt(int defaultValue) {
-        final ByteBuffer byteBuffer = getValueAsByteBuffer();
-        if (byteBuffer == null || byteBuffer.remaining() != Integer.BYTES) {
-            return defaultValue;
-        }
-        return getValueAsByteBuffer().getInt();
-    }
-
-    public InetAddress getValueAsInetAddress() {
-        if (nla_value == null) { return null; }
-
-        try {
-            return InetAddress.getByAddress(nla_value);
-        } catch (UnknownHostException ignored) {
-            return null;
-        }
-    }
-
-    public void pack(ByteBuffer byteBuffer) {
-        final ByteOrder originalOrder = byteBuffer.order();
-        final int originalPosition = byteBuffer.position();
-
-        byteBuffer.order(ByteOrder.nativeOrder());
-        try {
-            byteBuffer.putShort(nla_len);
-            byteBuffer.putShort(nla_type);
-            if (nla_value != null) byteBuffer.put(nla_value);
-        } finally {
-            byteBuffer.order(originalOrder);
-        }
-        byteBuffer.position(originalPosition + getAlignedLength());
-    }
-
-    private void setValue(byte[] value) {
-        nla_value = value;
-        nla_len = (short) (NLA_HEADERLEN + ((nla_value != null) ? nla_value.length : 0));
-    }
-
-    @Override
-    public String toString() {
-        return "StructNlAttr{ "
-                + "nla_len{" + nla_len + "}, "
-                + "nla_type{" + nla_type + "}, "
-                + "nla_value{" + NetlinkConstants.hexify(nla_value) + "}, "
-                + "}";
-    }
-}
diff --git a/common/netlinkclient/src/android/net/netlink/StructNlMsgErr.java b/common/netlinkclient/src/android/net/netlink/StructNlMsgErr.java
deleted file mode 100644
index 9ea4364..0000000
--- a/common/netlinkclient/src/android/net/netlink/StructNlMsgErr.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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 android.net.netlink;
-
-import java.nio.ByteBuffer;
-
-
-/**
- * struct nlmsgerr
- *
- * see &lt;linux_src&gt;/include/uapi/linux/netlink.h
- *
- * @hide
- */
-public class StructNlMsgErr {
-    public static final int STRUCT_SIZE = Integer.BYTES + StructNlMsgHdr.STRUCT_SIZE;
-
-    public static boolean hasAvailableSpace(ByteBuffer byteBuffer) {
-        return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE;
-    }
-
-    public static StructNlMsgErr parse(ByteBuffer byteBuffer) {
-        if (!hasAvailableSpace(byteBuffer)) { return null; }
-
-        // The ByteOrder must have already been set by the caller.  In most
-        // cases ByteOrder.nativeOrder() is correct, with the exception
-        // of usage within unittests.
-        final StructNlMsgErr struct = new StructNlMsgErr();
-        struct.error = byteBuffer.getInt();
-        struct.msg = StructNlMsgHdr.parse(byteBuffer);
-        return struct;
-    }
-
-    public int error;
-    public StructNlMsgHdr msg;
-
-    public void pack(ByteBuffer byteBuffer) {
-        // The ByteOrder must have already been set by the caller.  In most
-        // cases ByteOrder.nativeOrder() is correct, with the possible
-        // exception of usage within unittests.
-        byteBuffer.putInt(error);
-        if (msg != null) {
-            msg.pack(byteBuffer);
-        }
-    }
-
-    @Override
-    public String toString() {
-        return "StructNlMsgErr{ "
-                + "error{" + error + "}, "
-                + "msg{" + (msg == null ? "" : msg.toString()) + "} "
-                + "}";
-    }
-}
diff --git a/common/netlinkclient/src/android/net/netlink/StructNlMsgHdr.java b/common/netlinkclient/src/android/net/netlink/StructNlMsgHdr.java
deleted file mode 100644
index 55a649d..0000000
--- a/common/netlinkclient/src/android/net/netlink/StructNlMsgHdr.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * 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 android.net.netlink;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.nio.ByteBuffer;
-
-
-/**
- * struct nlmsghdr
- *
- * see &lt;linux_src&gt;/include/uapi/linux/netlink.h
- *
- * @hide
- */
-public class StructNlMsgHdr {
-    // Already aligned.
-    public static final int STRUCT_SIZE = 16;
-
-    public static final short NLM_F_REQUEST = 0x0001;
-    public static final short NLM_F_MULTI   = 0x0002;
-    public static final short NLM_F_ACK     = 0x0004;
-    public static final short NLM_F_ECHO    = 0x0008;
-    // Flags for a GET request.
-    public static final short NLM_F_ROOT    = 0x0100;
-    public static final short NLM_F_MATCH   = 0x0200;
-    public static final short NLM_F_DUMP    = NLM_F_ROOT|NLM_F_MATCH;
-    // Flags for a NEW request.
-    public static final short NLM_F_REPLACE   = 0x100;
-    public static final short NLM_F_EXCL      = 0x200;
-    public static final short NLM_F_CREATE    = 0x400;
-    public static final short NLM_F_APPEND    = 0x800;
-
-    // TODO: Probably need to distinguish the flags which have the same value. For example,
-    // NLM_F_MATCH (0x200) and NLM_F_EXCL (0x200).
-    public static String stringForNlMsgFlags(short flags) {
-        final StringBuilder sb = new StringBuilder();
-        if ((flags & NLM_F_REQUEST) != 0) {
-            sb.append("NLM_F_REQUEST");
-        }
-        if ((flags & NLM_F_MULTI) != 0) {
-            if (sb.length() > 0) { sb.append("|"); }
-            sb.append("NLM_F_MULTI");
-        }
-        if ((flags & NLM_F_ACK) != 0) {
-            if (sb.length() > 0) { sb.append("|"); }
-            sb.append("NLM_F_ACK");
-        }
-        if ((flags & NLM_F_ECHO) != 0) {
-            if (sb.length() > 0) { sb.append("|"); }
-            sb.append("NLM_F_ECHO");
-        }
-        if ((flags & NLM_F_ROOT) != 0) {
-            if (sb.length() > 0) { sb.append("|"); }
-            sb.append("NLM_F_ROOT");
-        }
-        if ((flags & NLM_F_MATCH) != 0) {
-            if (sb.length() > 0) { sb.append("|"); }
-            sb.append("NLM_F_MATCH");
-        }
-        return sb.toString();
-    }
-
-    public static boolean hasAvailableSpace(ByteBuffer byteBuffer) {
-        return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE;
-    }
-
-    public static StructNlMsgHdr parse(ByteBuffer byteBuffer) {
-        if (!hasAvailableSpace(byteBuffer)) { return null; }
-
-        // The ByteOrder must have already been set by the caller.  In most
-        // cases ByteOrder.nativeOrder() is correct, with the exception
-        // of usage within unittests.
-        final StructNlMsgHdr struct = new StructNlMsgHdr();
-        struct.nlmsg_len = byteBuffer.getInt();
-        struct.nlmsg_type = byteBuffer.getShort();
-        struct.nlmsg_flags = byteBuffer.getShort();
-        struct.nlmsg_seq = byteBuffer.getInt();
-        struct.nlmsg_pid = byteBuffer.getInt();
-
-        if (struct.nlmsg_len < STRUCT_SIZE) {
-            // Malformed.
-            return null;
-        }
-        return struct;
-    }
-
-    public int nlmsg_len;
-    public short nlmsg_type;
-    public short nlmsg_flags;
-    public int nlmsg_seq;
-    public int nlmsg_pid;
-
-    public StructNlMsgHdr() {
-        nlmsg_len = 0;
-        nlmsg_type = 0;
-        nlmsg_flags = 0;
-        nlmsg_seq = 0;
-        nlmsg_pid = 0;
-    }
-
-    public void pack(ByteBuffer byteBuffer) {
-        // The ByteOrder must have already been set by the caller.  In most
-        // cases ByteOrder.nativeOrder() is correct, with the possible
-        // exception of usage within unittests.
-        byteBuffer.putInt(nlmsg_len);
-        byteBuffer.putShort(nlmsg_type);
-        byteBuffer.putShort(nlmsg_flags);
-        byteBuffer.putInt(nlmsg_seq);
-        byteBuffer.putInt(nlmsg_pid);
-    }
-
-    @Override
-    public String toString() {
-        return toString(null /* unknown netlink family */);
-    }
-
-    /**
-     * Transform a netlink header into a string. The netlink family is required for transforming
-     * a netlink type integer into a string.
-     * @param nlFamily netlink family. Using Integer will not incur autoboxing penalties because
-     *                 family values are small, and all Integer objects between -128 and 127 are
-     *                 statically cached. See Integer.IntegerCache.
-     * @return A list of header elements.
-     */
-    @NonNull
-    public String toString(@Nullable Integer nlFamily) {
-        final String typeStr = "" + nlmsg_type
-                + "(" + (nlFamily == null
-                ? "" : NetlinkConstants.stringForNlMsgType(nlmsg_type, nlFamily))
-                + ")";
-        final String flagsStr = "" + nlmsg_flags
-                + "(" + stringForNlMsgFlags(nlmsg_flags) + ")";
-        return "StructNlMsgHdr{ "
-                + "nlmsg_len{" + nlmsg_len + "}, "
-                + "nlmsg_type{" + typeStr + "}, "
-                + "nlmsg_flags{" + flagsStr + ")}, "
-                + "nlmsg_seq{" + nlmsg_seq + "}, "
-                + "nlmsg_pid{" + nlmsg_pid + "} "
-                + "}";
-    }
-}
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp
index dc91881..c344d07 100644
--- a/common/networkstackclient/Android.bp
+++ b/common/networkstackclient/Android.bp
@@ -15,6 +15,10 @@
 //
 
 // AIDL interfaces between the core system and the networking mainline module.
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 aidl_interface {
     name: "ipmemorystore-aidl-interfaces",
     local_include_dir: "src",
@@ -68,7 +72,7 @@
         // For framework parcelables.
         "frameworks/base/core/java",
         // For API parcelables in connectivity
-        "frameworks/base/packages/Connectivity/framework/src",
+        "packages/modules/Connectivity/framework/aidl-export",
         "frameworks/native/aidl/binder", // For PersistableBundle.aidl
     ],
     srcs: [
@@ -117,7 +121,7 @@
             enabled: false,
         },
     },
-    imports: ["ipmemorystore-aidl-interfaces"],
+    imports: ["ipmemorystore-aidl-interfaces-V10"],
     versions: [
         "1",
         "2",
@@ -129,6 +133,8 @@
         "8",
         "9",
         "10",
+        "11",
+        "12",
     ],
     // TODO: have tethering depend on networkstack-client and set visibility to private
     visibility: [
@@ -141,25 +147,38 @@
 java_library {
     name: "networkstack-client",
     sdk_version: "system_current",
-    // this is part of updatable modules(NetworkStack) which targets 29(Q)
+    // this is part of updatable modules(NetworkStack) which runs on Q and above
     min_sdk_version: "29",
     srcs: [
         ":framework-annotations",
+        "src/android/net/ip/**/*.java",
+        "src/android/net/IpMemoryStore.java",
         "src/android/net/IpMemoryStoreClient.java",
         "src/android/net/ipmemorystore/**/*.java",
+        "src/android/net/NetworkMonitorManager.java",
         "src/android/net/networkstack/**/*.java",
         "src/android/net/networkstack/aidl/quirks/**/*.java",
         "src/android/net/shared/**/*.java",
+        "src/android/net/util/**/*.java",
+    ],
+    libs: [
+        "net-utils-framework-common", // XXX for IpUtils.java only
     ],
     static_libs: [
-        "ipmemorystore-aidl-interfaces-java",
-        "networkstack-aidl-interfaces-java",
+        "ipmemorystore-aidl-interfaces-V10-java",
+        "networkstack-aidl-interfaces-V12-java",
     ],
     visibility: [
-        "//frameworks/base/packages/Tethering",
+        "//frameworks/base/packages/Connectivity/service",
         "//packages/modules/Connectivity/Tethering",
+        "//packages/modules/Connectivity/service",
         "//frameworks/base/services/net",
         "//frameworks/opt/net/wifi/service",
+        "//packages/apps/Bluetooth",
         "//packages/modules/NetworkStack",
     ],
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.tethering",
+    ],
 }
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/IIpMemoryStore.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/IIpMemoryStore.aidl
index bf7a26d..048e84c 100644
--- a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/IIpMemoryStore.aidl
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/IIpMemoryStore.aidl
@@ -1,14 +1,30 @@
+/*
+ * Copyright (C) 2018 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/IIpMemoryStoreCallbacks.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/IIpMemoryStoreCallbacks.aidl
index 2024391..7dbbc98 100644
--- a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/IIpMemoryStoreCallbacks.aidl
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/IIpMemoryStoreCallbacks.aidl
@@ -1,14 +1,30 @@
+/*
+ * 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/Blob.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/Blob.aidl
index 8a1b57e..4300c83 100644
--- a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/Blob.aidl
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/Blob.aidl
@@ -1,14 +1,30 @@
+/*
+ * Copyright (C) 2018 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
index e711272..3a263e2 100644
--- a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
@@ -1,14 +1,30 @@
+/*
+ * Copyright (C) 2018 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
index 4abecb9..c663ccf 100644
--- a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
@@ -1,14 +1,30 @@
+/*
+ * Copyright (C) 2018 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
index 05c48b3..3740e15 100644
--- a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
@@ -1,14 +1,30 @@
+/*
+ * Copyright (C) 2018 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
index 0bc8c5e..9d87fbb 100644
--- a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
@@ -1,14 +1,30 @@
+/*
+ * Copyright (C) 2018 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnStatusAndCountListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnStatusAndCountListener.aidl
index cf30fa1..1e6a41c 100644
--- a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnStatusAndCountListener.aidl
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnStatusAndCountListener.aidl
@@ -1,14 +1,30 @@
+/*
+ * Copyright (C) 2020 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnStatusListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnStatusListener.aidl
index e71de47..dccdf27 100644
--- a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnStatusListener.aidl
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnStatusListener.aidl
@@ -1,14 +1,30 @@
+/*
+ * Copyright (C) 2018 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
index 2ac7644..227785d 100644
--- a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
@@ -1,14 +1,30 @@
+/*
+ * Copyright (C) 2018 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
index 42a1feb..377a3ec 100644
--- a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
@@ -1,14 +1,30 @@
+/*
+ * Copyright (C) 2018 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/StatusParcelable.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/StatusParcelable.aidl
index 1bea082..59b96cd 100644
--- a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/StatusParcelable.aidl
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/StatusParcelable.aidl
@@ -1,14 +1,30 @@
+/*
+ * Copyright (C) 2018 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/networkstack/aidl/quirks/IPv6ProvisioningLossQuirkParcelable.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/networkstack/aidl/quirks/IPv6ProvisioningLossQuirkParcelable.aidl
index e2ecbb4..c01564b 100644
--- a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/networkstack/aidl/quirks/IPv6ProvisioningLossQuirkParcelable.aidl
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/networkstack/aidl/quirks/IPv6ProvisioningLossQuirkParcelable.aidl
@@ -1,14 +1,30 @@
+/*
+ * Copyright (C) 2020 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/.hash b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/.hash
new file mode 100644
index 0000000..2914d2a
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/.hash
@@ -0,0 +1 @@
+7fecd0a7a6d978705afad88c5e492613cc46e2cb
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/DataStallReportParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/DataStallReportParcelable.aidl
new file mode 100644
index 0000000..771deda
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/DataStallReportParcelable.aidl
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable DataStallReportParcelable {
+  long timestampMillis = 0;
+  int detectionMethod = 1;
+  int tcpPacketFailRate = 2;
+  int tcpMetricsCollectionPeriodMillis = 3;
+  int dnsConsecutiveTimeouts = 4;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/DhcpResultsParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/DhcpResultsParcelable.aidl
new file mode 100644
index 0000000..31f2194
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/DhcpResultsParcelable.aidl
@@ -0,0 +1,44 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable DhcpResultsParcelable {
+  android.net.StaticIpConfiguration baseConfiguration;
+  int leaseDuration;
+  int mtu;
+  String serverAddress;
+  String vendorInfo;
+  @nullable String serverHostName;
+  @nullable String captivePortalApiUrl;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/INetworkMonitor.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/INetworkMonitor.aidl
new file mode 100644
index 0000000..d92196d
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/INetworkMonitor.aidl
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2018, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetworkMonitor {
+  oneway void start();
+  oneway void launchCaptivePortalApp();
+  oneway void notifyCaptivePortalAppFinished(int response);
+  oneway void setAcceptPartialConnectivity();
+  oneway void forceReevaluation(int uid);
+  oneway void notifyPrivateDnsChanged(in android.net.PrivateDnsConfigParcel config);
+  oneway void notifyDnsResponse(int returnCode);
+  oneway void notifyNetworkConnected(in android.net.LinkProperties lp, in android.net.NetworkCapabilities nc);
+  oneway void notifyNetworkDisconnected();
+  oneway void notifyLinkPropertiesChanged(in android.net.LinkProperties lp);
+  oneway void notifyNetworkCapabilitiesChanged(in android.net.NetworkCapabilities nc);
+  const int NETWORK_TEST_RESULT_VALID = 0;
+  const int NETWORK_TEST_RESULT_INVALID = 1;
+  const int NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY = 2;
+  const int NETWORK_VALIDATION_RESULT_VALID = 1;
+  const int NETWORK_VALIDATION_RESULT_PARTIAL = 2;
+  const int NETWORK_VALIDATION_RESULT_SKIPPED = 4;
+  const int NETWORK_VALIDATION_PROBE_DNS = 4;
+  const int NETWORK_VALIDATION_PROBE_HTTP = 8;
+  const int NETWORK_VALIDATION_PROBE_HTTPS = 16;
+  const int NETWORK_VALIDATION_PROBE_FALLBACK = 32;
+  const int NETWORK_VALIDATION_PROBE_PRIVDNS = 64;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/INetworkMonitorCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/INetworkMonitorCallbacks.aidl
new file mode 100644
index 0000000..36eda8e
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/INetworkMonitorCallbacks.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetworkMonitorCallbacks {
+  oneway void onNetworkMonitorCreated(in android.net.INetworkMonitor networkMonitor) = 0;
+  oneway void notifyNetworkTested(int testResult, @nullable String redirectUrl) = 1;
+  oneway void notifyPrivateDnsConfigResolved(in android.net.PrivateDnsConfigParcel config) = 2;
+  oneway void showProvisioningNotification(String action, String packageName) = 3;
+  oneway void hideProvisioningNotification() = 4;
+  oneway void notifyProbeStatusChanged(int probesCompleted, int probesSucceeded) = 5;
+  oneway void notifyNetworkTestedWithExtras(in android.net.NetworkTestResultParcelable result) = 6;
+  oneway void notifyDataStallSuspected(in android.net.DataStallReportParcelable report) = 7;
+  oneway void notifyCaptivePortalDataChanged(in android.net.CaptivePortalData data) = 8;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/INetworkStackConnector.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/INetworkStackConnector.aidl
new file mode 100644
index 0000000..8120ffc
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/INetworkStackConnector.aidl
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2018, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetworkStackConnector {
+  oneway void makeDhcpServer(in String ifName, in android.net.dhcp.DhcpServingParamsParcel params, in android.net.dhcp.IDhcpServerCallbacks cb);
+  oneway void makeNetworkMonitor(in android.net.Network network, String name, in android.net.INetworkMonitorCallbacks cb);
+  oneway void makeIpClient(in String ifName, in android.net.ip.IIpClientCallbacks callbacks);
+  oneway void fetchIpMemoryStore(in android.net.IIpMemoryStoreCallbacks cb);
+  oneway void allowTestUid(int uid, in android.net.INetworkStackStatusCallback cb);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/INetworkStackStatusCallback.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/INetworkStackStatusCallback.aidl
new file mode 100644
index 0000000..0b6b778
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/INetworkStackStatusCallback.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetworkStackStatusCallback {
+  oneway void onStatusAvailable(int statusCode);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/InformationElementParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/InformationElementParcelable.aidl
new file mode 100644
index 0000000..6103774
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/InformationElementParcelable.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable InformationElementParcelable {
+  int id;
+  byte[] payload;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/InitialConfigurationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/InitialConfigurationParcelable.aidl
new file mode 100644
index 0000000..6a597e6
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/InitialConfigurationParcelable.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable InitialConfigurationParcelable {
+  android.net.LinkAddress[] ipAddresses;
+  android.net.IpPrefix[] directlyConnectedRoutes;
+  String[] dnsServers;
+  String gateway;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/Layer2InformationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/Layer2InformationParcelable.aidl
new file mode 100644
index 0000000..83796ee
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/Layer2InformationParcelable.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable Layer2InformationParcelable {
+  String l2Key;
+  String cluster;
+  android.net.MacAddress bssid;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/Layer2PacketParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/Layer2PacketParcelable.aidl
new file mode 100644
index 0000000..4b3fff5
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/Layer2PacketParcelable.aidl
@@ -0,0 +1,39 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable Layer2PacketParcelable {
+  android.net.MacAddress dstMacAddress;
+  byte[] payload;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/NattKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/NattKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..18cf954
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/NattKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable NattKeepalivePacketDataParcelable {
+  byte[] srcAddress;
+  int srcPort;
+  byte[] dstAddress;
+  int dstPort;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/NetworkTestResultParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/NetworkTestResultParcelable.aidl
new file mode 100644
index 0000000..4d6d5a2
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/NetworkTestResultParcelable.aidl
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable NetworkTestResultParcelable {
+  long timestampMillis;
+  int result;
+  int probesSucceeded;
+  int probesAttempted;
+  String redirectUrl;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/PrivateDnsConfigParcel.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/PrivateDnsConfigParcel.aidl
new file mode 100644
index 0000000..1457caf
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/PrivateDnsConfigParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable PrivateDnsConfigParcel {
+  String hostname;
+  String[] ips;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/ProvisioningConfigurationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/ProvisioningConfigurationParcelable.aidl
new file mode 100644
index 0000000..0b7a7a1
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/ProvisioningConfigurationParcelable.aidl
@@ -0,0 +1,54 @@
+/*
+**
+** 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.
+*/
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable ProvisioningConfigurationParcelable {
+  boolean enableIPv4;
+  boolean enableIPv6;
+  boolean usingMultinetworkPolicyTracker;
+  boolean usingIpReachabilityMonitor;
+  int requestedPreDhcpActionMs;
+  android.net.InitialConfigurationParcelable initialConfig;
+  android.net.StaticIpConfiguration staticIpConfig;
+  android.net.apf.ApfCapabilities apfCapabilities;
+  int provisioningTimeoutMs;
+  int ipv6AddrGenMode;
+  android.net.Network network;
+  String displayName;
+  boolean enablePreconnection;
+  @nullable android.net.ScanResultInfoParcelable scanResultInfo;
+  @nullable android.net.Layer2InformationParcelable layer2Info;
+  @nullable List<android.net.networkstack.aidl.dhcp.DhcpOption> options;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/ScanResultInfoParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/ScanResultInfoParcelable.aidl
new file mode 100644
index 0000000..94fc27f
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/ScanResultInfoParcelable.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable ScanResultInfoParcelable {
+  String ssid;
+  String bssid;
+  android.net.InformationElementParcelable[] informationElements;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/TcpKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/TcpKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..0e1c21c
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/TcpKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable TcpKeepalivePacketDataParcelable {
+  byte[] srcAddress;
+  int srcPort;
+  byte[] dstAddress;
+  int dstPort;
+  int seq;
+  int ack;
+  int rcvWnd;
+  int rcvWndScale;
+  int tos;
+  int ttl;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/dhcp/DhcpLeaseParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/dhcp/DhcpLeaseParcelable.aidl
new file mode 100644
index 0000000..3cd8860
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/dhcp/DhcpLeaseParcelable.aidl
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.dhcp;
+@JavaDerive(toString=true)
+parcelable DhcpLeaseParcelable {
+  byte[] clientId;
+  byte[] hwAddr;
+  int netAddr;
+  int prefixLength;
+  long expTime;
+  String hostname;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/dhcp/DhcpServingParamsParcel.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/dhcp/DhcpServingParamsParcel.aidl
new file mode 100644
index 0000000..fa412cb
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/dhcp/DhcpServingParamsParcel.aidl
@@ -0,0 +1,48 @@
+/**
+ *
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.dhcp;
+@JavaDerive(toString=true)
+parcelable DhcpServingParamsParcel {
+  int serverAddr;
+  int serverAddrPrefixLength;
+  int[] defaultRouters;
+  int[] dnsServers;
+  int[] excludedAddrs;
+  long dhcpLeaseTimeSecs;
+  int linkMtu;
+  boolean metered;
+  int singleClientAddr = 0;
+  boolean changePrefixOnDecline = false;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/dhcp/IDhcpEventCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/dhcp/IDhcpEventCallbacks.aidl
new file mode 100644
index 0000000..9312f47
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/dhcp/IDhcpEventCallbacks.aidl
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.dhcp;
+interface IDhcpEventCallbacks {
+  oneway void onLeasesChanged(in List<android.net.dhcp.DhcpLeaseParcelable> newLeases);
+  oneway void onNewPrefixRequest(in android.net.IpPrefix currentPrefix);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/dhcp/IDhcpServer.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/dhcp/IDhcpServer.aidl
new file mode 100644
index 0000000..1109f35
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/dhcp/IDhcpServer.aidl
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2018, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.dhcp;
+/* @hide */
+interface IDhcpServer {
+  oneway void start(in android.net.INetworkStackStatusCallback cb) = 0;
+  oneway void startWithCallbacks(in android.net.INetworkStackStatusCallback statusCb, in android.net.dhcp.IDhcpEventCallbacks eventCb) = 3;
+  oneway void updateParams(in android.net.dhcp.DhcpServingParamsParcel params, in android.net.INetworkStackStatusCallback cb) = 1;
+  oneway void stop(in android.net.INetworkStackStatusCallback cb) = 2;
+  const int STATUS_UNKNOWN = 0;
+  const int STATUS_SUCCESS = 1;
+  const int STATUS_INVALID_ARGUMENT = 2;
+  const int STATUS_UNKNOWN_ERROR = 3;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/dhcp/IDhcpServerCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/dhcp/IDhcpServerCallbacks.aidl
new file mode 100644
index 0000000..ab8577c
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/dhcp/IDhcpServerCallbacks.aidl
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2018, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.dhcp;
+/* @hide */
+interface IDhcpServerCallbacks {
+  oneway void onDhcpServerCreated(int statusCode, in android.net.dhcp.IDhcpServer server);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/ip/IIpClient.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/ip/IIpClient.aidl
new file mode 100644
index 0000000..1fe4c4c
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/ip/IIpClient.aidl
@@ -0,0 +1,52 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.ip;
+/* @hide */
+interface IIpClient {
+  oneway void completedPreDhcpAction();
+  oneway void confirmConfiguration();
+  oneway void readPacketFilterComplete(in byte[] data);
+  oneway void shutdown();
+  oneway void startProvisioning(in android.net.ProvisioningConfigurationParcelable req);
+  oneway void stop();
+  oneway void setTcpBufferSizes(in String tcpBufferSizes);
+  oneway void setHttpProxy(in android.net.ProxyInfo proxyInfo);
+  oneway void setMulticastFilter(boolean enabled);
+  oneway void addKeepalivePacketFilter(int slot, in android.net.TcpKeepalivePacketDataParcelable pkt);
+  oneway void removeKeepalivePacketFilter(int slot);
+  oneway void setL2KeyAndGroupHint(in String l2Key, in String cluster);
+  oneway void addNattKeepalivePacketFilter(int slot, in android.net.NattKeepalivePacketDataParcelable pkt);
+  oneway void notifyPreconnectionComplete(boolean success);
+  oneway void updateLayer2Information(in android.net.Layer2InformationParcelable info);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/ip/IIpClientCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/ip/IIpClientCallbacks.aidl
new file mode 100644
index 0000000..488510d
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/ip/IIpClientCallbacks.aidl
@@ -0,0 +1,51 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.ip;
+/* @hide */
+interface IIpClientCallbacks {
+  oneway void onIpClientCreated(in android.net.ip.IIpClient ipClient);
+  oneway void onPreDhcpAction();
+  oneway void onPostDhcpAction();
+  oneway void onNewDhcpResults(in android.net.DhcpResultsParcelable dhcpResults);
+  oneway void onProvisioningSuccess(in android.net.LinkProperties newLp);
+  oneway void onProvisioningFailure(in android.net.LinkProperties newLp);
+  oneway void onLinkPropertiesChange(in android.net.LinkProperties newLp);
+  oneway void onReachabilityLost(in String logMsg);
+  oneway void onQuit();
+  oneway void installPacketFilter(in byte[] filter);
+  oneway void startReadPacketFilter();
+  oneway void setFallbackMulticastFilter(boolean enabled);
+  oneway void setNeighborDiscoveryOffload(boolean enable);
+  oneway void onPreconnectionStart(in List<android.net.Layer2PacketParcelable> packets);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/networkstack/aidl/dhcp/DhcpOption.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/networkstack/aidl/dhcp/DhcpOption.aidl
new file mode 100644
index 0000000..eea3e0d
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/11/android/net/networkstack/aidl/dhcp/DhcpOption.aidl
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.networkstack.aidl.dhcp;
+@JavaDerive(toString=true)
+parcelable DhcpOption {
+  byte type;
+  @nullable byte[] value;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/.hash b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/.hash
new file mode 100644
index 0000000..e96fe34
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/.hash
@@ -0,0 +1 @@
+ca534b24b8f1e946a36977f391a156016ea7ef4a
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/DataStallReportParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/DataStallReportParcelable.aidl
new file mode 100644
index 0000000..771deda
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/DataStallReportParcelable.aidl
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable DataStallReportParcelable {
+  long timestampMillis = 0;
+  int detectionMethod = 1;
+  int tcpPacketFailRate = 2;
+  int tcpMetricsCollectionPeriodMillis = 3;
+  int dnsConsecutiveTimeouts = 4;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/DhcpResultsParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/DhcpResultsParcelable.aidl
new file mode 100644
index 0000000..31f2194
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/DhcpResultsParcelable.aidl
@@ -0,0 +1,44 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable DhcpResultsParcelable {
+  android.net.StaticIpConfiguration baseConfiguration;
+  int leaseDuration;
+  int mtu;
+  String serverAddress;
+  String vendorInfo;
+  @nullable String serverHostName;
+  @nullable String captivePortalApiUrl;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/INetworkMonitor.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/INetworkMonitor.aidl
new file mode 100644
index 0000000..d92196d
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/INetworkMonitor.aidl
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2018, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetworkMonitor {
+  oneway void start();
+  oneway void launchCaptivePortalApp();
+  oneway void notifyCaptivePortalAppFinished(int response);
+  oneway void setAcceptPartialConnectivity();
+  oneway void forceReevaluation(int uid);
+  oneway void notifyPrivateDnsChanged(in android.net.PrivateDnsConfigParcel config);
+  oneway void notifyDnsResponse(int returnCode);
+  oneway void notifyNetworkConnected(in android.net.LinkProperties lp, in android.net.NetworkCapabilities nc);
+  oneway void notifyNetworkDisconnected();
+  oneway void notifyLinkPropertiesChanged(in android.net.LinkProperties lp);
+  oneway void notifyNetworkCapabilitiesChanged(in android.net.NetworkCapabilities nc);
+  const int NETWORK_TEST_RESULT_VALID = 0;
+  const int NETWORK_TEST_RESULT_INVALID = 1;
+  const int NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY = 2;
+  const int NETWORK_VALIDATION_RESULT_VALID = 1;
+  const int NETWORK_VALIDATION_RESULT_PARTIAL = 2;
+  const int NETWORK_VALIDATION_RESULT_SKIPPED = 4;
+  const int NETWORK_VALIDATION_PROBE_DNS = 4;
+  const int NETWORK_VALIDATION_PROBE_HTTP = 8;
+  const int NETWORK_VALIDATION_PROBE_HTTPS = 16;
+  const int NETWORK_VALIDATION_PROBE_FALLBACK = 32;
+  const int NETWORK_VALIDATION_PROBE_PRIVDNS = 64;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/INetworkMonitorCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/INetworkMonitorCallbacks.aidl
new file mode 100644
index 0000000..36eda8e
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/INetworkMonitorCallbacks.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetworkMonitorCallbacks {
+  oneway void onNetworkMonitorCreated(in android.net.INetworkMonitor networkMonitor) = 0;
+  oneway void notifyNetworkTested(int testResult, @nullable String redirectUrl) = 1;
+  oneway void notifyPrivateDnsConfigResolved(in android.net.PrivateDnsConfigParcel config) = 2;
+  oneway void showProvisioningNotification(String action, String packageName) = 3;
+  oneway void hideProvisioningNotification() = 4;
+  oneway void notifyProbeStatusChanged(int probesCompleted, int probesSucceeded) = 5;
+  oneway void notifyNetworkTestedWithExtras(in android.net.NetworkTestResultParcelable result) = 6;
+  oneway void notifyDataStallSuspected(in android.net.DataStallReportParcelable report) = 7;
+  oneway void notifyCaptivePortalDataChanged(in android.net.CaptivePortalData data) = 8;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/INetworkStackConnector.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/INetworkStackConnector.aidl
new file mode 100644
index 0000000..8120ffc
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/INetworkStackConnector.aidl
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2018, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetworkStackConnector {
+  oneway void makeDhcpServer(in String ifName, in android.net.dhcp.DhcpServingParamsParcel params, in android.net.dhcp.IDhcpServerCallbacks cb);
+  oneway void makeNetworkMonitor(in android.net.Network network, String name, in android.net.INetworkMonitorCallbacks cb);
+  oneway void makeIpClient(in String ifName, in android.net.ip.IIpClientCallbacks callbacks);
+  oneway void fetchIpMemoryStore(in android.net.IIpMemoryStoreCallbacks cb);
+  oneway void allowTestUid(int uid, in android.net.INetworkStackStatusCallback cb);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/INetworkStackStatusCallback.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/INetworkStackStatusCallback.aidl
new file mode 100644
index 0000000..0b6b778
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/INetworkStackStatusCallback.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetworkStackStatusCallback {
+  oneway void onStatusAvailable(int statusCode);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/InformationElementParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/InformationElementParcelable.aidl
new file mode 100644
index 0000000..6103774
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/InformationElementParcelable.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable InformationElementParcelable {
+  int id;
+  byte[] payload;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/InitialConfigurationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/InitialConfigurationParcelable.aidl
new file mode 100644
index 0000000..6a597e6
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/InitialConfigurationParcelable.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable InitialConfigurationParcelable {
+  android.net.LinkAddress[] ipAddresses;
+  android.net.IpPrefix[] directlyConnectedRoutes;
+  String[] dnsServers;
+  String gateway;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/Layer2InformationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/Layer2InformationParcelable.aidl
new file mode 100644
index 0000000..83796ee
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/Layer2InformationParcelable.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable Layer2InformationParcelable {
+  String l2Key;
+  String cluster;
+  android.net.MacAddress bssid;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/Layer2PacketParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/Layer2PacketParcelable.aidl
new file mode 100644
index 0000000..4b3fff5
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/Layer2PacketParcelable.aidl
@@ -0,0 +1,39 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable Layer2PacketParcelable {
+  android.net.MacAddress dstMacAddress;
+  byte[] payload;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/NattKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/NattKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..18cf954
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/NattKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable NattKeepalivePacketDataParcelable {
+  byte[] srcAddress;
+  int srcPort;
+  byte[] dstAddress;
+  int dstPort;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/NetworkTestResultParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/NetworkTestResultParcelable.aidl
new file mode 100644
index 0000000..4d6d5a2
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/NetworkTestResultParcelable.aidl
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable NetworkTestResultParcelable {
+  long timestampMillis;
+  int result;
+  int probesSucceeded;
+  int probesAttempted;
+  String redirectUrl;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/PrivateDnsConfigParcel.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/PrivateDnsConfigParcel.aidl
new file mode 100644
index 0000000..1457caf
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/PrivateDnsConfigParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable PrivateDnsConfigParcel {
+  String hostname;
+  String[] ips;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/ProvisioningConfigurationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/ProvisioningConfigurationParcelable.aidl
new file mode 100644
index 0000000..9ecd110
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/ProvisioningConfigurationParcelable.aidl
@@ -0,0 +1,62 @@
+/*
+**
+** 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.
+*/
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable ProvisioningConfigurationParcelable {
+  /**
+   * @deprecated use ipv4ProvisioningMode instead.
+   */
+  boolean enableIPv4;
+  /**
+   * @deprecated use ipv6ProvisioningMode instead.
+   */
+  boolean enableIPv6;
+  boolean usingMultinetworkPolicyTracker;
+  boolean usingIpReachabilityMonitor;
+  int requestedPreDhcpActionMs;
+  android.net.InitialConfigurationParcelable initialConfig;
+  android.net.StaticIpConfiguration staticIpConfig;
+  android.net.apf.ApfCapabilities apfCapabilities;
+  int provisioningTimeoutMs;
+  int ipv6AddrGenMode;
+  android.net.Network network;
+  String displayName;
+  boolean enablePreconnection;
+  @nullable android.net.ScanResultInfoParcelable scanResultInfo;
+  @nullable android.net.Layer2InformationParcelable layer2Info;
+  @nullable List<android.net.networkstack.aidl.dhcp.DhcpOption> options;
+  int ipv4ProvisioningMode;
+  int ipv6ProvisioningMode;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/ScanResultInfoParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/ScanResultInfoParcelable.aidl
new file mode 100644
index 0000000..94fc27f
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/ScanResultInfoParcelable.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable ScanResultInfoParcelable {
+  String ssid;
+  String bssid;
+  android.net.InformationElementParcelable[] informationElements;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/TcpKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/TcpKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..0e1c21c
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/TcpKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable TcpKeepalivePacketDataParcelable {
+  byte[] srcAddress;
+  int srcPort;
+  byte[] dstAddress;
+  int dstPort;
+  int seq;
+  int ack;
+  int rcvWnd;
+  int rcvWndScale;
+  int tos;
+  int ttl;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/dhcp/DhcpLeaseParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/dhcp/DhcpLeaseParcelable.aidl
new file mode 100644
index 0000000..3cd8860
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/dhcp/DhcpLeaseParcelable.aidl
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.dhcp;
+@JavaDerive(toString=true)
+parcelable DhcpLeaseParcelable {
+  byte[] clientId;
+  byte[] hwAddr;
+  int netAddr;
+  int prefixLength;
+  long expTime;
+  String hostname;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/dhcp/DhcpServingParamsParcel.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/dhcp/DhcpServingParamsParcel.aidl
new file mode 100644
index 0000000..fa412cb
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/dhcp/DhcpServingParamsParcel.aidl
@@ -0,0 +1,48 @@
+/**
+ *
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.dhcp;
+@JavaDerive(toString=true)
+parcelable DhcpServingParamsParcel {
+  int serverAddr;
+  int serverAddrPrefixLength;
+  int[] defaultRouters;
+  int[] dnsServers;
+  int[] excludedAddrs;
+  long dhcpLeaseTimeSecs;
+  int linkMtu;
+  boolean metered;
+  int singleClientAddr = 0;
+  boolean changePrefixOnDecline = false;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/dhcp/IDhcpEventCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/dhcp/IDhcpEventCallbacks.aidl
new file mode 100644
index 0000000..9312f47
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/dhcp/IDhcpEventCallbacks.aidl
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.dhcp;
+interface IDhcpEventCallbacks {
+  oneway void onLeasesChanged(in List<android.net.dhcp.DhcpLeaseParcelable> newLeases);
+  oneway void onNewPrefixRequest(in android.net.IpPrefix currentPrefix);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/dhcp/IDhcpServer.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/dhcp/IDhcpServer.aidl
new file mode 100644
index 0000000..1109f35
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/dhcp/IDhcpServer.aidl
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2018, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.dhcp;
+/* @hide */
+interface IDhcpServer {
+  oneway void start(in android.net.INetworkStackStatusCallback cb) = 0;
+  oneway void startWithCallbacks(in android.net.INetworkStackStatusCallback statusCb, in android.net.dhcp.IDhcpEventCallbacks eventCb) = 3;
+  oneway void updateParams(in android.net.dhcp.DhcpServingParamsParcel params, in android.net.INetworkStackStatusCallback cb) = 1;
+  oneway void stop(in android.net.INetworkStackStatusCallback cb) = 2;
+  const int STATUS_UNKNOWN = 0;
+  const int STATUS_SUCCESS = 1;
+  const int STATUS_INVALID_ARGUMENT = 2;
+  const int STATUS_UNKNOWN_ERROR = 3;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/dhcp/IDhcpServerCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/dhcp/IDhcpServerCallbacks.aidl
new file mode 100644
index 0000000..ab8577c
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/dhcp/IDhcpServerCallbacks.aidl
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2018, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.dhcp;
+/* @hide */
+interface IDhcpServerCallbacks {
+  oneway void onDhcpServerCreated(int statusCode, in android.net.dhcp.IDhcpServer server);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/ip/IIpClient.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/ip/IIpClient.aidl
new file mode 100644
index 0000000..a97511e
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/ip/IIpClient.aidl
@@ -0,0 +1,58 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.ip;
+/* @hide */
+interface IIpClient {
+  oneway void completedPreDhcpAction();
+  oneway void confirmConfiguration();
+  oneway void readPacketFilterComplete(in byte[] data);
+  oneway void shutdown();
+  oneway void startProvisioning(in android.net.ProvisioningConfigurationParcelable req);
+  oneway void stop();
+  oneway void setTcpBufferSizes(in String tcpBufferSizes);
+  oneway void setHttpProxy(in android.net.ProxyInfo proxyInfo);
+  oneway void setMulticastFilter(boolean enabled);
+  oneway void addKeepalivePacketFilter(int slot, in android.net.TcpKeepalivePacketDataParcelable pkt);
+  oneway void removeKeepalivePacketFilter(int slot);
+  oneway void setL2KeyAndGroupHint(in String l2Key, in String cluster);
+  oneway void addNattKeepalivePacketFilter(int slot, in android.net.NattKeepalivePacketDataParcelable pkt);
+  oneway void notifyPreconnectionComplete(boolean success);
+  oneway void updateLayer2Information(in android.net.Layer2InformationParcelable info);
+  const int PROV_IPV4_DISABLED = 0;
+  const int PROV_IPV4_STATIC = 1;
+  const int PROV_IPV4_DHCP = 2;
+  const int PROV_IPV6_DISABLED = 0;
+  const int PROV_IPV6_SLAAC = 1;
+  const int PROV_IPV6_LINKLOCAL = 2;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/ip/IIpClientCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/ip/IIpClientCallbacks.aidl
new file mode 100644
index 0000000..488510d
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/ip/IIpClientCallbacks.aidl
@@ -0,0 +1,51 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.ip;
+/* @hide */
+interface IIpClientCallbacks {
+  oneway void onIpClientCreated(in android.net.ip.IIpClient ipClient);
+  oneway void onPreDhcpAction();
+  oneway void onPostDhcpAction();
+  oneway void onNewDhcpResults(in android.net.DhcpResultsParcelable dhcpResults);
+  oneway void onProvisioningSuccess(in android.net.LinkProperties newLp);
+  oneway void onProvisioningFailure(in android.net.LinkProperties newLp);
+  oneway void onLinkPropertiesChange(in android.net.LinkProperties newLp);
+  oneway void onReachabilityLost(in String logMsg);
+  oneway void onQuit();
+  oneway void installPacketFilter(in byte[] filter);
+  oneway void startReadPacketFilter();
+  oneway void setFallbackMulticastFilter(boolean enabled);
+  oneway void setNeighborDiscoveryOffload(boolean enable);
+  oneway void onPreconnectionStart(in List<android.net.Layer2PacketParcelable> packets);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/networkstack/aidl/dhcp/DhcpOption.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/networkstack/aidl/dhcp/DhcpOption.aidl
new file mode 100644
index 0000000..eea3e0d
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/12/android/net/networkstack/aidl/dhcp/DhcpOption.aidl
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.networkstack.aidl.dhcp;
+@JavaDerive(toString=true)
+parcelable DhcpOption {
+  byte type;
+  @nullable byte[] value;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/DataStallReportParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/DataStallReportParcelable.aidl
index 0f860a5..771deda 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/DataStallReportParcelable.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/DataStallReportParcelable.aidl
@@ -1,3 +1,18 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/DhcpResultsParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/DhcpResultsParcelable.aidl
index 4445be7..31f2194 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/DhcpResultsParcelable.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/DhcpResultsParcelable.aidl
@@ -1,3 +1,18 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkMonitor.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkMonitor.aidl
index db9145f..d92196d 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkMonitor.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkMonitor.aidl
@@ -1,3 +1,18 @@
+/**
+ * Copyright (c) 2018, 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 perNmissions and
+ * limitations under the License.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
@@ -35,6 +50,7 @@
   const int NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY = 2;
   const int NETWORK_VALIDATION_RESULT_VALID = 1;
   const int NETWORK_VALIDATION_RESULT_PARTIAL = 2;
+  const int NETWORK_VALIDATION_RESULT_SKIPPED = 4;
   const int NETWORK_VALIDATION_PROBE_DNS = 4;
   const int NETWORK_VALIDATION_PROBE_HTTP = 8;
   const int NETWORK_VALIDATION_PROBE_HTTPS = 16;
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkMonitorCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkMonitorCallbacks.aidl
index b2685ad..36eda8e 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkMonitorCallbacks.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkMonitorCallbacks.aidl
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2018 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkStackConnector.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkStackConnector.aidl
index 396b42a..8120ffc 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkStackConnector.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkStackConnector.aidl
@@ -1,3 +1,18 @@
+/**
+ * Copyright (c) 2018, 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 perNmissions and
+ * limitations under the License.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkStackStatusCallback.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkStackStatusCallback.aidl
index 97c9970..0b6b778 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkStackStatusCallback.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkStackStatusCallback.aidl
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2018 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/InformationElementParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/InformationElementParcelable.aidl
index 77fca83..6103774 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/InformationElementParcelable.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/InformationElementParcelable.aidl
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2020 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/InitialConfigurationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/InitialConfigurationParcelable.aidl
index 6137305..6a597e6 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/InitialConfigurationParcelable.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/InitialConfigurationParcelable.aidl
@@ -1,3 +1,18 @@
+/*
+ * 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/Layer2InformationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/Layer2InformationParcelable.aidl
index d3adbb3..83796ee 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/Layer2InformationParcelable.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/Layer2InformationParcelable.aidl
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2020 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/Layer2PacketParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/Layer2PacketParcelable.aidl
index b45f6da..4b3fff5 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/Layer2PacketParcelable.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/Layer2PacketParcelable.aidl
@@ -1,3 +1,18 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/NattKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/NattKeepalivePacketDataParcelable.aidl
index 7634ac9..18cf954 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/NattKeepalivePacketDataParcelable.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/NattKeepalivePacketDataParcelable.aidl
@@ -1,3 +1,18 @@
+/*
+ * 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/NetworkTestResultParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/NetworkTestResultParcelable.aidl
index 1d0bbbe..4d6d5a2 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/NetworkTestResultParcelable.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/NetworkTestResultParcelable.aidl
@@ -1,3 +1,18 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/PrivateDnsConfigParcel.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/PrivateDnsConfigParcel.aidl
index c6d6361..1457caf 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/PrivateDnsConfigParcel.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/PrivateDnsConfigParcel.aidl
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2018 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ProvisioningConfigurationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ProvisioningConfigurationParcelable.aidl
index 171817c..9ecd110 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ProvisioningConfigurationParcelable.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ProvisioningConfigurationParcelable.aidl
@@ -1,3 +1,19 @@
+/*
+**
+** 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.
+*/
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
@@ -19,7 +35,13 @@
 package android.net;
 @JavaDerive(toString=true)
 parcelable ProvisioningConfigurationParcelable {
+  /**
+   * @deprecated use ipv4ProvisioningMode instead.
+   */
   boolean enableIPv4;
+  /**
+   * @deprecated use ipv6ProvisioningMode instead.
+   */
   boolean enableIPv6;
   boolean usingMultinetworkPolicyTracker;
   boolean usingIpReachabilityMonitor;
@@ -35,4 +57,6 @@
   @nullable android.net.ScanResultInfoParcelable scanResultInfo;
   @nullable android.net.Layer2InformationParcelable layer2Info;
   @nullable List<android.net.networkstack.aidl.dhcp.DhcpOption> options;
+  int ipv4ProvisioningMode;
+  int ipv6ProvisioningMode;
 }
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ScanResultInfoParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ScanResultInfoParcelable.aidl
index 4646ede..94fc27f 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ScanResultInfoParcelable.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ScanResultInfoParcelable.aidl
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2020 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/TcpKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/TcpKeepalivePacketDataParcelable.aidl
index 00f15da..0e1c21c 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/TcpKeepalivePacketDataParcelable.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/TcpKeepalivePacketDataParcelable.aidl
@@ -1,3 +1,18 @@
+/*
+ * 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/dhcp/DhcpLeaseParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/dhcp/DhcpLeaseParcelable.aidl
index b0a0f0f..3cd8860 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/dhcp/DhcpLeaseParcelable.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/dhcp/DhcpLeaseParcelable.aidl
@@ -1,3 +1,18 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/dhcp/DhcpServingParamsParcel.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/dhcp/DhcpServingParamsParcel.aidl
index d56ef8e..fa412cb 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/dhcp/DhcpServingParamsParcel.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/dhcp/DhcpServingParamsParcel.aidl
@@ -1,3 +1,19 @@
+/**
+ *
+ * Copyright (C) 2018 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.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/dhcp/IDhcpEventCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/dhcp/IDhcpEventCallbacks.aidl
index 8f3288e..9312f47 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/dhcp/IDhcpEventCallbacks.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/dhcp/IDhcpEventCallbacks.aidl
@@ -1,3 +1,18 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/dhcp/IDhcpServer.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/dhcp/IDhcpServer.aidl
index 83cebdf..1109f35 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/dhcp/IDhcpServer.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/dhcp/IDhcpServer.aidl
@@ -1,3 +1,18 @@
+/**
+ * Copyright (c) 2018, 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 perNmissions and
+ * limitations under the License.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/dhcp/IDhcpServerCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/dhcp/IDhcpServerCallbacks.aidl
index 35da06c..ab8577c 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/dhcp/IDhcpServerCallbacks.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/dhcp/IDhcpServerCallbacks.aidl
@@ -1,3 +1,18 @@
+/**
+ * Copyright (c) 2018, 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 perNmissions and
+ * limitations under the License.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ip/IIpClient.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ip/IIpClient.aidl
index 5607b2a..a97511e 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ip/IIpClient.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ip/IIpClient.aidl
@@ -1,3 +1,18 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
@@ -34,4 +49,10 @@
   oneway void addNattKeepalivePacketFilter(int slot, in android.net.NattKeepalivePacketDataParcelable pkt);
   oneway void notifyPreconnectionComplete(boolean success);
   oneway void updateLayer2Information(in android.net.Layer2InformationParcelable info);
+  const int PROV_IPV4_DISABLED = 0;
+  const int PROV_IPV4_STATIC = 1;
+  const int PROV_IPV4_DHCP = 2;
+  const int PROV_IPV6_DISABLED = 0;
+  const int PROV_IPV6_SLAAC = 1;
+  const int PROV_IPV6_LINKLOCAL = 2;
 }
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ip/IIpClientCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ip/IIpClientCallbacks.aidl
index 9a84784..488510d 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ip/IIpClientCallbacks.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ip/IIpClientCallbacks.aidl
@@ -1,3 +1,18 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/networkstack/aidl/dhcp/DhcpOption.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/networkstack/aidl/dhcp/DhcpOption.aidl
index c97212b..eea3e0d 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/networkstack/aidl/dhcp/DhcpOption.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/networkstack/aidl/dhcp/DhcpOption.aidl
@@ -1,3 +1,18 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
 ///////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/common/networkstackclient/src/android/net/INetworkMonitor.aidl b/common/networkstackclient/src/android/net/INetworkMonitor.aidl
index 3fc81a3..b124734 100644
--- a/common/networkstackclient/src/android/net/INetworkMonitor.aidl
+++ b/common/networkstackclient/src/android/net/INetworkMonitor.aidl
@@ -44,10 +44,16 @@
     // are set, then it's equal to NETWORK_TEST_RESULT_INVALID. If NETWORK_VALIDATION_RESULT_VALID
     // is set, then the network validates and equal to NETWORK_TEST_RESULT_VALID. If
     // NETWORK_VALIDATION_RESULT_PARTIAL is set, then the network has partial connectivity which
-    // is equal to NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY. NETWORK_VALIDATION_PROBE_* is set
-    // when the specific probe result of the network is resolved.
+    // is equal to NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY. Networks receiving validation that both
+    // do not require validation and are not validated will have NETWORK_VALIDATION_RESULT_SKIPPED
+    // set. NETWORK_VALIDATION_PROBE_* is set when the specific probe result of the network is
+    // resolved.
     const int NETWORK_VALIDATION_RESULT_VALID = 0x01;
     const int NETWORK_VALIDATION_RESULT_PARTIAL = 0x02;
+    const int NETWORK_VALIDATION_RESULT_SKIPPED = 0x04;
+
+    // NETWORK_VALIDATION_RESULT_* and NETWORK_VALIDATION_PROBE_* are independent values sent in
+    // different ints.
     const int NETWORK_VALIDATION_PROBE_DNS = 0x04;
     const int NETWORK_VALIDATION_PROBE_HTTP = 0x08;
     const int NETWORK_VALIDATION_PROBE_HTTPS = 0x10;
diff --git a/common/networkstackclient/src/android/net/IpMemoryStore.java b/common/networkstackclient/src/android/net/IpMemoryStore.java
new file mode 100644
index 0000000..f2c1d35
--- /dev/null
+++ b/common/networkstackclient/src/android/net/IpMemoryStore.java
@@ -0,0 +1,98 @@
+/*
+ * 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 android.net;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.net.networkstack.ModuleNetworkStackClient;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+/**
+ * Manager class used to communicate with the ip memory store service in the network stack,
+ * which is running in a separate module.
+ * @hide
+*/
+public class IpMemoryStore extends IpMemoryStoreClient {
+    private static final String TAG = IpMemoryStore.class.getSimpleName();
+    @NonNull private final CompletableFuture<IIpMemoryStore> mService;
+    @NonNull private final AtomicReference<CompletableFuture<IIpMemoryStore>> mTailNode;
+
+    public IpMemoryStore(@NonNull final Context context) {
+        super(context);
+        mService = new CompletableFuture<>();
+        mTailNode = new AtomicReference<CompletableFuture<IIpMemoryStore>>(mService);
+        getModuleNetworkStackClient(context).fetchIpMemoryStore(
+                new IIpMemoryStoreCallbacks.Stub() {
+                    @Override
+                    public void onIpMemoryStoreFetched(@NonNull final IIpMemoryStore memoryStore) {
+                        mService.complete(memoryStore);
+                    }
+
+                    @Override
+                    public int getInterfaceVersion() {
+                        return this.VERSION;
+                    }
+
+                    @Override
+                    public String getInterfaceHash() {
+                        return this.HASH;
+                    }
+                });
+    }
+
+    /*
+     *  If the IpMemoryStore is ready, this function will run the request synchronously.
+     *  Otherwise, it will enqueue the requests for execution immediately after the
+     *  service becomes ready. The requests are guaranteed to be executed in the order
+     *  they are sumbitted.
+     */
+    @Override
+    protected void runWhenServiceReady(Consumer<IIpMemoryStore> cb) throws ExecutionException {
+        mTailNode.getAndUpdate(future -> future.handle((store, exception) -> {
+            if (exception != null) {
+                // this should never happens since we also catch the exception below
+                Log.wtf(TAG, "Error fetching IpMemoryStore", exception);
+                return store;
+            }
+
+            try {
+                cb.accept(store);
+            } catch (Exception e) {
+                Log.wtf(TAG, "Exception occurred: " + e.getMessage());
+            }
+            return store;
+        }));
+    }
+
+    @VisibleForTesting
+    protected ModuleNetworkStackClient getModuleNetworkStackClient(Context context) {
+        return ModuleNetworkStackClient.getInstance(context);
+    }
+
+    /** Gets an instance of the memory store */
+    @NonNull
+    public static IpMemoryStore getMemoryStore(final Context context) {
+        return new IpMemoryStore(context);
+    }
+}
diff --git a/common/networkstackclient/src/android/net/NetworkMonitorManager.java b/common/networkstackclient/src/android/net/NetworkMonitorManager.java
new file mode 100644
index 0000000..0f66981
--- /dev/null
+++ b/common/networkstackclient/src/android/net/NetworkMonitorManager.java
@@ -0,0 +1,203 @@
+/*
+ * 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 android.net;
+
+import android.annotation.Hide;
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A convenience wrapper for INetworkMonitor.
+ *
+ * Wraps INetworkMonitor calls, making them a bit more friendly to use. Currently handles:
+ * - Clearing calling identity
+ * - Ignoring RemoteExceptions
+ * - Converting to stable parcelables
+ *
+ * By design, all methods on INetworkMonitor are asynchronous oneway IPCs and are thus void. All the
+ * wrapper methods in this class return a boolean that callers can use to determine whether
+ * RemoteException was thrown.
+ */
+@Hide
+public class NetworkMonitorManager {
+
+    @NonNull private final INetworkMonitor mNetworkMonitor;
+    @NonNull private final String mTag;
+
+    public NetworkMonitorManager(@NonNull INetworkMonitor networkMonitorManager,
+            @NonNull String tag) {
+        mNetworkMonitor = networkMonitorManager;
+        mTag = tag;
+    }
+
+    public NetworkMonitorManager(@NonNull INetworkMonitor networkMonitorManager) {
+        this(networkMonitorManager, NetworkMonitorManager.class.getSimpleName());
+    }
+
+    private void log(String s, Throwable e) {
+        Log.e(mTag, s, e);
+    }
+
+    // CHECKSTYLE:OFF Generated code
+
+    public boolean start() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.start();
+            return true;
+        } catch (RemoteException e) {
+            log("Error in start", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean launchCaptivePortalApp() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.launchCaptivePortalApp();
+            return true;
+        } catch (RemoteException e) {
+            log("Error in launchCaptivePortalApp", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyCaptivePortalAppFinished(int response) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyCaptivePortalAppFinished(response);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyCaptivePortalAppFinished", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean setAcceptPartialConnectivity() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.setAcceptPartialConnectivity();
+            return true;
+        } catch (RemoteException e) {
+            log("Error in setAcceptPartialConnectivity", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean forceReevaluation(int uid) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.forceReevaluation(uid);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in forceReevaluation", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyPrivateDnsChanged(PrivateDnsConfigParcel config) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyPrivateDnsChanged(config);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyPrivateDnsChanged", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyDnsResponse(int returnCode) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyDnsResponse(returnCode);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyDnsResponse", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyNetworkConnected(LinkProperties lp, NetworkCapabilities nc) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyNetworkConnected(lp, nc);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyNetworkConnected", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyNetworkDisconnected() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyNetworkDisconnected();
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyNetworkDisconnected", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyLinkPropertiesChanged(LinkProperties lp) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyLinkPropertiesChanged(lp);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyLinkPropertiesChanged", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyNetworkCapabilitiesChanged(NetworkCapabilities nc) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyNetworkCapabilitiesChanged(nc);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyNetworkCapabilitiesChanged", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // CHECKSTYLE:ON Generated code
+}
diff --git a/common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl b/common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl
index 0aeebcb..54a5729 100644
--- a/common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl
+++ b/common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl
@@ -29,7 +29,9 @@
 
 @JavaDerive(toString=true)
 parcelable ProvisioningConfigurationParcelable {
+    /** @deprecated use ipv4ProvisioningMode instead. */
     boolean enableIPv4;
+    /** @deprecated use ipv6ProvisioningMode instead. */
     boolean enableIPv6;
     boolean usingMultinetworkPolicyTracker;
     boolean usingIpReachabilityMonitor;
@@ -45,4 +47,6 @@
     @nullable ScanResultInfoParcelable scanResultInfo;
     @nullable Layer2InformationParcelable layer2Info;
     @nullable List<DhcpOption> options;
+    int ipv4ProvisioningMode;
+    int ipv6ProvisioningMode;
 }
diff --git a/common/networkstackclient/src/android/net/ip/IIpClient.aidl b/common/networkstackclient/src/android/net/ip/IIpClient.aidl
index 029bdb3..f76a230 100644
--- a/common/networkstackclient/src/android/net/ip/IIpClient.aidl
+++ b/common/networkstackclient/src/android/net/ip/IIpClient.aidl
@@ -23,6 +23,36 @@
 
 /** @hide */
 oneway interface IIpClient {
+    /**
+     * Disable IPv4 provisioning.
+     */
+    const int PROV_IPV4_DISABLED = 0x00;
+
+    /**
+     * Enable IPv4 provisioning using static IP addresses.
+     */
+    const int PROV_IPV4_STATIC = 0x01;
+
+    /**
+     * Enable IPv4 provisioning using DHCP.
+     */
+    const int PROV_IPV4_DHCP = 0x02;
+
+    /**
+     * Disable IPv6 provisioning.
+     */
+    const int PROV_IPV6_DISABLED = 0x00;
+
+    /**
+     * Enable IPv6 provisioning via SLAAC.
+     */
+    const int PROV_IPV6_SLAAC = 0x01;
+
+    /**
+     * Enable IPv6 Link-local only.
+     */
+    const int PROV_IPV6_LINKLOCAL = 0x02;
+
     void completedPreDhcpAction();
     void confirmConfiguration();
     void readPacketFilterComplete(in byte[] data);
diff --git a/common/networkstackclient/src/android/net/ip/IpClientCallbacks.java b/common/networkstackclient/src/android/net/ip/IpClientCallbacks.java
new file mode 100644
index 0000000..b17fcaa
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ip/IpClientCallbacks.java
@@ -0,0 +1,136 @@
+/*
+ * 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 android.net.ip;
+
+import android.net.DhcpResultsParcelable;
+import android.net.Layer2PacketParcelable;
+import android.net.LinkProperties;
+
+import java.util.List;
+
+/**
+ * Callbacks for handling IpClient events.
+ *
+ * This is a convenience class to allow clients not to override all methods of IIpClientCallbacks,
+ * and avoid unparceling arguments.
+ * These methods are called asynchronously on a Binder thread, as IpClient lives in a different
+ * process.
+ * @hide
+ */
+public class IpClientCallbacks {
+
+    /**
+     * Callback called upon IpClient creation.
+     *
+     * @param ipClient The Binder token to communicate with IpClient.
+     */
+    public void onIpClientCreated(IIpClient ipClient) {}
+
+    /**
+     * Callback called prior to DHCP discovery/renewal.
+     *
+     * <p>In order to receive onPreDhcpAction(), call #withPreDhcpAction() when constructing a
+     * ProvisioningConfiguration.
+     *
+     * <p>Implementations of onPreDhcpAction() must call IpClient#completedPreDhcpAction() to
+     * indicate that DHCP is clear to proceed.
+      */
+    public void onPreDhcpAction() {}
+
+    /**
+     * Callback called after DHCP discovery/renewal.
+     */
+    public void onPostDhcpAction() {}
+
+    /**
+     * Callback called when new DHCP results are available.
+     *
+     * <p>This is purely advisory and not an indication of provisioning success or failure.  This is
+     * only here for callers that want to expose DHCPv4 results to other APIs
+     * (e.g., WifiInfo#setInetAddress).
+     *
+     * <p>DHCPv4 or static IPv4 configuration failure or success can be determined by whether or not
+     * the passed-in DhcpResults object is null.
+     */
+    public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {
+        // In general callbacks would not use a parcelable directly (DhcpResultsParcelable), and
+        // would use a wrapper instead, because of the lack of safety of stable parcelables. But
+        // there are already two classes in the tree for DHCP information: DhcpInfo and DhcpResults,
+        // and neither of them exposes an appropriate API (they are bags of mutable fields and can't
+        // be changed because they are public API and @UnsupportedAppUsage, being no better than the
+        // stable parcelable). Adding a third class would cost more than the gain considering that
+        // the only client of this callback is WiFi, which will end up converting the results to
+        // DhcpInfo anyway.
+    }
+
+    /**
+     * Indicates that provisioning was successful.
+     */
+    public void onProvisioningSuccess(LinkProperties newLp) {}
+
+    /**
+     * Indicates that provisioning failed.
+     */
+    public void onProvisioningFailure(LinkProperties newLp) {}
+
+    /**
+     * Invoked on LinkProperties changes.
+     */
+    public void onLinkPropertiesChange(LinkProperties newLp) {}
+
+    /**Called when the internal IpReachabilityMonitor (if enabled) has
+     * detected the loss of a critical number of required neighbors.
+     */
+    public void onReachabilityLost(String logMsg) {}
+
+    /**
+     * Called when the IpClient state machine terminates.
+     */
+    public void onQuit() {}
+
+    /**
+     * Called to indicate that a new APF program must be installed to filter incoming packets.
+     */
+    public void installPacketFilter(byte[] filter) {}
+
+    /**
+     * Called to indicate that the APF Program & data buffer must be read asynchronously from the
+     * wifi driver.
+     *
+     * <p>Due to Wifi HAL limitations, the current implementation only supports dumping the entire
+     * buffer. In response to this request, the driver returns the data buffer asynchronously
+     * by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message.
+     */
+    public void startReadPacketFilter() {}
+
+    /**
+     * If multicast filtering cannot be accomplished with APF, this function will be called to
+     * actuate multicast filtering using another means.
+     */
+    public void setFallbackMulticastFilter(boolean enabled) {}
+
+    /**
+     * Enabled/disable Neighbor Discover offload functionality. This is called, for example,
+     * whenever 464xlat is being started or stopped.
+     */
+    public void setNeighborDiscoveryOffload(boolean enable) {}
+
+    /**
+     * Invoked on starting preconnection process.
+     */
+    public void onPreconnectionStart(List<Layer2PacketParcelable> packets) {}
+}
diff --git a/common/networkstackclient/src/android/net/ip/IpClientManager.java b/common/networkstackclient/src/android/net/ip/IpClientManager.java
new file mode 100644
index 0000000..b45405f
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ip/IpClientManager.java
@@ -0,0 +1,326 @@
+/*
+ * 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 android.net.ip;
+
+import android.annotation.Hide;
+import android.annotation.NonNull;
+import android.net.NattKeepalivePacketData;
+import android.net.ProxyInfo;
+import android.net.TcpKeepalivePacketData;
+import android.net.TcpKeepalivePacketDataParcelable;
+import android.net.shared.Layer2Information;
+import android.net.shared.ProvisioningConfiguration;
+import android.net.util.KeepalivePacketDataUtil;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A convenience wrapper for IpClient.
+ *
+ * Wraps IIpClient calls, making them a bit more friendly to use. Currently handles:
+ * - Clearing calling identity
+ * - Ignoring RemoteExceptions
+ * - Converting to stable parcelables
+ *
+ * By design, all methods on IIpClient are asynchronous oneway IPCs and are thus void. All the
+ * wrapper methods in this class return a boolean that callers can use to determine whether
+ * RemoteException was thrown.
+ */
+@Hide
+public class IpClientManager {
+    @NonNull private final IIpClient mIpClient;
+    @NonNull private final String mTag;
+
+    public IpClientManager(@NonNull IIpClient ipClient, @NonNull String tag) {
+        mIpClient = ipClient;
+        mTag = tag;
+    }
+
+    public IpClientManager(@NonNull IIpClient ipClient) {
+        this(ipClient, IpClientManager.class.getSimpleName());
+    }
+
+    private void log(String s, Throwable e) {
+        Log.e(mTag, s, e);
+    }
+
+    /**
+     * For clients using {@link ProvisioningConfiguration.Builder#withPreDhcpAction()}, must be
+     * called after {@link IIpClientCallbacks#onPreDhcpAction} to indicate that DHCP is clear to
+     * proceed.
+     */
+    public boolean completedPreDhcpAction() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.completedPreDhcpAction();
+            return true;
+        } catch (RemoteException e) {
+            log("Error completing PreDhcpAction", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Confirm the provisioning configuration.
+     */
+    public boolean confirmConfiguration() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.confirmConfiguration();
+            return true;
+        } catch (RemoteException e) {
+            log("Error confirming IpClient configuration", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Indicate that packet filter read is complete.
+     */
+    public boolean readPacketFilterComplete(byte[] data) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.readPacketFilterComplete(data);
+            return true;
+        } catch (RemoteException e) {
+            log("Error notifying IpClient of packet filter read", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Shut down this IpClient instance altogether.
+     */
+    public boolean shutdown() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.shutdown();
+            return true;
+        } catch (RemoteException e) {
+            log("Error shutting down IpClient", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Start provisioning with the provided parameters.
+     */
+    public boolean startProvisioning(ProvisioningConfiguration prov) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.startProvisioning(prov.toStableParcelable());
+            return true;
+        } catch (RemoteException e) {
+            log("Error starting IpClient provisioning", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Stop this IpClient.
+     *
+     * <p>This does not shut down the StateMachine itself, which is handled by {@link #shutdown()}.
+     */
+    public boolean stop() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.stop();
+            return true;
+        } catch (RemoteException e) {
+            log("Error stopping IpClient", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Set the TCP buffer sizes to use.
+     *
+     * This may be called, repeatedly, at any time before or after a call to
+     * #startProvisioning(). The setting is cleared upon calling #stop().
+     */
+    public boolean setTcpBufferSizes(String tcpBufferSizes) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.setTcpBufferSizes(tcpBufferSizes);
+            return true;
+        } catch (RemoteException e) {
+            log("Error setting IpClient TCP buffer sizes", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Set the HTTP Proxy configuration to use.
+     *
+     * This may be called, repeatedly, at any time before or after a call to
+     * #startProvisioning(). The setting is cleared upon calling #stop().
+     */
+    public boolean setHttpProxy(ProxyInfo proxyInfo) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.setHttpProxy(proxyInfo);
+            return true;
+        } catch (RemoteException e) {
+            log("Error setting IpClient proxy", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Enable or disable the multicast filter.  Attempts to use APF to accomplish the filtering,
+     * if not, Callback.setFallbackMulticastFilter() is called.
+     */
+    public boolean setMulticastFilter(boolean enabled) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.setMulticastFilter(enabled);
+            return true;
+        } catch (RemoteException e) {
+            log("Error setting multicast filter", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Add a TCP keepalive packet filter before setting up keepalive offload.
+     */
+    public boolean addKeepalivePacketFilter(int slot, TcpKeepalivePacketData pkt) {
+        return addKeepalivePacketFilter(slot, KeepalivePacketDataUtil.toStableParcelable(pkt));
+    }
+
+    /**
+     * Add a TCP keepalive packet filter before setting up keepalive offload.
+     * @deprecated This method is for use on pre-S platforms where TcpKeepalivePacketData is not
+     *             system API. On newer platforms use
+     *             addKeepalivePacketFilter(int, TcpKeepalivePacketData) instead.
+     */
+    @Deprecated
+    public boolean addKeepalivePacketFilter(int slot, TcpKeepalivePacketDataParcelable pkt) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.addKeepalivePacketFilter(slot, pkt);
+            return true;
+        } catch (RemoteException e) {
+            log("Error adding Keepalive Packet Filter ", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Add a NAT-T keepalive packet filter before setting up keepalive offload.
+     */
+    public boolean addKeepalivePacketFilter(int slot, NattKeepalivePacketData pkt) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.addNattKeepalivePacketFilter(
+                    slot, KeepalivePacketDataUtil.toStableParcelable(pkt));
+            return true;
+        } catch (RemoteException e) {
+            log("Error adding NAT-T Keepalive Packet Filter ", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Remove a keepalive packet filter after stopping keepalive offload.
+     */
+    public boolean removeKeepalivePacketFilter(int slot) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.removeKeepalivePacketFilter(slot);
+            return true;
+        } catch (RemoteException e) {
+            log("Error removing Keepalive Packet Filter ", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Set the L2 key and group hint for storing info into the memory store.
+     */
+    public boolean setL2KeyAndGroupHint(String l2Key, String groupHint) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.setL2KeyAndGroupHint(l2Key, groupHint);
+            return true;
+        } catch (RemoteException e) {
+            log("Failed setL2KeyAndGroupHint", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Notify IpClient that preconnection is complete and that the link is ready for use.
+     * The success parameter indicates whether the packets passed in by 'onPreconnectionStart'
+     * were successfully sent to the network or not.
+     */
+    public boolean notifyPreconnectionComplete(boolean success) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.notifyPreconnectionComplete(success);
+            return true;
+        } catch (RemoteException e) {
+            log("Error notifying IpClient Preconnection completed", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Update the bssid, L2 key and group hint layer2 information.
+     */
+    public boolean updateLayer2Information(Layer2Information info) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.updateLayer2Information(info.toStableParcelable());
+            return true;
+        } catch (RemoteException e) {
+            log("Error updating layer2 information", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+}
diff --git a/common/networkstackclient/src/android/net/ip/IpClientUtil.java b/common/networkstackclient/src/android/net/ip/IpClientUtil.java
new file mode 100644
index 0000000..1b55776
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ip/IpClientUtil.java
@@ -0,0 +1,204 @@
+/*
+ * 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 android.net.ip;
+
+import android.content.Context;
+import android.net.DhcpResultsParcelable;
+import android.net.Layer2PacketParcelable;
+import android.net.LinkProperties;
+import android.net.networkstack.ModuleNetworkStackClient;
+import android.os.ConditionVariable;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+
+/**
+ * Utilities and wrappers to simplify communication with IpClient, which lives in the NetworkStack
+ * process.
+ *
+ * @hide
+ */
+public class IpClientUtil {
+    // TODO: remove with its callers
+    public static final String DUMP_ARG = "ipclient";
+
+    /**
+     * Subclass of {@link IpClientCallbacks} allowing clients to block until provisioning is
+     * complete with {@link WaitForProvisioningCallbacks#waitForProvisioning()}.
+     */
+    public static class WaitForProvisioningCallbacks extends IpClientCallbacks {
+        private final ConditionVariable mCV = new ConditionVariable();
+        private LinkProperties mCallbackLinkProperties;
+
+        /**
+         * Block until either {@link #onProvisioningSuccess(LinkProperties)} or
+         * {@link #onProvisioningFailure(LinkProperties)} is called.
+         */
+        public LinkProperties waitForProvisioning() {
+            mCV.block();
+            return mCallbackLinkProperties;
+        }
+
+        @Override
+        public void onProvisioningSuccess(LinkProperties newLp) {
+            mCallbackLinkProperties = newLp;
+            mCV.open();
+        }
+
+        @Override
+        public void onProvisioningFailure(LinkProperties newLp) {
+            mCallbackLinkProperties = null;
+            mCV.open();
+        }
+    }
+
+    /**
+     * Create a new IpClient.
+     *
+     * <p>This is a convenience method to allow clients to use {@link IpClientCallbacks} instead of
+     * {@link IIpClientCallbacks}.
+     * @see {@link ModuleNetworkStackClient#makeIpClient(String, IIpClientCallbacks)}
+     */
+    public static void makeIpClient(Context context, String ifName, IpClientCallbacks callback) {
+        ModuleNetworkStackClient.getInstance(context)
+                .makeIpClient(ifName, new IpClientCallbacksProxy(callback));
+    }
+
+    /**
+     * Wrapper to relay calls from {@link IIpClientCallbacks} to {@link IpClientCallbacks}.
+     */
+    private static class IpClientCallbacksProxy extends IIpClientCallbacks.Stub {
+        protected final IpClientCallbacks mCb;
+
+        /**
+         * Create a new IpClientCallbacksProxy.
+         */
+        IpClientCallbacksProxy(IpClientCallbacks cb) {
+            mCb = cb;
+        }
+
+        @Override
+        public void onIpClientCreated(IIpClient ipClient) {
+            mCb.onIpClientCreated(ipClient);
+        }
+
+        @Override
+        public void onPreDhcpAction() {
+            mCb.onPreDhcpAction();
+        }
+
+        @Override
+        public void onPostDhcpAction() {
+            mCb.onPostDhcpAction();
+        }
+
+        // This is purely advisory and not an indication of provisioning
+        // success or failure.  This is only here for callers that want to
+        // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
+        // DHCPv4 or static IPv4 configuration failure or success can be
+        // determined by whether or not the passed-in DhcpResults object is
+        // null or not.
+        @Override
+        public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {
+            mCb.onNewDhcpResults(dhcpResults);
+        }
+
+        @Override
+        public void onProvisioningSuccess(LinkProperties newLp) {
+            mCb.onProvisioningSuccess(newLp);
+        }
+        @Override
+        public void onProvisioningFailure(LinkProperties newLp) {
+            mCb.onProvisioningFailure(newLp);
+        }
+
+        // Invoked on LinkProperties changes.
+        @Override
+        public void onLinkPropertiesChange(LinkProperties newLp) {
+            mCb.onLinkPropertiesChange(newLp);
+        }
+
+        // Called when the internal IpReachabilityMonitor (if enabled) has
+        // detected the loss of a critical number of required neighbors.
+        @Override
+        public void onReachabilityLost(String logMsg) {
+            mCb.onReachabilityLost(logMsg);
+        }
+
+        // Called when the IpClient state machine terminates.
+        @Override
+        public void onQuit() {
+            mCb.onQuit();
+        }
+
+        // Install an APF program to filter incoming packets.
+        @Override
+        public void installPacketFilter(byte[] filter) {
+            mCb.installPacketFilter(filter);
+        }
+
+        // Asynchronously read back the APF program & data buffer from the wifi driver.
+        // Due to Wifi HAL limitations, the current implementation only supports dumping the entire
+        // buffer. In response to this request, the driver returns the data buffer asynchronously
+        // by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message.
+        @Override
+        public void startReadPacketFilter() {
+            mCb.startReadPacketFilter();
+        }
+
+        // If multicast filtering cannot be accomplished with APF, this function will be called to
+        // actuate multicast filtering using another means.
+        @Override
+        public void setFallbackMulticastFilter(boolean enabled) {
+            mCb.setFallbackMulticastFilter(enabled);
+        }
+
+        // Enabled/disable Neighbor Discover offload functionality. This is
+        // called, for example, whenever 464xlat is being started or stopped.
+        @Override
+        public void setNeighborDiscoveryOffload(boolean enable) {
+            mCb.setNeighborDiscoveryOffload(enable);
+        }
+
+        // Invoked on starting preconnection process.
+        @Override
+        public void onPreconnectionStart(List<Layer2PacketParcelable> packets) {
+            mCb.onPreconnectionStart(packets);
+        }
+
+        @Override
+        public int getInterfaceVersion() {
+            return this.VERSION;
+        }
+
+        @Override
+        public String getInterfaceHash() {
+            return this.HASH;
+        }
+    }
+
+    /**
+     * Dump logs for the specified IpClient.
+     * TODO: remove callers and delete
+     */
+    public static void dumpIpClient(
+            IIpClient connector, FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("IpClient logs have moved to dumpsys network_stack");
+    }
+}
diff --git a/common/moduleutils/src/android/net/shared/InitialConfiguration.java b/common/networkstackclient/src/android/net/shared/InitialConfiguration.java
similarity index 100%
rename from common/moduleutils/src/android/net/shared/InitialConfiguration.java
rename to common/networkstackclient/src/android/net/shared/InitialConfiguration.java
diff --git a/common/moduleutils/src/android/net/shared/Layer2Information.java b/common/networkstackclient/src/android/net/shared/Layer2Information.java
similarity index 100%
rename from common/moduleutils/src/android/net/shared/Layer2Information.java
rename to common/networkstackclient/src/android/net/shared/Layer2Information.java
diff --git a/common/moduleutils/src/android/net/shared/ParcelableUtil.java b/common/networkstackclient/src/android/net/shared/ParcelableUtil.java
similarity index 100%
rename from common/moduleutils/src/android/net/shared/ParcelableUtil.java
rename to common/networkstackclient/src/android/net/shared/ParcelableUtil.java
diff --git a/common/moduleutils/src/android/net/shared/PrivateDnsConfig.java b/common/networkstackclient/src/android/net/shared/PrivateDnsConfig.java
similarity index 100%
rename from common/moduleutils/src/android/net/shared/PrivateDnsConfig.java
rename to common/networkstackclient/src/android/net/shared/PrivateDnsConfig.java
diff --git a/common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java b/common/networkstackclient/src/android/net/shared/ProvisioningConfiguration.java
similarity index 81%
rename from common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java
rename to common/networkstackclient/src/android/net/shared/ProvisioningConfiguration.java
index d3bc04d..3bf6744 100644
--- a/common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java
+++ b/common/networkstackclient/src/android/net/shared/ProvisioningConfiguration.java
@@ -16,12 +16,17 @@
 
 package android.net.shared;
 
+import static android.net.ip.IIpClient.PROV_IPV4_DHCP;
+import static android.net.ip.IIpClient.PROV_IPV4_DISABLED;
+import static android.net.ip.IIpClient.PROV_IPV4_STATIC;
+import static android.net.ip.IIpClient.PROV_IPV6_DISABLED;
+import static android.net.ip.IIpClient.PROV_IPV6_LINKLOCAL;
+import static android.net.ip.IIpClient.PROV_IPV6_SLAAC;
 import static android.net.shared.ParcelableUtil.fromParcelableArray;
 import static android.net.shared.ParcelableUtil.toParcelableArray;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.net.INetd;
 import android.net.InformationElementParcelable;
 import android.net.Network;
 import android.net.ProvisioningConfigurationParcelable;
@@ -32,6 +37,8 @@
 import android.net.networkstack.aidl.dhcp.DhcpOption;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
@@ -76,6 +83,17 @@
     // allowing for 10% jitter.
     private static final int DEFAULT_TIMEOUT_MS = 18 * 1000;
 
+    // TODO: These cannot be imported from INetd.aidl, because networkstack-client cannot depend on
+    // INetd, as there are users of IpClient that depend on INetd directly (potentially at a
+    // different version, which is not allowed by the build system).
+    // Find a better way to express these constants.
+    public static final int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+    public static final int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+
+    // ipv4ProvisioningMode and ipv6ProvisioningMode members are introduced since
+    // networkstack-aidl-interfaces-v12.
+    public static final int VERSION_ADDED_PROVISIONING_ENUM = 12;
+
     /**
      * Builder to create a {@link ProvisioningConfiguration}.
      */
@@ -86,7 +104,7 @@
          * Specify that the configuration should not enable IPv4. It is enabled by default.
          */
         public Builder withoutIPv4() {
-            mConfig.mEnableIPv4 = false;
+            mConfig.mIPv4ProvisioningMode = PROV_IPV4_DISABLED;
             return this;
         }
 
@@ -94,7 +112,7 @@
          * Specify that the configuration should not enable IPv6. It is enabled by default.
          */
         public Builder withoutIPv6() {
-            mConfig.mEnableIPv6 = false;
+            mConfig.mIPv6ProvisioningMode = PROV_IPV6_DISABLED;
             return this;
         }
 
@@ -156,6 +174,7 @@
          * Specify a static configuration for provisioning.
          */
         public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
+            mConfig.mIPv4ProvisioningMode = PROV_IPV4_STATIC;
             mConfig.mStaticIpConfig = staticConfig;
             return this;
         }
@@ -180,7 +199,7 @@
          * Specify that IPv6 address generation should use a random MAC address.
          */
         public Builder withRandomMacAddress() {
-            mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
+            mConfig.mIPv6AddrGenMode = IPV6_ADDR_GEN_MODE_EUI64;
             return this;
         }
 
@@ -188,7 +207,7 @@
          * Specify that IPv6 address generation should use a stable MAC address.
          */
         public Builder withStableMacAddress() {
-            mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+            mConfig.mIPv6AddrGenMode = IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
             return this;
         }
 
@@ -233,15 +252,32 @@
          *
          * @param: options customized DHCP option stable parcelable list.
          */
-        public Builder withDhcpOptions(List<DhcpOption> options) {
+        public Builder withDhcpOptions(@Nullable List<DhcpOption> options) {
             mConfig.mDhcpOptions = options;
             return this;
         }
 
         /**
+         * Specify that the configuration should enable IPv6 link-local only mode used for
+         * WiFi Neighbor Aware Networking and other link-local-only technologies. It's not
+         * used by default, and IPv4 must be disabled when this mode is enabled.
+         *
+         * @note This API is only supported since Android T.
+         */
+        public Builder withIpv6LinkLocalOnly() {
+            mConfig.mIPv6ProvisioningMode = PROV_IPV6_LINKLOCAL;
+            return this;
+        }
+
+        /**
          * Build the configuration using previously specified parameters.
          */
         public ProvisioningConfiguration build() {
+            if (mConfig.mIPv6ProvisioningMode == PROV_IPV6_LINKLOCAL
+                    && mConfig.mIPv4ProvisioningMode != PROV_IPV4_DISABLED) {
+                throw new IllegalArgumentException("IPv4 must be disabled in IPv6 link-local"
+                        + "only mode.");
+            }
             return new ProvisioningConfiguration(mConfig);
         }
     }
@@ -427,8 +463,6 @@
         }
     }
 
-    public boolean mEnableIPv4 = true;
-    public boolean mEnableIPv6 = true;
     public boolean mEnablePreconnection = false;
     public boolean mUsingMultinetworkPolicyTracker = true;
     public boolean mUsingIpReachabilityMonitor = true;
@@ -437,18 +471,18 @@
     public StaticIpConfiguration mStaticIpConfig;
     public ApfCapabilities mApfCapabilities;
     public int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
-    public int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+    public int mIPv6AddrGenMode = IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
     public Network mNetwork = null;
     public String mDisplayName = null;
     public ScanResultInfo mScanResultInfo;
     public Layer2Information mLayer2Info;
     public List<DhcpOption> mDhcpOptions;
+    public int mIPv4ProvisioningMode = PROV_IPV4_DHCP;
+    public int mIPv6ProvisioningMode = PROV_IPV6_SLAAC;
 
     public ProvisioningConfiguration() {} // used by Builder
 
     public ProvisioningConfiguration(ProvisioningConfiguration other) {
-        mEnableIPv4 = other.mEnableIPv4;
-        mEnableIPv6 = other.mEnableIPv6;
         mEnablePreconnection = other.mEnablePreconnection;
         mUsingMultinetworkPolicyTracker = other.mUsingMultinetworkPolicyTracker;
         mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
@@ -465,6 +499,8 @@
         mScanResultInfo = other.mScanResultInfo;
         mLayer2Info = other.mLayer2Info;
         mDhcpOptions = other.mDhcpOptions;
+        mIPv4ProvisioningMode = other.mIPv4ProvisioningMode;
+        mIPv6ProvisioningMode = other.mIPv6ProvisioningMode;
     }
 
     /**
@@ -472,8 +508,10 @@
      */
     public ProvisioningConfigurationParcelable toStableParcelable() {
         final ProvisioningConfigurationParcelable p = new ProvisioningConfigurationParcelable();
-        p.enableIPv4 = mEnableIPv4;
-        p.enableIPv6 = mEnableIPv6;
+        p.enableIPv4 = (mIPv4ProvisioningMode != PROV_IPV4_DISABLED);
+        p.ipv4ProvisioningMode = mIPv4ProvisioningMode;
+        p.enableIPv6 = (mIPv6ProvisioningMode != PROV_IPV6_DISABLED);
+        p.ipv6ProvisioningMode = mIPv6ProvisioningMode;
         p.enablePreconnection = mEnablePreconnection;
         p.usingMultinetworkPolicyTracker = mUsingMultinetworkPolicyTracker;
         p.usingIpReachabilityMonitor = mUsingIpReachabilityMonitor;
@@ -495,13 +533,16 @@
 
     /**
      * Create a ProvisioningConfiguration from a ProvisioningConfigurationParcelable.
+     *
+     * @param p stable parcelable instance to be converted to a {@link ProvisioningConfiguration}.
+     * @param interfaceVersion IIpClientCallbacks interface version called by the remote peer,
+     *                         which is used to determine the appropriate parcelable members for
+     *                         backwards compatibility.
      */
     public static ProvisioningConfiguration fromStableParcelable(
-            @Nullable ProvisioningConfigurationParcelable p) {
+            @Nullable ProvisioningConfigurationParcelable p, int interfaceVersion) {
         if (p == null) return null;
         final ProvisioningConfiguration config = new ProvisioningConfiguration();
-        config.mEnableIPv4 = p.enableIPv4;
-        config.mEnableIPv6 = p.enableIPv6;
         config.mEnablePreconnection = p.enablePreconnection;
         config.mUsingMultinetworkPolicyTracker = p.usingMultinetworkPolicyTracker;
         config.mUsingIpReachabilityMonitor = p.usingIpReachabilityMonitor;
@@ -518,14 +559,49 @@
         config.mScanResultInfo = ScanResultInfo.fromStableParcelable(p.scanResultInfo);
         config.mLayer2Info = Layer2Information.fromStableParcelable(p.layer2Info);
         config.mDhcpOptions = (p.options == null) ? null : new ArrayList<>(p.options);
+        if (interfaceVersion < VERSION_ADDED_PROVISIONING_ENUM) {
+            config.mIPv4ProvisioningMode = p.enableIPv4 ? PROV_IPV4_DHCP : PROV_IPV4_DISABLED;
+            config.mIPv6ProvisioningMode = p.enableIPv6 ? PROV_IPV6_SLAAC : PROV_IPV6_DISABLED;
+        } else {
+            config.mIPv4ProvisioningMode = p.ipv4ProvisioningMode;
+            config.mIPv6ProvisioningMode = p.ipv6ProvisioningMode;
+        }
         return config;
     }
 
+    @VisibleForTesting
+    static String ipv4ProvisioningModeToString(int mode) {
+        switch (mode) {
+            case PROV_IPV4_DISABLED:
+                return "disabled";
+            case PROV_IPV4_STATIC:
+                return "static";
+            case PROV_IPV4_DHCP:
+                return "dhcp";
+            default:
+                return "unknown";
+        }
+    }
+
+    @VisibleForTesting
+    static String ipv6ProvisioningModeToString(int mode) {
+        switch (mode) {
+            case PROV_IPV6_DISABLED:
+                return "disabled";
+            case PROV_IPV6_SLAAC:
+                return "slaac";
+            case PROV_IPV6_LINKLOCAL:
+                return "link-local";
+            default:
+                return "unknown";
+        }
+    }
+
     @Override
     public String toString() {
+        final String ipv4ProvisioningMode = ipv4ProvisioningModeToString(mIPv4ProvisioningMode);
+        final String ipv6ProvisioningMode = ipv6ProvisioningModeToString(mIPv6ProvisioningMode);
         return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
-                .add("mEnableIPv4: " + mEnableIPv4)
-                .add("mEnableIPv6: " + mEnableIPv6)
                 .add("mEnablePreconnection: " + mEnablePreconnection)
                 .add("mUsingMultinetworkPolicyTracker: " + mUsingMultinetworkPolicyTracker)
                 .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
@@ -540,6 +616,8 @@
                 .add("mScanResultInfo: " + mScanResultInfo)
                 .add("mLayer2Info: " + mLayer2Info)
                 .add("mDhcpOptions: " + mDhcpOptions)
+                .add("mIPv4ProvisioningMode: " + ipv4ProvisioningMode)
+                .add("mIPv6ProvisioningMode: " + ipv6ProvisioningMode)
                 .toString();
     }
 
@@ -569,9 +647,7 @@
     public boolean equals(Object obj) {
         if (!(obj instanceof ProvisioningConfiguration)) return false;
         final ProvisioningConfiguration other = (ProvisioningConfiguration) obj;
-        return mEnableIPv4 == other.mEnableIPv4
-                && mEnableIPv6 == other.mEnableIPv6
-                && mEnablePreconnection == other.mEnablePreconnection
+        return mEnablePreconnection == other.mEnablePreconnection
                 && mUsingMultinetworkPolicyTracker == other.mUsingMultinetworkPolicyTracker
                 && mUsingIpReachabilityMonitor == other.mUsingIpReachabilityMonitor
                 && mRequestedPreDhcpActionMs == other.mRequestedPreDhcpActionMs
@@ -584,7 +660,9 @@
                 && Objects.equals(mDisplayName, other.mDisplayName)
                 && Objects.equals(mScanResultInfo, other.mScanResultInfo)
                 && Objects.equals(mLayer2Info, other.mLayer2Info)
-                && dhcpOptionListEquals(mDhcpOptions, other.mDhcpOptions);
+                && dhcpOptionListEquals(mDhcpOptions, other.mDhcpOptions)
+                && mIPv4ProvisioningMode == other.mIPv4ProvisioningMode
+                && mIPv6ProvisioningMode == other.mIPv6ProvisioningMode;
     }
 
     public boolean isValid() {
diff --git a/common/networkstackclient/src/android/net/util/KeepalivePacketDataUtil.java b/common/networkstackclient/src/android/net/util/KeepalivePacketDataUtil.java
new file mode 100644
index 0000000..5666985
--- /dev/null
+++ b/common/networkstackclient/src/android/net/util/KeepalivePacketDataUtil.java
@@ -0,0 +1,223 @@
+/*
+ * 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 android.net.util;
+
+import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.InvalidPacketException;
+import android.net.KeepalivePacketData;
+import android.net.NattKeepalivePacketData;
+import android.net.NattKeepalivePacketDataParcelable;
+import android.net.TcpKeepalivePacketData;
+import android.net.TcpKeepalivePacketDataParcelable;
+import android.os.Build;
+import android.system.OsConstants;
+import android.util.Log;
+
+import com.android.net.module.util.IpUtils;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Utility class to convert to/from keepalive data parcelables.
+ *
+ * TODO: move to networkstack-client library when it is moved to frameworks/libs/net.
+ * This class cannot go into other shared libraries as it depends on NetworkStack AIDLs.
+ * @hide
+ */
+public final class KeepalivePacketDataUtil {
+    private static final int IPV4_HEADER_LENGTH = 20;
+    private static final int IPV6_HEADER_LENGTH = 40;
+    private static final int TCP_HEADER_LENGTH = 20;
+
+    private static final String TAG = KeepalivePacketDataUtil.class.getSimpleName();
+
+    /**
+     * Convert a NattKeepalivePacketData to a NattKeepalivePacketDataParcelable.
+     */
+    @NonNull
+    public static NattKeepalivePacketDataParcelable toStableParcelable(
+            @NonNull NattKeepalivePacketData pkt) {
+        final NattKeepalivePacketDataParcelable parcel = new NattKeepalivePacketDataParcelable();
+        final InetAddress srcAddress = pkt.getSrcAddress();
+        final InetAddress dstAddress = pkt.getDstAddress();
+        parcel.srcAddress = srcAddress.getAddress();
+        parcel.srcPort = pkt.getSrcPort();
+        parcel.dstAddress = dstAddress.getAddress();
+        parcel.dstPort = pkt.getDstPort();
+        return parcel;
+    }
+
+    /**
+     * Convert a TcpKeepalivePacketData to a TcpKeepalivePacketDataParcelable.
+     */
+    @NonNull
+    public static TcpKeepalivePacketDataParcelable toStableParcelable(
+            @NonNull TcpKeepalivePacketData pkt) {
+        final TcpKeepalivePacketDataParcelable parcel = new TcpKeepalivePacketDataParcelable();
+        final InetAddress srcAddress = pkt.getSrcAddress();
+        final InetAddress dstAddress = pkt.getDstAddress();
+        parcel.srcAddress = srcAddress.getAddress();
+        parcel.srcPort = pkt.getSrcPort();
+        parcel.dstAddress = dstAddress.getAddress();
+        parcel.dstPort = pkt.getDstPort();
+        parcel.seq = pkt.getTcpSeq();
+        parcel.ack = pkt.getTcpAck();
+        parcel.rcvWnd = pkt.getTcpWindow();
+        parcel.rcvWndScale = pkt.getTcpWindowScale();
+        parcel.tos = pkt.getIpTos();
+        parcel.ttl = pkt.getIpTtl();
+        return parcel;
+    }
+
+    /**
+     * Factory method to create tcp keepalive packet structure.
+     * @hide
+     */
+    public static TcpKeepalivePacketData fromStableParcelable(
+            TcpKeepalivePacketDataParcelable tcpDetails) throws InvalidPacketException {
+        final byte[] packet;
+        try {
+            if ((tcpDetails.srcAddress != null) && (tcpDetails.dstAddress != null)
+                    && (tcpDetails.srcAddress.length == 4 /* V4 IP length */)
+                    && (tcpDetails.dstAddress.length == 4 /* V4 IP length */)) {
+                packet = buildV4Packet(tcpDetails);
+            } else {
+                // TODO: support ipv6
+                throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
+            }
+            return new TcpKeepalivePacketData(
+                    InetAddress.getByAddress(tcpDetails.srcAddress),
+                    tcpDetails.srcPort,
+                    InetAddress.getByAddress(tcpDetails.dstAddress),
+                    tcpDetails.dstPort,
+                    packet,
+                    tcpDetails.seq, tcpDetails.ack, tcpDetails.rcvWnd, tcpDetails.rcvWndScale,
+                    tcpDetails.tos, tcpDetails.ttl);
+        } catch (UnknownHostException e) {
+            throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
+        }
+
+    }
+
+    /**
+     * Build ipv4 tcp keepalive packet, not including the link-layer header.
+     */
+    // TODO : if this code is ever moved to the network stack, factorize constants with the ones
+    // over there.
+    private static byte[] buildV4Packet(TcpKeepalivePacketDataParcelable tcpDetails) {
+        final int length = IPV4_HEADER_LENGTH + TCP_HEADER_LENGTH;
+        ByteBuffer buf = ByteBuffer.allocate(length);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.put((byte) 0x45);                       // IP version and IHL
+        buf.put((byte) tcpDetails.tos);             // TOS
+        buf.putShort((short) length);
+        buf.putInt(0x00004000);                     // ID, flags=DF, offset
+        buf.put((byte) tcpDetails.ttl);             // TTL
+        buf.put((byte) OsConstants.IPPROTO_TCP);
+        final int ipChecksumOffset = buf.position();
+        buf.putShort((short) 0);                    // IP checksum
+        buf.put(tcpDetails.srcAddress);
+        buf.put(tcpDetails.dstAddress);
+        buf.putShort((short) tcpDetails.srcPort);
+        buf.putShort((short) tcpDetails.dstPort);
+        buf.putInt(tcpDetails.seq);                 // Sequence Number
+        buf.putInt(tcpDetails.ack);                 // ACK
+        buf.putShort((short) 0x5010);               // TCP length=5, flags=ACK
+        buf.putShort((short) (tcpDetails.rcvWnd >> tcpDetails.rcvWndScale));   // Window size
+        final int tcpChecksumOffset = buf.position();
+        buf.putShort((short) 0);                    // TCP checksum
+        // URG is not set therefore the urgent pointer is zero.
+        buf.putShort((short) 0);                    // Urgent pointer
+
+        buf.putShort(ipChecksumOffset, com.android.net.module.util.IpUtils.ipChecksum(buf, 0));
+        buf.putShort(tcpChecksumOffset, IpUtils.tcpChecksum(
+                buf, 0, IPV4_HEADER_LENGTH, TCP_HEADER_LENGTH));
+
+        return buf.array();
+    }
+
+    // TODO: add buildV6Packet.
+
+    /**
+     * Get a {@link TcpKeepalivePacketDataParcelable} from {@link KeepalivePacketData}, if the
+     * generic class actually contains TCP keepalive data.
+     *
+     * @deprecated This method is used on R platforms where android.net.TcpKeepalivePacketData was
+     * not yet system API. Newer platforms should use android.net.TcpKeepalivePacketData directly.
+     *
+     * @param data A {@link KeepalivePacketData} that may contain TCP keepalive data.
+     * @return A parcelable containing TCP keepalive data, or null if the input data does not
+     *         contain TCP keepalive data.
+     */
+    @Deprecated
+    @SuppressWarnings("AndroidFrameworkCompatChange") // API version check used to Log.wtf
+    @Nullable
+    public static TcpKeepalivePacketDataParcelable parseTcpKeepalivePacketData(
+            @Nullable KeepalivePacketData data) {
+        if (data == null) return null;
+
+        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
+            Log.wtf(TAG, "parseTcpKeepalivePacketData should not be used after R, use "
+                    + "TcpKeepalivePacketData instead.");
+        }
+
+        // Reconstruct TcpKeepalivePacketData from the packet contained in KeepalivePacketData
+        final ByteBuffer buffer = ByteBuffer.wrap(data.getPacket());
+        buffer.order(ByteOrder.BIG_ENDIAN);
+
+        // Most of the fields are accessible from the KeepalivePacketData superclass: instead of
+        // using Struct to parse everything, just extract the extra fields necessary for
+        // TcpKeepalivePacketData.
+        final int tcpSeq;
+        final int tcpAck;
+        final int wndSize;
+        final int ipTos;
+        final int ttl;
+        try {
+            // This only support IPv4, because TcpKeepalivePacketData only supports IPv4 for R and
+            // below, and this method should not be used on newer platforms.
+            tcpSeq = buffer.getInt(IPV4_HEADER_LENGTH + 4);
+            tcpAck = buffer.getInt(IPV4_HEADER_LENGTH + 8);
+            wndSize = buffer.getShort(IPV4_HEADER_LENGTH + 14);
+            ipTos = buffer.get(1);
+            ttl = buffer.get(8);
+        } catch (IndexOutOfBoundsException e) {
+            return null;
+        }
+
+        final TcpKeepalivePacketDataParcelable p = new TcpKeepalivePacketDataParcelable();
+        p.srcAddress = data.getSrcAddress().getAddress();
+        p.srcPort = data.getSrcPort();
+        p.dstAddress = data.getDstAddress().getAddress();
+        p.dstPort = data.getDstPort();
+        p.seq = tcpSeq;
+        p.ack = tcpAck;
+        // TcpKeepalivePacketData could actually use non-zero wndScale, but this does not affect
+        // actual functionality as generated packets will be the same (no wndScale option added)
+        p.rcvWnd = wndSize;
+        p.rcvWndScale = 0;
+        p.tos = ipTos;
+        p.ttl = ttl;
+        return p;
+    }
+}
diff --git a/jarjar-rules-shared.txt b/jarjar-rules-shared.txt
index bb2acd4..e8c9c19 100644
--- a/jarjar-rules-shared.txt
+++ b/jarjar-rules-shared.txt
@@ -11,3 +11,6 @@
 rule android.util.LocalLog* android.net.networkstack.util.LocalLog@1
 
 rule android.util.IndentingPrintWriter* android.net.networkstack.util.AndroidUtilIndentingPrintWriter@1
+
+# Classes from modules-utils-build_system
+rule com.android.modules.utils.build.** com.android.networkstack.utils.build.@1
\ No newline at end of file
diff --git a/lint-baseline-api-30-shims.xml b/lint-baseline-api-30-shims.xml
new file mode 100644
index 0000000..da541cd
--- /dev/null
+++ b/lint-baseline-api-30-shims.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `android.net.LinkProperties#getNat64Prefix`"
+        errorLine1="        return lp.getNat64Prefix();"
+        errorLine2="                  ~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/apishim/30/com/android/networkstack/apishim/api30/NetworkInformationShimImpl.java"
+            line="85"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `android.net.LinkProperties#setNat64Prefix`"
+        errorLine1="        lp.setNat64Prefix(prefix);"
+        errorLine2="           ~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/apishim/30/com/android/networkstack/apishim/api30/NetworkInformationShimImpl.java"
+            line="90"
+            column="12"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `android.net.LinkProperties#setDhcpServerAddress`"
+        errorLine1="        lp.setDhcpServerAddress(serverAddress);"
+        errorLine2="           ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/apishim/30/com/android/networkstack/apishim/api30/NetworkInformationShimImpl.java"
+            line="109"
+            column="12"/>
+    </issue>
+
+</issues>
diff --git a/lint-baseline-current-lib.xml b/lint-baseline-current-lib.xml
new file mode 100644
index 0000000..e8cfe3e
--- /dev/null
+++ b/lint-baseline-current-lib.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `android.net.LinkProperties#getNat64Prefix`"
+        errorLine1="        newLp.setNat64Prefix(netlinkLinkProperties.getNat64Prefix());"
+        errorLine2="                                                   ~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/src/android/net/ip/IpClient.java"
+            line="1337"
+            column="52"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `android.net.LinkProperties#setNat64Prefix`"
+        errorLine1="        newLp.setNat64Prefix(netlinkLinkProperties.getNat64Prefix());"
+        errorLine2="              ~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/src/android/net/ip/IpClient.java"
+            line="1337"
+            column="15"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `android.telephony.NetworkRegistrationInfo#getCellIdentity`"
+        errorLine1="                    nri == null ? null : nri.getCellIdentity());"
+        errorLine2="                                             ~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java"
+            line="3088"
+            column="46"/>
+    </issue>
+
+</issues>
diff --git a/lint-baseline-stable-lib.xml b/lint-baseline-stable-lib.xml
new file mode 100644
index 0000000..e8cfe3e
--- /dev/null
+++ b/lint-baseline-stable-lib.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `android.net.LinkProperties#getNat64Prefix`"
+        errorLine1="        newLp.setNat64Prefix(netlinkLinkProperties.getNat64Prefix());"
+        errorLine2="                                                   ~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/src/android/net/ip/IpClient.java"
+            line="1337"
+            column="52"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `android.net.LinkProperties#setNat64Prefix`"
+        errorLine1="        newLp.setNat64Prefix(netlinkLinkProperties.getNat64Prefix());"
+        errorLine2="              ~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/src/android/net/ip/IpClient.java"
+            line="1337"
+            column="15"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `android.telephony.NetworkRegistrationInfo#getCellIdentity`"
+        errorLine1="                    nri == null ? null : nri.getCellIdentity());"
+        errorLine2="                                             ~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java"
+            line="3088"
+            column="46"/>
+    </issue>
+
+</issues>
diff --git a/proguard.flags b/proguard.flags
index af4262a..7f8f207 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -8,6 +8,10 @@
     static final int EVENT_*;
 }
 
+-keepclassmembers public class * extends com.android.networkstack.util.Struct {
+    *;
+}
+
 # The lite proto runtime uses reflection to access fields based on the names in
 # the schema, keep all the fields.
 # This replicates the base proguard rule used by the build by default
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index e54f11c..706f174 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -19,7 +19,7 @@
     <string name="notification_channel_name_connected" msgid="1795068343200033922">"Godkendelse til loginportal"</string>
     <string name="notification_channel_description_connected" msgid="7239184168268014518">"De notifikationer, der vises, når enheden er blevet godkendt til et netværk via en loginportal"</string>
     <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Oplysninger om netværksplacering"</string>
-    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifikationer, der vises for at indikere, at netværket har en side med oplysninger om placeringen"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifikationer, der vises for at indikere, at netværket har en side med oplysninger om lokationen"</string>
     <string name="connected" msgid="4563643884927480998">"Der er oprettet forbindelse"</string>
     <string name="tap_for_info" msgid="6849746325626883711">"Der er oprettet forbindelse/tryk for at se website"</string>
     <string name="application_label" msgid="1322847171305285454">"Netværksadministrator"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 4772691..2267cd4 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -18,9 +18,9 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="notification_channel_name_connected" msgid="1795068343200033922">"אימות של פורטל שבוי"</string>
     <string name="notification_channel_description_connected" msgid="7239184168268014518">"התראות המוצגות כשהמכשיר אומת בהצלחה וחובר לרשת של פורטל שבוי"</string>
-    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"מידע על מקום רשת"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"מידע על מקום הרשת"</string>
     <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"התראות המוצגות כדי לציין שלרשת יש דף מידע על מקום"</string>
-    <string name="connected" msgid="4563643884927480998">"מחובר"</string>
+    <string name="connected" msgid="4563643884927480998">"המכשיר מחובר"</string>
     <string name="tap_for_info" msgid="6849746325626883711">"מחוברת / יש להקיש כדי להציג את האתר"</string>
     <string name="application_label" msgid="1322847171305285454">"ניהול רשתות"</string>
 </resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 9aec881..bed0ea7 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -17,9 +17,9 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="notification_channel_name_connected" msgid="1795068343200033922">"Кирүү бетинин аутентификациясы"</string>
-    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Түзмөк тармактын кирүү бети аркылуу аутентификациядан ийгиликтүү өткөндө билдирмелер көрсөтүлөт"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Түзмөк тармактын кирүү бети аркылуу аутентификациядан ийгиликтүү өткөндө билдирмелер көрүнөт"</string>
     <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Тармактын жайгашуусу жөнүндө маалымат"</string>
-    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Тармак маалымат барагына киргенде билдирме көрсөтүлөт"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Тармак маалымат барагына киргенде билдирме көрүнөт"</string>
     <string name="connected" msgid="4563643884927480998">"Туташты"</string>
     <string name="tap_for_info" msgid="6849746325626883711">"Туташты / Вебсайтты көрүү үчүн таптаңыз"</string>
     <string name="application_label" msgid="1322847171305285454">"Тармактарды башкаргыч"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 500d584..b7ff1bc 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -20,7 +20,7 @@
     <string name="notification_channel_description_connected" msgid="7239184168268014518">"यन्त्र क्याप्टिभ पोर्टल नेटवर्कमा सफलतापूर्वक जोडिएको कुरा प्रमाणित भएपछि देखाइने सूचनाहरू"</string>
     <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"नेटवर्कको स्थानसम्बन्धी जानकारी"</string>
     <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"नेटवर्कको स्थानसम्बन्धी जानकारी भएको पृष्ठ रहेको सङ्केत गर्न देखाइने सूचनाहरू"</string>
-    <string name="connected" msgid="4563643884927480998">"जोडिएको छ"</string>
+    <string name="connected" msgid="4563643884927480998">"कनेक्ट गरिएको छ"</string>
     <string name="tap_for_info" msgid="6849746325626883711">"जोडियो / वेबसाइट हेर्न ट्याप गर्नुहोस्"</string>
     <string name="application_label" msgid="1322847171305285454">"नेटवर्क व्यवस्थापक"</string>
 </resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 527d895..7b54302 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -17,9 +17,9 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="notification_channel_name_connected" msgid="1795068343200033922">"Verificatie van captive portal"</string>
-    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Er worden meldingen weergegeven als het apparaat is geverifieerd voor een captive portal-netwerk"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Er worden meldingen getoond als het apparaat is geverifieerd voor een captive portal-netwerk"</string>
     <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Netwerklocatie-informatie"</string>
-    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Er worden meldingen weergegeven om aan te geven dat het netwerk een locatie-informatiepagina heeft"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Er worden meldingen getoond om aan te geven dat het netwerk een locatie-informatiepagina heeft"</string>
     <string name="connected" msgid="4563643884927480998">"Verbonden"</string>
     <string name="tap_for_info" msgid="6849746325626883711">"Verbonden / Tik om de website te bekijken"</string>
     <string name="application_label" msgid="1322847171305285454">"Netwerkbeheer"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index d6a11ab..805ca04 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -105,4 +105,20 @@
          increased until reaching the config_max_retry_timer. -->
     <integer name="config_evaluating_bandwidth_min_retry_timer_ms"></integer>
     <integer name="config_evaluating_bandwidth_max_retry_timer_ms"></integer>
+
+    <!-- Whether the APF Filter in the device should filter out IEEE 802.3 Frames
+         Those frames are identified by the field Eth-type having values
+         less than 0x600 -->
+    <bool name="config_apfDrop802_3Frames">true</bool>
+
+    <!-- An array of Denylisted EtherType, packets with EtherTypes within this array
+         will be dropped
+         TODO: need to put proper values, these are for testing purposes only -->
+    <integer-array name="config_apfEthTypeDenyList">
+        <item>0x88A2</item>
+        <item>0x88A4</item>
+        <item>0x88B8</item>
+        <item>0x88CD</item>
+        <item>0x88E3</item>
+    </integer-array>
 </resources>
diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml
index b2967b9..bfb450e 100644
--- a/res/values/overlayable.xml
+++ b/res/values/overlayable.xml
@@ -77,6 +77,13 @@
             <item type="integer" name="config_evaluating_bandwidth_timeout_ms"/>
             <item type="integer" name="config_evaluating_bandwidth_min_retry_timer_ms"/>
             <item type="integer" name="config_evaluating_bandwidth_max_retry_timer_ms"/>
+
+            <!-- Whether the APF Filter in the device should filter out IEEE 802.3 Frames
+            Those frames are identified by the field Eth-type having values less than 0x600 -->
+            <item type="bool" name="config_apfDrop802_3Frames"/>
+            <!-- An array of Denylisted EtherType, packets with EtherTypes within this array
+            will be dropped -->
+            <item type="array" name="config_apfEthTypeDenyList"/>
         </policy>
     </overlayable>
 </resources>
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index 0bb4094..7a13392 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -27,6 +27,7 @@
 import static android.system.OsConstants.IPPROTO_UDP;
 import static android.system.OsConstants.SOCK_RAW;
 
+import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST;
 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
@@ -285,8 +286,6 @@
     private static final int ETH_ETHERTYPE_OFFSET = 12;
     private static final int ETH_TYPE_MIN = 0x0600;
     private static final int ETH_TYPE_MAX = 0xFFFF;
-    private static final byte[] ETH_BROADCAST_MAC_ADDRESS =
-            {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
     // TODO: Make these offsets relative to end of link-layer header; don't include ETH_HEADER_LEN.
     private static final int IPV4_TOTAL_LENGTH_OFFSET = ETH_HEADER_LEN + 2;
     private static final int IPV4_FRAGMENT_OFFSET_OFFSET = ETH_HEADER_LEN + 6;
@@ -1254,7 +1253,7 @@
         // Pass if unicast reply.
         gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
         maybeSetupCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY);
-        gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel);
+        gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel);
 
         // Either a unicast request, a unicast reply, or a broadcast reply.
         gen.defineLabel(checkTargetIPv4);
@@ -1350,7 +1349,7 @@
             // TODO: can we invert this condition to fall through to the common pass case below?
             maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST);
             gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
-            gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel);
+            gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel);
             maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST);
             gen.addJump(mCountAndDropLabel);
         } else {
@@ -1412,7 +1411,7 @@
         //     pass
         // if it's ICMPv6 RS to any:
         //   drop
-        // if it's ICMPv6 NA to ff02::1:
+        // if it's ICMPv6 NA to anything in ff02::/120
         //   drop
         // if keepalive ack
         //   drop
@@ -1466,11 +1465,14 @@
         gen.addJumpIfR0Equals(ICMPV6_ROUTER_SOLICITATION, mCountAndDropLabel);
         // If not neighbor announcements, skip filter.
         gen.addJumpIfR0NotEquals(ICMPV6_NEIGHBOR_ADVERTISEMENT, skipUnsolicitedMulticastNALabel);
-        // If to ff02::1, drop.
+        // Drop all multicast NA to ff02::/120.
+        // This is a way to cover ff02::1 and ff02::2 with a single JNEBS.
         // TODO: Drop only if they don't contain the address of on-link neighbours.
+        final byte[] unsolicitedNaDropPrefix = Arrays.copyOf(IPV6_ALL_NODES_ADDRESS, 15);
         gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET);
-        gen.addJumpIfBytesNotEqual(Register.R0, IPV6_ALL_NODES_ADDRESS,
+        gen.addJumpIfBytesNotEqual(Register.R0, unsolicitedNaDropPrefix,
                 skipUnsolicitedMulticastNALabel);
+
         maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA);
         gen.addJump(mCountAndDropLabel);
         gen.defineLabel(skipUnsolicitedMulticastNALabel);
@@ -1493,7 +1495,7 @@
      * <li>Drop all broadcast non-IP non-ARP packets.
      * <li>Pass all non-ICMPv6 IPv6 packets,
      * <li>Pass all non-IPv4 and non-IPv6 packets,
-     * <li>Drop IPv6 ICMPv6 NAs to ff02::1.
+     * <li>Drop IPv6 ICMPv6 NAs to anything in ff02::/120.
      * <li>Drop IPv6 ICMPv6 RSs.
      * <li>Filter IPv4 packets (see generateIPv4FilterLocked())
      * <li>Filter IPv6 packets (see generateIPv6FilterLocked())
@@ -1569,7 +1571,7 @@
         // Drop non-IP non-ARP broadcasts, pass the rest
         gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
         maybeSetupCounter(gen, Counter.PASSED_NON_IP_UNICAST);
-        gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel);
+        gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel);
         maybeSetupCounter(gen, Counter.DROPPED_ETH_BROADCAST);
         gen.addJump(mCountAndDropLabel);
 
diff --git a/src/android/net/apf/ApfGenerator.java b/src/android/net/apf/ApfGenerator.java
index 44ce2db..bf4d910 100644
--- a/src/android/net/apf/ApfGenerator.java
+++ b/src/android/net/apf/ApfGenerator.java
@@ -752,7 +752,8 @@
 
     /**
      * Add an instruction to the end of the program to jump to {@code target} if the bytes of the
-     * packet at an offset specified by {@code register} match {@code bytes}.
+     * packet at an offset specified by {@code register} don't match {@code bytes}, {@code register}
+     * must be R0.
      */
     public ApfGenerator addJumpIfBytesNotEqual(Register register, byte[] bytes, String target)
             throws IllegalInstructionException {
diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java
index 5f77128..8e0e9d3 100644
--- a/src/android/net/dhcp/DhcpClient.java
+++ b/src/android/net/dhcp/DhcpClient.java
@@ -97,9 +97,9 @@
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
-import com.android.internal.util.TrafficStatsConstants;
 import com.android.internal.util.WakeupMessage;
 import com.android.net.module.util.DeviceConfigUtils;
+import com.android.net.module.util.NetworkStackConstants;
 import com.android.net.module.util.PacketReader;
 import com.android.networkstack.R;
 import com.android.networkstack.apishim.CaptivePortalDataShimImpl;
@@ -611,7 +611,7 @@
 
     private boolean initUdpSocket() {
         final int oldTag = TrafficStats.getAndSetThreadStatsTag(
-                TrafficStatsConstants.TAG_SYSTEM_DHCP);
+                NetworkStackConstants.TAG_SYSTEM_DHCP);
         try {
             mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
             SocketUtils.bindSocketToInterface(mUdpSock, mIfaceName);
diff --git a/src/android/net/dhcp/DhcpPacket.java b/src/android/net/dhcp/DhcpPacket.java
index f70f3ec..76dc807 100644
--- a/src/android/net/dhcp/DhcpPacket.java
+++ b/src/android/net/dhcp/DhcpPacket.java
@@ -200,7 +200,8 @@
      * DHCP Optional Type: DHCP Interface MTU
      */
     public static final byte DHCP_MTU = 26;
-    protected Short mMtu;
+    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+    public Short mMtu;
 
     /**
      * DHCP Optional Type: DHCP BROADCAST ADDRESS
@@ -402,7 +403,7 @@
 
     // Set in unit tests, to ensure that the test does not break when run on different devices and
     // on different releases.
-    static String testOverrideVendorId = null;
+    static String sTestOverrideVendorId = null;
 
     protected DhcpPacket(int transId, short secs, Inet4Address clientIp, Inet4Address yourIp,
                          Inet4Address nextIp, Inet4Address relayIp,
@@ -778,7 +779,7 @@
      * with the customized option value if any.
      */
     private static String getVendorId(@Nullable List<DhcpOption> customizedClientOptions) {
-        if (testOverrideVendorId != null) return testOverrideVendorId;
+        if (sTestOverrideVendorId != null) return sTestOverrideVendorId;
 
         String vendorId = "android-dhcp-" + Build.VERSION.RELEASE;
         if (customizedClientOptions != null) {
diff --git a/src/android/net/dhcp/DhcpServer.java b/src/android/net/dhcp/DhcpServer.java
index 3acd76e..3465e72 100644
--- a/src/android/net/dhcp/DhcpServer.java
+++ b/src/android/net/dhcp/DhcpServer.java
@@ -33,12 +33,12 @@
 import static android.system.OsConstants.SO_BROADCAST;
 import static android.system.OsConstants.SO_REUSEADDR;
 
-import static com.android.internal.util.TrafficStatsConstants.TAG_SYSTEM_DHCP_SERVER;
 import static com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress;
 import static com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address;
 import static com.android.net.module.util.NetworkStackConstants.INFINITE_LEASE;
 import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ALL;
 import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY;
+import static com.android.net.module.util.NetworkStackConstants.TAG_SYSTEM_DHCP_SERVER;
 import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission;
 
 import static java.lang.Integer.toUnsignedLong;
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index a73e997..12ab3fd 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -18,12 +18,28 @@
 
 import static android.net.RouteInfo.RTN_UNICAST;
 import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable;
+import static android.net.ip.IIpClient.PROV_IPV4_DISABLED;
+import static android.net.ip.IIpClient.PROV_IPV6_DISABLED;
+import static android.net.ip.IIpClient.PROV_IPV6_LINKLOCAL;
+import static android.net.util.NetworkStackUtils.IPCLIENT_DISABLE_ACCEPT_RA_VERSION;
+import static android.net.util.NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION;
+import static android.net.util.NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION;
+import static android.net.util.SocketUtils.makePacketSocketAddress;
 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
+import static android.system.OsConstants.AF_PACKET;
+import static android.system.OsConstants.ETH_P_ARP;
+import static android.system.OsConstants.ETH_P_IPV6;
+import static android.system.OsConstants.SOCK_NONBLOCK;
+import static android.system.OsConstants.SOCK_RAW;
 
+import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
 import static com.android.net.module.util.NetworkStackConstants.VENDOR_SPECIFIC_IE_ID;
 import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.DhcpResults;
 import android.net.INetd;
@@ -48,19 +64,25 @@
 import android.net.metrics.IpManagerEvent;
 import android.net.networkstack.aidl.dhcp.DhcpOption;
 import android.net.shared.InitialConfiguration;
+import android.net.shared.Layer2Information;
 import android.net.shared.ProvisioningConfiguration;
 import android.net.shared.ProvisioningConfiguration.ScanResultInfo;
 import android.net.shared.ProvisioningConfiguration.ScanResultInfo.InformationElement;
 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.Handler;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.os.SystemClock;
 import android.stats.connectivity.DisconnectCode;
+import android.stats.connectivity.NetworkQuirkEvent;
+import android.system.ErrnoException;
+import android.system.Os;
 import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.Log;
@@ -78,17 +100,26 @@
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.WakeupMessage;
 import com.android.net.module.util.DeviceConfigUtils;
+import com.android.networkstack.R;
 import com.android.networkstack.apishim.NetworkInformationShimImpl;
+import com.android.networkstack.apishim.SocketUtilsShimImpl;
 import com.android.networkstack.apishim.common.NetworkInformationShim;
 import com.android.networkstack.apishim.common.ShimUtils;
+import com.android.networkstack.arp.ArpPacket;
 import com.android.networkstack.metrics.IpProvisioningMetrics;
+import com.android.networkstack.metrics.NetworkQuirkMetrics;
+import com.android.networkstack.packets.NeighborAdvertisement;
 import com.android.server.NetworkObserverRegistry;
 import com.android.server.NetworkStackService.NetworkStackServiceManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.MalformedURLException;
+import java.net.SocketAddress;
+import java.net.SocketException;
 import java.net.URL;
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
@@ -121,6 +152,7 @@
  * @hide
  */
 public class IpClient extends StateMachine {
+    private static final String TAG = IpClient.class.getSimpleName();
     private static final boolean DBG = false;
 
     // For message logging.
@@ -134,6 +166,7 @@
     private final NetworkStackIpMemoryStore mIpMemoryStore;
     private final NetworkInformationShim mShim = NetworkInformationShimImpl.newInstance();
     private final IpProvisioningMetrics mIpProvisioningMetrics = new IpProvisioningMetrics();
+    private final NetworkQuirkMetrics mNetworkQuirkMetrics;
 
     /**
      * Dump all state machine and connectivity packet logs to the specified writer.
@@ -367,6 +400,24 @@
                 log("Failed to call onPreconnectionStart", e);
             }
         }
+
+        /**
+         * Get the version of the IIpClientCallbacks AIDL interface.
+         */
+        public int getInterfaceVersion() {
+            log("getInterfaceVersion");
+            try {
+                return mCallback.getInterfaceVersion();
+            } catch (RemoteException e) {
+                // This can never happen for callers in the system server, because if the
+                // system server crashes, then the networkstack will crash as well. But it can
+                // happen for other callers such as bluetooth or telephony (if it starts to use
+                // IpClient). 0 will generally work but will assume an old client and disable
+                // all new features.
+                log("Failed to call getInterfaceVersion", e);
+                return 0;
+            }
+        }
     }
 
     public static final String DUMP_ARG_CONFIRM = "confirm";
@@ -479,6 +530,8 @@
     private final MessageHandlingLogger mMsgStateLogger;
     private final IpConnectivityLog mMetricsLog;
     private final InterfaceController mInterfaceCtrl;
+    // Set of IPv6 addresses for which unsolicited gratuitous NA packets have been sent.
+    private final Set<Inet6Address> mGratuitousNaTargetAddresses = new HashSet<>();
 
     // Ignore nonzero RDNSS option lifetimes below this value. 0 = disabled.
     private final int mMinRdnssLifetimeSec;
@@ -501,7 +554,7 @@
     private boolean mMulticastFiltering;
     private long mStartTimeMillis;
     private MacAddress mCurrentBssid;
-    private boolean mHasDisabledIPv6OnProvLoss;
+    private boolean mHasDisabledIpv6OrAcceptRaOnProvLoss;
 
     /**
      * Reading the snapshot is an asynchronous operation initiated by invoking
@@ -564,6 +617,52 @@
         public IpConnectivityLog getIpConnectivityLog() {
             return new IpConnectivityLog();
         }
+
+        /**
+         * Get a NetworkQuirkMetrics instance.
+         */
+        public NetworkQuirkMetrics getNetworkQuirkMetrics() {
+            return new NetworkQuirkMetrics();
+        }
+
+        /**
+         * Get a IpReachabilityMonitor instance.
+         */
+        public IpReachabilityMonitor getIpReachabilityMonitor(Context context,
+                InterfaceParams ifParams, Handler h, SharedLog log,
+                IpReachabilityMonitor.Callback callback, boolean usingMultinetworkPolicyTracker,
+                IpReachabilityMonitor.Dependencies deps, final INetd netd) {
+            return new IpReachabilityMonitor(context, ifParams, h, log, callback,
+                    usingMultinetworkPolicyTracker, deps, netd);
+        }
+
+        /**
+         * Get a IpReachabilityMonitor dependencies instance.
+         */
+        public IpReachabilityMonitor.Dependencies getIpReachabilityMonitorDeps(Context context,
+                String name) {
+            return IpReachabilityMonitor.Dependencies.makeDefault(context, name);
+        }
+
+        /**
+         * Return whether a feature guarded by a feature flag is enabled.
+         * @see NetworkStackUtils#isFeatureEnabled(Context, String, String)
+         */
+        public boolean isFeatureEnabled(final Context context, final String name,
+                boolean defaultEnabled) {
+            return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name,
+                    defaultEnabled);
+        }
+
+        /**
+         * Create an APF filter if apfCapabilities indicates support for packet filtering using
+         * APF programs.
+         * @see ApfFilter#maybeCreate
+         */
+        public ApfFilter maybeCreateApfFilter(Context context, ApfFilter.ApfConfiguration config,
+                InterfaceParams ifParams, IpClientCallbacksWrapper cb) {
+            return ApfFilter.maybeCreate(context, config, ifParams, cb);
+        }
     }
 
     public IpClient(Context context, String ifName, IIpClientCallbacks callback,
@@ -586,6 +685,7 @@
         mClatInterfaceName = CLAT_PREFIX + ifName;
         mDependencies = deps;
         mMetricsLog = deps.getIpConnectivityLog();
+        mNetworkQuirkMetrics = deps.getNetworkQuirkMetrics();
         mShutdownLatch = new CountDownLatch(1);
         mCm = mContext.getSystemService(ConnectivityManager.class);
         mObserverRegistry = observerRegistry;
@@ -615,7 +715,7 @@
                 (ifaceUp) -> sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED, ifaceUp
                         ? ARG_LINKPROP_CHANGED_LINKSTATE_UP
                         : ARG_LINKPROP_CHANGED_LINKSTATE_DOWN),
-                config, mLog) {
+                config, mLog, mDependencies) {
             @Override
             public void onInterfaceAdded(String iface) {
                 super.onInterfaceAdded(iface);
@@ -647,6 +747,21 @@
                 logMsg(msg);
             }
 
+            @Override
+            public void onInterfaceAddressRemoved(LinkAddress address, String iface) {
+                super.onInterfaceAddressRemoved(address, iface);
+                if (!mInterfaceName.equals(iface)) return;
+                if (!address.isIpv6()) return;
+                final Inet6Address targetIp = (Inet6Address) address.getAddress();
+                if (mGratuitousNaTargetAddresses.contains(targetIp)) {
+                    mGratuitousNaTargetAddresses.remove(targetIp);
+
+                    final String msg = "Global IPv6 address: " + targetIp
+                            + " has removed from the set of gratuitous NA target address.";
+                    logMsg(msg);
+                }
+            }
+
             private void logMsg(String msg) {
                 Log.d(mTag, msg);
                 getHandler().post(() -> mLog.log("OBSERVED " + msg));
@@ -701,7 +816,8 @@
         @Override
         public void startProvisioning(ProvisioningConfigurationParcelable req) {
             enforceNetworkStackCallingPermission();
-            IpClient.this.startProvisioning(ProvisioningConfiguration.fromStableParcelable(req));
+            IpClient.this.startProvisioning(ProvisioningConfiguration.fromStableParcelable(req,
+                    mCallback.getInterfaceVersion()));
         }
         @Override
         public void stop() {
@@ -790,9 +906,49 @@
 
     private void stopStateMachineUpdaters() {
         mObserverRegistry.unregisterObserver(mLinkObserver);
+        mLinkObserver.clearInterfaceParams();
         mLinkObserver.shutdown();
     }
 
+    private boolean isGratuitousNaEnabled() {
+        return mDependencies.isFeatureEnabled(mContext, IPCLIENT_GRATUITOUS_NA_VERSION,
+                false /* defaultEnabled */);
+    }
+
+    private boolean isGratuitousArpNaRoamingEnabled() {
+        return mDependencies.isFeatureEnabled(mContext, IPCLIENT_GARP_NA_ROAMING_VERSION,
+                false /* defaultEnabled */);
+    }
+
+    @VisibleForTesting
+    static MacAddress getInitialBssid(final Layer2Information layer2Info,
+            final ScanResultInfo scanResultInfo, boolean isAtLeastS) {
+        MacAddress bssid = null;
+        // http://b/185202634
+        // ScanResultInfo is not populated in some situations.
+        // On S and above, prefer getting the BSSID from the Layer2Info.
+        // On R and below, get the BSSID from the ScanResultInfo and fall back to
+        // getting it from the Layer2Info. This ensures no regressions if any R
+        // devices pass in a null or meaningless BSSID in the Layer2Info.
+        if (!isAtLeastS && scanResultInfo != null) {
+            try {
+                bssid = MacAddress.fromString(scanResultInfo.getBssid());
+            } catch (IllegalArgumentException e) {
+                Log.wtf(TAG, "Invalid BSSID: " + scanResultInfo.getBssid()
+                        + " in provisioning configuration", e);
+            }
+        }
+        if (bssid == null && layer2Info != null) {
+            bssid = layer2Info.mBssid;
+        }
+        return bssid;
+    }
+
+    private boolean shouldDisableAcceptRaOnProvisioningLoss() {
+        return mDependencies.isFeatureEnabled(mContext, IPCLIENT_DISABLE_ACCEPT_RA_VERSION,
+                true /* defaultEnabled */);
+    }
+
     @Override
     protected void onQuitting() {
         mCallback.onQuit();
@@ -816,17 +972,8 @@
             return;
         }
 
-        final ScanResultInfo scanResultInfo = req.mScanResultInfo;
-        mCurrentBssid = null;
-        if (scanResultInfo != null) {
-            try {
-                mCurrentBssid = MacAddress.fromString(scanResultInfo.getBssid());
-            } catch (IllegalArgumentException e) {
-                Log.wtf(mTag, "Invalid BSSID: " + scanResultInfo.getBssid()
-                        + " in provisioning configuration", e);
-            }
-        }
-
+        mCurrentBssid = getInitialBssid(req.mLayer2Info, req.mScanResultInfo,
+                ShimUtils.isAtLeastS());
         if (req.mLayer2Info != null) {
             mL2Key = req.mLayer2Info.mL2Key;
             mCluster = req.mLayer2Info.mCluster;
@@ -1103,12 +1250,41 @@
         transitionTo(mStoppingState);
     }
 
+    private static boolean hasIpv6LinkLocalInterfaceRoute(final LinkProperties lp) {
+        for (RouteInfo r : lp.getRoutes()) {
+            if (r.getDestination().equals(new IpPrefix("fe80::/64"))
+                    && r.getGateway().isAnyLocalAddress()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean hasIpv6LinkLocalAddress(final LinkProperties lp) {
+        for (LinkAddress address : lp.getLinkAddresses()) {
+            if (address.isIpv6() && address.getAddress().isLinkLocalAddress()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // LinkProperties has a link-local (fe80::xxx) IPv6 address and route to fe80::/64 destination.
+    private boolean isIpv6LinkLocalProvisioned(final LinkProperties lp) {
+        if (mConfiguration == null
+                || mConfiguration.mIPv6ProvisioningMode != PROV_IPV6_LINKLOCAL) return false;
+        if (hasIpv6LinkLocalAddress(lp) && hasIpv6LinkLocalInterfaceRoute(lp)) return true;
+        return false;
+    }
+
     // For now: use WifiStateMachine's historical notion of provisioned.
     @VisibleForTesting
-    static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
-        // For historical reasons, we should connect even if all we have is
-        // an IPv4 address and nothing else.
-        if (lp.hasIpv4Address() || lp.isProvisioned()) {
+    boolean isProvisioned(final LinkProperties lp, final InitialConfiguration config) {
+        // For historical reasons, we should connect even if all we have is an IPv4
+        // address and nothing else. If IPv6 link-local only mode is enabled and
+        // it's provisioned without IPv4, then still connecting once IPv6 link-local
+        // address is ready to use and route to fe80::/64 destination is up.
+        if (lp.hasIpv4Address() || lp.isProvisioned() || isIpv6LinkLocalProvisioned(lp)) {
             return true;
         }
         if (config == null) {
@@ -1120,6 +1296,21 @@
         return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
     }
 
+    private void setIpv6AcceptRa(int acceptRa) {
+        try {
+            mNetd.setProcSysNet(INetd.IPV6, INetd.CONF, mInterfaceParams.name, "accept_ra",
+                    Integer.toString(acceptRa));
+        } catch (Exception e) {
+            Log.e(mTag, "Failed to set accept_ra to " + acceptRa);
+        }
+    }
+
+    private void restartIpv6WithAcceptRaDisabled() {
+        mInterfaceCtrl.disableIPv6();
+        setIpv6AcceptRa(0 /* accept_ra */);
+        startIPv6();
+    }
+
     // TODO: Investigate folding all this into the existing static function
     // LinkProperties.compareProvisioning() or some other single function that
     // takes two LinkProperties objects and returns a ProvisioningChange
@@ -1169,7 +1360,7 @@
         // Note that we can still be disconnected by IpReachabilityMonitor
         // if the IPv6 default gateway (but not the IPv6 DNS servers; see
         // accompanying code in IpReachabilityMonitor) is unreachable.
-        final boolean ignoreIPv6ProvisioningLoss = mHasDisabledIPv6OnProvLoss
+        final boolean ignoreIPv6ProvisioningLoss = mHasDisabledIpv6OrAcceptRaOnProvLoss
                 || (mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker
                         && !mCm.shouldAvoidBadWifi());
 
@@ -1197,18 +1388,31 @@
         if (oldLp.hasGlobalIpv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
             // Although link properties have lost IPv6 default route in this case, if IPv4 is still
             // working with appropriate routes and DNS servers, we can keep the current connection
-            // without disconnecting from the network, just disable IPv6 on that given network until
-            // to the next provisioning. Disabling IPv6 will result in all IPv6 connectivity torn
-            // down and all IPv6 sockets being closed, the non-routable IPv6 DNS servers will be
-            // stripped out, so applications will be able to reconnect immediately over IPv4. See
-            // b/131781810.
+            // without disconnecting from the network, just disable IPv6 or accept_ra parameter on
+            // that given network until to the next provisioning.
+            //
+            // Disabling IPv6 stack will result in all IPv6 connectivity torn down and all IPv6
+            // sockets being closed, the non-routable IPv6 DNS servers will be stripped out, so
+            // applications will be able to reconnect immediately over IPv4. See b/131781810.
+            //
+            // Sometimes disabling IPv6 stack might introduce other issues(see b/179222860),
+            // instead disabling accept_ra will result in only IPv4 provisioning and IPv6 link
+            // local address left on the interface, so applications will be able to reconnect
+            // immediately over IPv4 and keep IPv6 link-local capable.
             if (newLp.isIpv4Provisioned()) {
-                mInterfaceCtrl.disableIPv6();
-                mHasDisabledIPv6OnProvLoss = true;
-                delta = PROV_CHANGE_STILL_PROVISIONED;
-                if (DBG) {
-                    mLog.log("Disable IPv6 stack completely when the default router has gone");
+                if (shouldDisableAcceptRaOnProvisioningLoss()) {
+                    restartIpv6WithAcceptRaDisabled();
+                } else {
+                    mInterfaceCtrl.disableIPv6();
                 }
+                mNetworkQuirkMetrics.setEvent(NetworkQuirkEvent.QE_IPV6_PROVISIONING_ROUTER_LOST);
+                mNetworkQuirkMetrics.statsWrite();
+                mHasDisabledIpv6OrAcceptRaOnProvLoss = true;
+                delta = PROV_CHANGE_STILL_PROVISIONED;
+                mLog.log(shouldDisableAcceptRaOnProvisioningLoss()
+                        ? "Disabled accept_ra parameter "
+                        : "Disabled IPv6 stack completely "
+                        + "when the IPv6 default router has gone");
             } else {
                 delta = PROV_CHANGE_LOST_PROVISIONING;
             }
@@ -1377,6 +1581,92 @@
         }
     }
 
+    private void transmitPacket(final ByteBuffer packet, final SocketAddress sockAddress,
+            final String msg) {
+        FileDescriptor sock = null;
+        try {
+            sock = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0 /* protocol */);
+            Os.sendto(sock, packet.array(), 0 /* byteOffset */, packet.limit() /* byteCount */,
+                    0 /* flags */, sockAddress);
+        } catch (SocketException | ErrnoException e) {
+            logError(msg, e);
+        } finally {
+            NetworkStackUtils.closeSocketQuietly(sock);
+        }
+    }
+
+    private void sendGratuitousNA(final Inet6Address srcIp, final Inet6Address targetIp) {
+        final int flags = 0; // R=0, S=0, O=0
+        final Inet6Address dstIp = IPV6_ADDR_ALL_ROUTERS_MULTICAST;
+        // Ethernet multicast destination address: 33:33:00:00:00:02.
+        final MacAddress dstMac = NetworkStackUtils.ipv6MulticastToEthernetMulticast(dstIp);
+        final ByteBuffer packet = NeighborAdvertisement.build(mInterfaceParams.macAddr, dstMac,
+                srcIp, dstIp, flags, targetIp);
+        final SocketAddress sockAddress =
+                SocketUtilsShimImpl.newInstance().makePacketSocketAddress(ETH_P_IPV6,
+                        mInterfaceParams.index, dstMac.toByteArray());
+
+        transmitPacket(packet, sockAddress, "Failed to send Gratuitous Neighbor Advertisement");
+    }
+
+    private void sendGratuitousARP(final Inet4Address srcIp) {
+        final ByteBuffer packet = ArpPacket.buildArpPacket(ETHER_BROADCAST /* dstMac */,
+                mInterfaceParams.macAddr.toByteArray() /* srcMac */,
+                srcIp.getAddress() /* targetIp */,
+                ETHER_BROADCAST /* targetHwAddress */,
+                srcIp.getAddress() /* senderIp */, (short) ARP_REPLY);
+        final SocketAddress sockAddress =
+                makePacketSocketAddress(ETH_P_ARP, mInterfaceParams.index);
+
+        transmitPacket(packet, sockAddress, "Failed to send GARP");
+    }
+
+    private static Inet6Address getIpv6LinkLocalAddress(final LinkProperties newLp) {
+        for (LinkAddress la : newLp.getLinkAddresses()) {
+            if (!la.isIpv6()) continue;
+            final Inet6Address ip = (Inet6Address) la.getAddress();
+            if (ip.isLinkLocalAddress()) return ip;
+        }
+        return null;
+    }
+
+    private void maybeSendGratuitousNAs(final LinkProperties lp, boolean afterRoaming) {
+        if (!lp.hasGlobalIpv6Address()) return;
+
+        final Inet6Address srcIp = getIpv6LinkLocalAddress(lp);
+        if (srcIp == null) return;
+
+        // TODO: add experiment with sending only one gratuitous NA packet instead of one
+        // packet per address.
+        for (LinkAddress la : lp.getLinkAddresses()) {
+            if (!la.isIpv6() || !la.isGlobalPreferred()) continue;
+            final Inet6Address targetIp = (Inet6Address) la.getAddress();
+            // Already sent gratuitous NA with this target global IPv6 address. But for
+            // the L2 roaming case, device should always (re)transmit Gratuitous NA for
+            // each IPv6 global unicast address respectively after roaming.
+            if (!afterRoaming && mGratuitousNaTargetAddresses.contains(targetIp)) continue;
+            if (DBG) {
+                mLog.log("send Gratuitous NA from " + srcIp.getHostAddress() + " for "
+                        + targetIp.getHostAddress() + (afterRoaming ? " after roaming" : ""));
+            }
+            sendGratuitousNA(srcIp, targetIp);
+            if (!afterRoaming) mGratuitousNaTargetAddresses.add(targetIp);
+        }
+    }
+
+    private void maybeSendGratuitousARP(final LinkProperties lp) {
+        for (LinkAddress address : lp.getLinkAddresses()) {
+            if (address.getAddress() instanceof Inet4Address) {
+                final Inet4Address srcIp = (Inet4Address) address.getAddress();
+                if (DBG) {
+                    mLog.log("send GARP for " + srcIp.getHostAddress() + " HW address: "
+                            + mInterfaceParams.macAddr);
+                }
+                sendGratuitousARP(srcIp);
+            }
+        }
+    }
+
     // Returns false if we have lost provisioning, true otherwise.
     private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
         final LinkProperties newLp = assembleLinkProperties();
@@ -1384,6 +1674,13 @@
             return true;
         }
 
+        // Check if new assigned IPv6 GUA is available in the LinkProperties now. If so, initiate
+        // gratuitous multicast unsolicited Neighbor Advertisements as soon as possible to inform
+        // first-hop routers that the new GUA host is goning to use.
+        if (isGratuitousNaEnabled()) {
+            maybeSendGratuitousNAs(newLp, false /* isGratuitousNaAfterRoaming */);
+        }
+
         // Either success IPv4 or IPv6 provisioning triggers new LinkProperties update,
         // wait for the provisioning completion and record the latency.
         mIpProvisioningMetrics.setIPv4ProvisionedLatencyOnFirstTime(newLp.isIpv4Provisioned());
@@ -1554,7 +1851,7 @@
 
     private boolean startIpReachabilityMonitor() {
         try {
-            mIpReachabilityMonitor = new IpReachabilityMonitor(
+            mIpReachabilityMonitor = mDependencies.getIpReachabilityMonitor(
                     mContext,
                     mInterfaceParams,
                     getHandler(),
@@ -1566,6 +1863,7 @@
                         }
                     },
                     mConfiguration.mUsingMultinetworkPolicyTracker,
+                    mDependencies.getIpReachabilityMonitorDeps(mContext, mInterfaceParams.name),
                     mNetd);
         } catch (IllegalArgumentException iae) {
             // Failed to start IpReachabilityMonitor. Log it and call
@@ -1639,8 +1937,15 @@
         // If the BSSID has not changed, there is nothing to do.
         if (info.bssid.equals(mCurrentBssid)) return;
 
+        // Before trigger probing to the interesting neighbors, send Gratuitous ARP
+        // and Neighbor Advertisment in advance to propgate host's IPv4/v6 addresses.
+        if (isGratuitousArpNaRoamingEnabled()) {
+            maybeSendGratuitousARP(mLinkProperties);
+            maybeSendGratuitousNAs(mLinkProperties, true /* isGratuitousNaAfterRoaming */);
+        }
+
         if (mIpReachabilityMonitor != null) {
-            mIpReachabilityMonitor.probeAll();
+            mIpReachabilityMonitor.probeAll(true /* dueToRoam */);
         }
 
         // Check whether to refresh previous IP lease on L2 roaming happened.
@@ -1661,9 +1966,10 @@
         @Override
         public void enter() {
             stopAllIP();
-            mHasDisabledIPv6OnProvLoss = false;
+            setIpv6AcceptRa(2 /* accept_ra */);
+            mHasDisabledIpv6OrAcceptRaOnProvLoss = false;
+            mGratuitousNaTargetAddresses.clear();
 
-            mLinkObserver.clearInterfaceParams();
             resetLinkProperties();
             if (mStartTimeMillis > 0) {
                 // Completed a life-cycle; send a final empty LinkProperties
@@ -1735,6 +2041,9 @@
             if (mDhcpClient == null) {
                 // There's no DHCPv4 for which to wait; proceed to stopped.
                 deferMessage(obtainMessage(CMD_JUMP_STOPPING_TO_STOPPED));
+            } else {
+                mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
+                mDhcpClient.doQuit();
             }
 
             // Restore the interface MTU to initial value if it has changed.
@@ -1971,6 +2280,14 @@
         }
     }
 
+    private boolean isIpv6Enabled() {
+        return mConfiguration.mIPv6ProvisioningMode != PROV_IPV6_DISABLED;
+    }
+
+    private boolean isIpv4Enabled() {
+        return mConfiguration.mIPv4ProvisioningMode != PROV_IPV4_DISABLED;
+    }
+
     class RunningState extends State {
         private ConnectivityPacketTracker mPacketTracker;
         private boolean mDhcpActionInFlight;
@@ -1981,10 +2298,19 @@
             apfConfig.apfCapabilities = mConfiguration.mApfCapabilities;
             apfConfig.multicastFilter = mMulticastFiltering;
             // Get the Configuration for ApfFilter from Context
-            apfConfig.ieee802_3Filter = ApfCapabilities.getApfDrop8023Frames();
-            apfConfig.ethTypeBlackList = ApfCapabilities.getApfEtherTypeBlackList();
+            // Resource settings were moved from ApfCapabilities APIs to NetworkStack resources in S
+            if (ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.R)) {
+                final Resources res = mContext.getResources();
+                apfConfig.ieee802_3Filter = res.getBoolean(R.bool.config_apfDrop802_3Frames);
+                apfConfig.ethTypeBlackList = res.getIntArray(R.array.config_apfEthTypeDenyList);
+            } else {
+                apfConfig.ieee802_3Filter = ApfCapabilities.getApfDrop8023Frames();
+                apfConfig.ethTypeBlackList = ApfCapabilities.getApfEtherTypeBlackList();
+            }
+
             apfConfig.minRdnssLifetimeSec = mMinRdnssLifetimeSec;
-            mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback);
+            mApfFilter = mDependencies.maybeCreateApfFilter(mContext, apfConfig, mInterfaceParams,
+                    mCallback);
             // TODO: investigate the effects of any multicast filtering racing/interfering with the
             // rest of this IP configuration startup.
             if (mApfFilter == null) {
@@ -1994,13 +2320,13 @@
             mPacketTracker = createPacketTracker();
             if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName);
 
-            if (mConfiguration.mEnableIPv6 && !startIPv6()) {
+            if (isIpv6Enabled() && !startIPv6()) {
                 doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
                 enqueueJumpToStoppingState(DisconnectCode.DC_ERROR_STARTING_IPV6);
                 return;
             }
 
-            if (mConfiguration.mEnableIPv4 && !isUsingPreconnection() && !startIPv4()) {
+            if (isIpv4Enabled() && !isUsingPreconnection() && !startIPv4()) {
                 doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
                 enqueueJumpToStoppingState(DisconnectCode.DC_ERROR_STARTING_IPV4);
                 return;
@@ -2031,11 +2357,6 @@
                 mIpReachabilityMonitor = null;
             }
 
-            if (mDhcpClient != null) {
-                mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
-                mDhcpClient.doQuit();
-            }
-
             if (mPacketTracker != null) {
                 mPacketTracker.stop();
                 mPacketTracker = null;
@@ -2098,7 +2419,7 @@
                     // a DHCPv4 RENEW.  We used to do this on Wi-Fi framework
                     // roams.
                     if (mIpReachabilityMonitor != null) {
-                        mIpReachabilityMonitor.probeAll();
+                        mIpReachabilityMonitor.probeAll(false /* dueToRoam */);
                     }
                     break;
 
diff --git a/src/android/net/ip/IpClientLinkObserver.java b/src/android/net/ip/IpClientLinkObserver.java
index cc4a851..ff0aafe 100644
--- a/src/android/net/ip/IpClientLinkObserver.java
+++ b/src/android/net/ip/IpClientLinkObserver.java
@@ -16,6 +16,7 @@
 
 package android.net.ip;
 
+import static android.net.util.NetworkStackUtils.IPCLIENT_PARSE_NETLINK_EVENTS_VERSION;
 import static android.system.OsConstants.AF_INET6;
 
 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
@@ -27,16 +28,17 @@
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.RouteInfo;
-import android.net.netlink.NduseroptMessage;
-import android.net.netlink.NetlinkConstants;
-import android.net.netlink.NetlinkMessage;
-import android.net.netlink.StructNdOptPref64;
 import android.net.util.InterfaceParams;
 import android.net.util.SharedLog;
 import android.os.Handler;
 import android.system.OsConstants;
 import android.util.Log;
 
+import com.android.net.module.util.netlink.NduseroptMessage;
+import com.android.net.module.util.netlink.NetlinkConstants;
+import com.android.net.module.util.netlink.NetlinkMessage;
+import com.android.net.module.util.netlink.StructNdOptPref64;
+import com.android.net.module.util.netlink.StructNdOptRdnss;
 import com.android.networkstack.apishim.NetworkInformationShimImpl;
 import com.android.networkstack.apishim.common.NetworkInformationShim;
 import com.android.server.NetworkObserver;
@@ -107,6 +109,7 @@
         }
     }
 
+    private final Context mContext;
     private final String mInterfaceName;
     private final Callback mCallback;
     private final LinkProperties mLinkProperties;
@@ -115,13 +118,15 @@
     private final AlarmManager mAlarmManager;
     private final Configuration mConfig;
     private final Handler mHandler;
+    private final IpClient.Dependencies mDependencies;
 
     private final MyNetlinkMonitor mNetlinkMonitor;
 
     private static final boolean DBG = false;
 
     public IpClientLinkObserver(Context context, Handler h, String iface, Callback callback,
-            Configuration config, SharedLog log) {
+            Configuration config, SharedLog log, IpClient.Dependencies deps) {
+        mContext = context;
         mInterfaceName = iface;
         mTag = "NetlinkTracker/" + mInterfaceName;
         mCallback = callback;
@@ -134,6 +139,7 @@
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         mNetlinkMonitor = new MyNetlinkMonitor(h, log, mTag);
         mHandler.post(mNetlinkMonitor::start);
+        mDependencies = deps;
     }
 
     public void shutdown() {
@@ -153,6 +159,11 @@
         }
     }
 
+    private boolean isNetlinkEventParsingEnabled() {
+        return mDependencies.isFeatureEnabled(mContext, IPCLIENT_PARSE_NETLINK_EVENTS_VERSION,
+                false /* default value */);
+    }
+
     @Override
     public void onInterfaceRemoved(String iface) {
         maybeLog("interfaceRemoved", iface);
@@ -246,17 +257,21 @@
 
     @Override
     public void onInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) {
-        if (mInterfaceName.equals(iface)) {
-            maybeLog("interfaceDnsServerInfo", Arrays.toString(addresses));
-            final boolean changed = mDnsServerRepository.addServers(lifetime, addresses);
-            final boolean linkState;
-            if (changed) {
-                synchronized (this) {
-                    mDnsServerRepository.setDnsServersOn(mLinkProperties);
-                    linkState = getInterfaceLinkStateLocked();
-                }
-                mCallback.update(linkState);
+        if (isNetlinkEventParsingEnabled()) return;
+        if (!mInterfaceName.equals(iface)) return;
+        maybeLog("interfaceDnsServerInfo", Arrays.toString(addresses));
+        updateInterfaceDnsServerInfo(lifetime, addresses);
+    }
+
+    private void updateInterfaceDnsServerInfo(long lifetime, final String[] addresses) {
+        final boolean changed = mDnsServerRepository.addServers(lifetime, addresses);
+        final boolean linkState;
+        if (changed) {
+            synchronized (this) {
+                mDnsServerRepository.setDnsServersOn(mLinkProperties);
+                linkState = getInterfaceLinkStateLocked();
             }
+            mCallback.update(linkState);
         }
     }
 
@@ -408,6 +423,15 @@
             updatePref64(opt.prefix, now, expiry);
         }
 
+        private void processRdnssOption(StructNdOptRdnss opt) {
+            if (!isNetlinkEventParsingEnabled()) return;
+            final String[] addresses = new String[opt.servers.length];
+            for (int i = 0; i < opt.servers.length; i++) {
+                addresses[i] = opt.servers[i].getHostAddress();
+            }
+            updateInterfaceDnsServerInfo(opt.header.lifetime, addresses);
+        }
+
         private void processNduseroptMessage(NduseroptMessage msg, final long whenMs) {
             if (msg.family != AF_INET6 || msg.option == null || msg.ifindex != mIfindex) return;
             if (msg.icmp_type != (byte) ICMPV6_ROUTER_ADVERTISEMENT) return;
@@ -417,8 +441,12 @@
                     processPref64Option((StructNdOptPref64) msg.option, whenMs);
                     break;
 
+                case StructNdOptRdnss.TYPE:
+                    processRdnssOption((StructNdOptRdnss) msg.option);
+                    break;
+
                 default:
-                    // TODO: implement RDNSS and DNSSL.
+                    // TODO: implement DNSSL.
                     break;
             }
         }
diff --git a/src/android/net/ip/IpReachabilityMonitor.java b/src/android/net/ip/IpReachabilityMonitor.java
index 3dbe662..0f199e2 100644
--- a/src/android/net/ip/IpReachabilityMonitor.java
+++ b/src/android/net/ip/IpReachabilityMonitor.java
@@ -20,6 +20,8 @@
 import static android.net.metrics.IpReachabilityEvent.NUD_FAILED_ORGANIC;
 import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST;
 import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST_ORGANIC;
+import static android.net.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -30,7 +32,6 @@
 import android.net.ip.IpNeighborMonitor.NeighborEventConsumer;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.IpReachabilityEvent;
-import android.net.netlink.StructNdMsg;
 import android.net.util.InterfaceParams;
 import android.net.util.SharedLog;
 import android.os.ConditionVariable;
@@ -40,12 +41,21 @@
 import android.os.PowerManager.WakeLock;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.stats.connectivity.IpType;
+import android.stats.connectivity.NudEventType;
+import android.stats.connectivity.NudNeighborType;
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+import com.android.net.module.util.DeviceConfigUtils;
+import com.android.net.module.util.netlink.StructNdMsg;
 import com.android.networkstack.R;
+import com.android.networkstack.metrics.IpReachabilityMonitorMetrics;
 
 import java.io.PrintWriter;
 import java.net.Inet6Address;
@@ -143,6 +153,10 @@
     protected static final int MIN_NUD_SOLICIT_NUM = 5;
     protected static final int MAX_NUD_SOLICIT_INTERVAL_MS = 1000;
     protected static final int MIN_NUD_SOLICIT_INTERVAL_MS = 750;
+    protected static final int NUD_MCAST_RESOLICIT_NUM = 3;
+    private static final int INVALID_NUD_MCAST_RESOLICIT_NUM = -1;
+
+    private static final int INVALID_LEGACY_NUD_FAILURE_TYPE = -1;
 
     public interface Callback {
         /**
@@ -161,6 +175,8 @@
     interface Dependencies {
         void acquireWakeLock(long durationMs);
         IpNeighborMonitor makeIpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb);
+        boolean isFeatureEnabled(Context context, String name, boolean defaultEnabled);
+        IpReachabilityMonitorMetrics getIpReachabilityMonitorMetrics();
 
         static Dependencies makeDefault(Context context, String iface) {
             final String lockName = TAG + "." + iface;
@@ -176,6 +192,16 @@
                         NeighborEventConsumer cb) {
                     return new IpNeighborMonitor(h, log, cb);
                 }
+
+                public boolean isFeatureEnabled(final Context context, final String name,
+                        boolean defaultEnabled) {
+                    return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name,
+                            defaultEnabled);
+                }
+
+                public IpReachabilityMonitorMetrics getIpReachabilityMonitorMetrics() {
+                    return new IpReachabilityMonitorMetrics();
+                }
             };
         }
     }
@@ -183,25 +209,30 @@
     private final InterfaceParams mInterfaceParams;
     private final IpNeighborMonitor mIpNeighborMonitor;
     private final SharedLog mLog;
-    private final Callback mCallback;
     private final Dependencies mDependencies;
     private final boolean mUsingMultinetworkPolicyTracker;
     private final ConnectivityManager mCm;
     private final IpConnectivityLog mMetricsLog;
     private final Context mContext;
     private final INetd mNetd;
+    private final IpReachabilityMonitorMetrics mIpReachabilityMetrics;
     private LinkProperties mLinkProperties = new LinkProperties();
     private Map<InetAddress, NeighborEvent> mNeighborWatchList = new HashMap<>();
     // Time in milliseconds of the last forced probe request.
     private volatile long mLastProbeTimeMs;
+    // Time in milliseconds of the last forced probe request due to roam or CMD_CONFIRM.
+    private long mLastProbeDueToRoamMs;
+    private long mLastProbeDueToConfirmMs;
     private int mNumSolicits;
     private int mInterSolicitIntervalMs;
+    @NonNull
+    private final Callback mCallback;
 
     public IpReachabilityMonitor(
             Context context, InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
-            boolean usingMultinetworkPolicyTracker, final INetd netd) {
-        this(context, ifParams, h, log, callback, usingMultinetworkPolicyTracker,
-                Dependencies.makeDefault(context, ifParams.name), new IpConnectivityLog(), netd);
+            boolean usingMultinetworkPolicyTracker, Dependencies dependencies, final INetd netd) {
+        this(context, ifParams, h, log, callback, usingMultinetworkPolicyTracker, dependencies,
+                new IpConnectivityLog(), netd);
     }
 
     @VisibleForTesting
@@ -225,7 +256,10 @@
         // In case the overylaid parameters specify an invalid configuration, set the parameters
         // to the hardcoded defaults first, then set them to the values used in the steady state.
         try {
-            setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS);
+            int numResolicits = isMulticastResolicitEnabled()
+                    ? NUD_MCAST_RESOLICIT_NUM
+                    : INVALID_NUD_MCAST_RESOLICIT_NUM;
+            setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS, numResolicits);
         } catch (Exception e) {
             Log.e(TAG, "Failed to adjust neighbor parameters with hardcoded defaults");
         }
@@ -241,13 +275,16 @@
                     // TODO: Consider what to do with other states that are not within
                     // NeighborEvent#isValid() (i.e. NUD_NONE, NUD_INCOMPLETE).
                     if (event.nudState == StructNdMsg.NUD_FAILED) {
+                        // After both unicast probe and multicast probe(if mcast_resolicit is not 0)
+                        // attempts fail, trigger the neighbor lost event and disconnect.
                         mLog.w("ALERT neighbor went from: " + prev + " to: " + event);
                         handleNeighborLost(event);
                     } else if (event.nudState == StructNdMsg.NUD_REACHABLE) {
-                        maybeRestoreNeighborParameters();
+                        handleNeighborReachable(prev, event);
                     }
                 });
         mIpNeighborMonitor.start();
+        mIpReachabilityMetrics = dependencies.getIpReachabilityMonitorMetrics();
     }
 
     public void stop() {
@@ -296,6 +333,33 @@
         return false;
     }
 
+    private boolean hasDefaultRouterNeighborMacAddressChanged(
+            @Nullable final NeighborEvent prev, @NonNull final NeighborEvent event) {
+        if (prev == null || !isNeighborDefaultRouter(event)) return false;
+        return !event.macAddr.equals(prev.macAddr);
+    }
+
+    private boolean isNeighborDefaultRouter(@NonNull final NeighborEvent event) {
+        // For the IPv6 link-local scoped address, equals() works because the NeighborEvent.ip
+        // doesn't have a scope id and Inet6Address#equals doesn't consider scope id neither.
+        for (RouteInfo route : mLinkProperties.getRoutes()) {
+            if (route.isDefaultRoute() && event.ip.equals(route.getGateway())) return true;
+        }
+        return false;
+    }
+
+    private boolean isNeighborDnsServer(@NonNull final NeighborEvent event) {
+        for (InetAddress dns : mLinkProperties.getDnsServers()) {
+            if (event.ip.equals(dns)) return true;
+        }
+        return false;
+    }
+
+    private boolean isMulticastResolicitEnabled() {
+        return mDependencies.isFeatureEnabled(mContext, IP_REACHABILITY_MCAST_RESOLICIT_VERSION,
+                false /* defaultEnabled */);
+    }
+
     public void updateLinkProperties(LinkProperties lp) {
         if (!mInterfaceParams.name.equals(lp.getInterfaceName())) {
             // TODO: figure out whether / how to cope with interface changes.
@@ -333,6 +397,25 @@
         if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); }
     }
 
+    private void handleNeighborReachable(@Nullable final NeighborEvent prev,
+            @NonNull final NeighborEvent event) {
+        if (isMulticastResolicitEnabled()
+                && hasDefaultRouterNeighborMacAddressChanged(prev, event)) {
+            // This implies device has confirmed the neighbor's reachability from
+            // other states(e.g., NUD_PROBE or NUD_STALE), checking if the mac
+            // address hasn't changed is required. If Mac address does change, then
+            // trigger a new neighbor lost event and disconnect.
+            final String logMsg = "ALERT neighbor: " + event.ip
+                    + " MAC address changed from: " + prev.macAddr
+                    + " to: " + event.macAddr;
+            mLog.w(logMsg);
+            mCallback.notifyLost(event.ip, logMsg);
+            logNudFailed(event, NudEventType.NUD_MAC_ADDRESS_CHANGED);
+            return;
+        }
+        maybeRestoreNeighborParameters();
+    }
+
     private void handleNeighborLost(NeighborEvent event) {
         final LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
 
@@ -366,17 +449,17 @@
         final boolean lostProvisioning =
                 (mLinkProperties.isIpv4Provisioned() && !whatIfLp.isIpv4Provisioned())
                 || (mLinkProperties.isIpv6Provisioned() && !whatIfLp.isIpv6Provisioned());
+        final NudEventType type = getNudFailureEventType(isFromProbe(),
+                isNudFailureDueToRoam(), lostProvisioning);
 
         if (lostProvisioning) {
             final String logMsg = "FAILURE: LOST_PROVISIONING, " + event;
             Log.w(TAG, logMsg);
-            if (mCallback != null) {
-                // TODO: remove |ip| when the callback signature no longer has
-                // an InetAddress argument.
-                mCallback.notifyLost(ip, logMsg);
-            }
+            // TODO: remove |ip| when the callback signature no longer has
+            // an InetAddress argument.
+            mCallback.notifyLost(ip, logMsg);
         }
-        logNudFailed(lostProvisioning);
+        logNudFailed(event, type);
     }
 
     private void maybeRestoreNeighborParameters() {
@@ -400,7 +483,13 @@
         return !mUsingMultinetworkPolicyTracker || mCm.shouldAvoidBadWifi();
     }
 
-    public void probeAll() {
+    /**
+     * Force probe to verify whether or not the critical on-link neighbours are still reachable.
+     *
+     * @param dueToRoam indicate on which situation forced probe has been sent, e.g., on post
+     *                  roaming or receiving CMD_CONFIRM from IpClient.
+     */
+    public void probeAll(boolean dueToRoam) {
         setNeighbourParametersPostRoaming();
 
         final List<InetAddress> ipProbeList = new ArrayList<>(mNeighborWatchList.keySet());
@@ -421,6 +510,11 @@
             logEvent(IpReachabilityEvent.PROBE, rval);
         }
         mLastProbeTimeMs = SystemClock.elapsedRealtime();
+        if (dueToRoam) {
+            mLastProbeDueToRoamMs = mLastProbeTimeMs;
+        } else {
+            mLastProbeDueToConfirmMs = mLastProbeTimeMs;
+        }
     }
 
     private long getProbeWakeLockDuration() {
@@ -450,6 +544,12 @@
 
     private void setNeighborParameters(int numSolicits, int interSolicitIntervalMs)
             throws RemoteException, IllegalArgumentException {
+        // Do not set mcast_resolicit param by default.
+        setNeighborParameters(numSolicits, interSolicitIntervalMs, INVALID_NUD_MCAST_RESOLICIT_NUM);
+    }
+
+    private void setNeighborParameters(int numSolicits, int interSolicitIntervalMs,
+            int numResolicits) throws RemoteException, IllegalArgumentException {
         Preconditions.checkArgument(numSolicits >= MIN_NUD_SOLICIT_NUM,
                 "numSolicits must be at least " + MIN_NUD_SOLICIT_NUM);
         Preconditions.checkArgument(numSolicits <= MAX_NUD_SOLICIT_NUM,
@@ -464,32 +564,117 @@
                     Integer.toString(interSolicitIntervalMs));
             mNetd.setProcSysNet(family, INetd.NEIGH, mInterfaceParams.name, "ucast_solicit",
                     Integer.toString(numSolicits));
+            if (numResolicits != INVALID_NUD_MCAST_RESOLICIT_NUM) {
+                mNetd.setProcSysNet(family, INetd.NEIGH, mInterfaceParams.name, "mcast_resolicit",
+                        Integer.toString(numResolicits));
+            }
         }
 
         mNumSolicits = numSolicits;
         mInterSolicitIntervalMs = interSolicitIntervalMs;
     }
 
+    private boolean isFromProbe() {
+        final long duration = SystemClock.elapsedRealtime() - mLastProbeTimeMs;
+        return duration < getProbeWakeLockDuration();
+    }
+
+    private boolean isNudFailureDueToRoam() {
+        if (!isFromProbe()) return false;
+
+        // Check to which probe expiry the curren timestamp gets close when NUD failure event
+        // happens, theoretically that indicates which probe event(due to roam or CMD_CONFIRM)
+        // was triggered eariler.
+        //
+        // Note that this would be incorrect if the probe or confirm was so long ago that the
+        // probe duration has already expired. That cannot happen because isFromProbe would return
+        // false.
+        final long probeExpiryAfterRoam = mLastProbeDueToRoamMs + getProbeWakeLockDuration();
+        final long probeExpiryAfterConfirm =
+                mLastProbeDueToConfirmMs + getProbeWakeLockDuration();
+        final long currentTime = SystemClock.elapsedRealtime();
+        return Math.abs(probeExpiryAfterRoam - currentTime)
+                < Math.abs(probeExpiryAfterConfirm - currentTime);
+    }
+
     private void logEvent(int probeType, int errorCode) {
         int eventType = probeType | (errorCode & 0xff);
         mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType));
     }
 
-    private void logNudFailed(boolean lostProvisioning) {
-        long duration = SystemClock.elapsedRealtime() - mLastProbeTimeMs;
-        boolean isFromProbe = (duration < getProbeWakeLockDuration());
-        int eventType = nudFailureEventType(isFromProbe, lostProvisioning);
+    private void logNudFailed(final NeighborEvent event, final NudEventType type) {
+        logNeighborLostEvent(event, type);
+
+        // The legacy metrics only record whether the failure came from a probe and whether
+        // the network is still provisioned. They do not record provisioning failures due to
+        // multicast resolicits finding that the MAC address has changed.
+        final int eventType = legacyNudFailureType(type);
+        if (eventType == INVALID_LEGACY_NUD_FAILURE_TYPE) return;
         mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType));
     }
 
     /**
+     * Returns the neighbor type code corresponding to the given conditions.
+     */
+    private NudNeighborType getNeighborType(final NeighborEvent event) {
+        final boolean isGateway = isNeighborDefaultRouter(event);
+        final boolean isDnsServer = isNeighborDnsServer(event);
+
+        if (isGateway && isDnsServer) return NudNeighborType.NUD_NEIGHBOR_BOTH;
+        if (isGateway && !isDnsServer) return NudNeighborType.NUD_NEIGHBOR_GATEWAY;
+        if (!isGateway && isDnsServer) return NudNeighborType.NUD_NEIGHBOR_DNS;
+        return NudNeighborType.NUD_NEIGHBOR_UNKNOWN;
+    }
+
+    /**
      * Returns the NUD failure event type code corresponding to the given conditions.
      */
-    private static int nudFailureEventType(boolean isFromProbe, boolean isProvisioningLost) {
-        if (isFromProbe) {
-            return isProvisioningLost ? PROVISIONING_LOST : NUD_FAILED;
-        } else {
-            return isProvisioningLost ? PROVISIONING_LOST_ORGANIC : NUD_FAILED_ORGANIC;
+    private static NudEventType getNudFailureEventType(boolean isFromProbe, boolean isDueToRoam,
+            boolean isProvisioningLost) {
+        if (!isFromProbe) {
+            return isProvisioningLost
+                    ? NudEventType.NUD_ORGANIC_FAILED_CRITICAL
+                    : NudEventType.NUD_ORGANIC_FAILED;
+        }
+        return isProvisioningLost
+                ? isDueToRoam
+                        ? NudEventType.NUD_POST_ROAMING_FAILED_CRITICAL
+                        : NudEventType.NUD_CONFIRM_FAILED_CRITICAL
+                : isDueToRoam
+                        ? NudEventType.NUD_POST_ROAMING_FAILED
+                        : NudEventType.NUD_CONFIRM_FAILED;
+    }
+
+    /**
+     * Log NUD failure metrics with new Westworld APIs while the function using mMetricsLog API
+     * still sends the legacy metrics, @see #logNudFailed.
+     */
+    private void logNeighborLostEvent(final NeighborEvent event, final NudEventType type) {
+        final IpType ipType = (event.ip instanceof Inet6Address) ? IpType.IPV6 : IpType.IPV4;
+        mIpReachabilityMetrics.setNudIpType(ipType);
+        mIpReachabilityMetrics.setNudNeighborType(getNeighborType(event));
+        mIpReachabilityMetrics.setNudEventType(type);
+        mIpReachabilityMetrics.statsWrite();
+    }
+
+    /**
+     * Returns the NUD failure event type code corresponding to the given conditions.
+     */
+    private static int legacyNudFailureType(final NudEventType type) {
+        switch (type) {
+            case NUD_POST_ROAMING_FAILED:
+            case NUD_CONFIRM_FAILED:
+                return NUD_FAILED;
+            case NUD_POST_ROAMING_FAILED_CRITICAL:
+            case NUD_CONFIRM_FAILED_CRITICAL:
+                return PROVISIONING_LOST;
+            case NUD_ORGANIC_FAILED:
+                return NUD_FAILED_ORGANIC;
+            case NUD_ORGANIC_FAILED_CRITICAL:
+                return PROVISIONING_LOST_ORGANIC;
+            default:
+                // Do not log legacy event
+                return INVALID_LEGACY_NUD_FAILURE_TYPE;
         }
     }
 }
diff --git a/src/android/net/util/NetworkStackUtils.java b/src/android/net/util/NetworkStackUtils.java
index 5c11733..6dc2a5b 100755
--- a/src/android/net/util/NetworkStackUtils.java
+++ b/src/android/net/util/NetworkStackUtils.java
@@ -17,12 +17,16 @@
 package android.net.util;
 
 import android.content.Context;
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
 
 import com.android.net.module.util.DeviceConfigUtils;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.SocketException;
 
 /**
@@ -232,6 +236,39 @@
      */
     public static final String VALIDATION_METRICS_VERSION = "validation_metrics_version";
 
+    /**
+     * Experiment flag to enable sending gratuitous multicast unsolicited Neighbor Advertisements
+     * to propagate new assigned IPv6 GUA as quickly as possible.
+     */
+    public static final String IPCLIENT_GRATUITOUS_NA_VERSION = "ipclient_gratuitous_na_version";
+
+    /**
+     * Experiment flag to enable sending Gratuitous APR and Gratuitous Neighbor Advertisement for
+     * all assigned IPv4 and IPv6 GUAs after completing L2 roaming.
+     */
+    public static final String IPCLIENT_GARP_NA_ROAMING_VERSION =
+            "ipclient_garp_na_roaming_version";
+
+    /**
+     * Experiment flag to enable parsing netlink events from kernel directly instead from netd aidl
+     * interface.
+     */
+    public static final String IPCLIENT_PARSE_NETLINK_EVENTS_VERSION =
+            "ipclient_parse_netlink_events_version";
+
+    /**
+     * Experiment flag to disable accept_ra parameter when IPv6 provisioning loss happens due to
+     * the default route has gone.
+     */
+    public static final String IPCLIENT_DISABLE_ACCEPT_RA_VERSION = "ipclient_disable_accept_ra";
+
+    /**
+     * Experiment flag to enable "mcast_resolicit" neighbor parameter in IpReachabilityMonitor,
+     * set it to 3 by default.
+     */
+    public static final String IP_REACHABILITY_MCAST_RESOLICIT_VERSION =
+            "ip_reachability_mcast_resolicit_version";
+
     static {
         System.loadLibrary("networkstackutilsjni");
     }
@@ -247,6 +284,21 @@
     }
 
     /**
+     * Convert IPv6 multicast address to ethernet multicast address in network order.
+     */
+    public static MacAddress ipv6MulticastToEthernetMulticast(@NonNull final Inet6Address addr) {
+        final byte[] etherMulticast = new byte[6];
+        final byte[] ipv6Multicast = addr.getAddress();
+        etherMulticast[0] = (byte) 0x33;
+        etherMulticast[1] = (byte) 0x33;
+        etherMulticast[2] = ipv6Multicast[12];
+        etherMulticast[3] = ipv6Multicast[13];
+        etherMulticast[4] = ipv6Multicast[14];
+        etherMulticast[5] = ipv6Multicast[15];
+        return MacAddress.fromBytes(etherMulticast);
+    }
+
+    /**
      * Attaches a socket filter that accepts DHCP packets to the given socket.
      */
     public static native void attachDhcpFilter(FileDescriptor fd) throws SocketException;
diff --git a/src/com/android/networkstack/NetworkStackNotifier.java b/src/com/android/networkstack/NetworkStackNotifier.java
index 0558d3a..acf3c95 100644
--- a/src/com/android/networkstack/NetworkStackNotifier.java
+++ b/src/com/android/networkstack/NetworkStackNotifier.java
@@ -237,8 +237,8 @@
 
             // If the venue friendly name is available (in Passpoint use-case), display it.
             // Otherwise, display the SSID.
-            final String friendlyName = capportData.getVenueFriendlyName();
-            final String venueDisplayName = TextUtils.isEmpty(friendlyName)
+            final CharSequence friendlyName = capportData.getVenueFriendlyName();
+            final CharSequence venueDisplayName = TextUtils.isEmpty(friendlyName)
                     ? getSsid(networkStatus) : friendlyName;
 
             builder = getNotificationBuilder(channel, networkStatus, res, venueDisplayName)
@@ -284,9 +284,9 @@
 
     private Notification.Builder getNotificationBuilder(@NonNull String channelId,
             @NonNull TrackedNetworkStatus networkStatus, @NonNull Resources res,
-            @NonNull String ssid) {
+            @NonNull CharSequence networkIdentifier) {
         return new Notification.Builder(mContext, channelId)
-                .setContentTitle(ssid)
+                .setContentTitle(networkIdentifier)
                 .setSmallIcon(R.drawable.icon_wifi);
     }
 
diff --git a/src/com/android/networkstack/metrics/IpProvisioningMetrics.java b/src/com/android/networkstack/metrics/IpProvisioningMetrics.java
index b015a51..5ca996e 100644
--- a/src/com/android/networkstack/metrics/IpProvisioningMetrics.java
+++ b/src/com/android/networkstack/metrics/IpProvisioningMetrics.java
@@ -157,17 +157,17 @@
         mStatsBuilder.setDhcpSession(mDhcpSessionBuilder);
         mStatsBuilder.setProvisioningDurationMicros(mWatch.stop());
         mStatsBuilder.setRandomNumber((int) (Math.random() * 1000));
-        final NetworkIpProvisioningReported Stats = mStatsBuilder.build();
-        final byte[] DhcpSession = Stats.getDhcpSession().toByteArray();
+        final NetworkIpProvisioningReported stats = mStatsBuilder.build();
+        final byte[] DhcpSession = stats.getDhcpSession().toByteArray();
         NetworkStackStatsLog.write(NetworkStackStatsLog.NETWORK_IP_PROVISIONING_REPORTED,
-                Stats.getTransportType().getNumber(),
-                Stats.getIpv4LatencyMicros(),
-                Stats.getIpv6LatencyMicros(),
-                Stats.getProvisioningDurationMicros(),
-                Stats.getDisconnectCode().getNumber(),
+                stats.getTransportType().getNumber(),
+                stats.getIpv4LatencyMicros(),
+                stats.getIpv6LatencyMicros(),
+                stats.getProvisioningDurationMicros(),
+                stats.getDisconnectCode().getNumber(),
                 DhcpSession,
-                Stats.getRandomNumber());
+                stats.getRandomNumber());
         mWatch.reset();
-        return Stats;
+        return stats;
     }
 }
diff --git a/src/com/android/networkstack/metrics/IpReachabilityMonitorMetrics.java b/src/com/android/networkstack/metrics/IpReachabilityMonitorMetrics.java
new file mode 100644
index 0000000..401de4a
--- /dev/null
+++ b/src/com/android/networkstack/metrics/IpReachabilityMonitorMetrics.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.metrics;
+
+import android.stats.connectivity.IpType;
+import android.stats.connectivity.NudEventType;
+import android.stats.connectivity.NudNeighborType;
+
+/**
+ * Class to record the network stack IpReachabilityMonitor metrics into statsd.
+ *
+ * This class is not thread-safe, and should always be accessed from the same thread.
+ *
+ * @hide
+ */
+public class IpReachabilityMonitorMetrics {
+    private final NetworkIpReachabilityMonitorReported.Builder mStatsBuilder =
+            NetworkIpReachabilityMonitorReported.newBuilder();
+
+    /**
+     * Write the NUD event type into mStatsBuilder.
+     */
+    public void setNudEventType(final NudEventType type) {
+        mStatsBuilder.setEventType(type);
+    }
+
+    /**
+     * Write the NUD probe type(IPv4 or IPv6) into mStatsBuilder.
+     */
+    public void setNudIpType(final IpType type) {
+        mStatsBuilder.setIpType(type);
+    }
+
+    /**
+     * Write the NUD probe neighbor type into mStatsBuilder.
+     */
+    public void setNudNeighborType(final NudNeighborType type) {
+        mStatsBuilder.setNeighborType(type);
+    }
+
+    /**
+     * Write the NetworkIpReachabilityMonitorReported proto into statsd.
+     */
+    public NetworkIpReachabilityMonitorReported statsWrite() {
+        final NetworkIpReachabilityMonitorReported stats = mStatsBuilder.build();
+        NetworkStackStatsLog.write(NetworkStackStatsLog.NETWORK_IP_REACHABILITY_MONITOR_REPORTED,
+                stats.getEventType().getNumber(),
+                stats.getIpType().getNumber(),
+                stats.getNeighborType().getNumber());
+        return stats;
+    }
+}
diff --git a/src/com/android/networkstack/metrics/NetworkQuirkMetrics.java b/src/com/android/networkstack/metrics/NetworkQuirkMetrics.java
new file mode 100644
index 0000000..dee4504
--- /dev/null
+++ b/src/com/android/networkstack/metrics/NetworkQuirkMetrics.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.metrics;
+
+import android.stats.connectivity.NetworkQuirkEvent;
+
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * Class to record the network Quirk event into statsd.
+ * @hide
+ */
+public class NetworkQuirkMetrics {
+    private final Dependencies mDependencies;
+    private final NetworkStackQuirkReported.Builder mStatsBuilder =
+            NetworkStackQuirkReported.newBuilder();
+    /**
+     * Dependencies of {@link NetworkQuirkMetrics}, useful for testing.
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        /**
+         * @see NetworkStackStatsLog#write.
+         */
+        public void writeStats(int event) {
+            NetworkStackStatsLog.write(NetworkStackStatsLog.NETWORK_STACK_QUIRK_REPORTED,
+                    0, event);
+        }
+    }
+
+    /**
+     * Get a NetworkQuirkMetrics instance.
+     */
+    public NetworkQuirkMetrics() {
+        this(new Dependencies());
+    }
+
+    @VisibleForTesting
+    public NetworkQuirkMetrics(Dependencies deps) {
+        mDependencies = deps;
+    }
+
+    /**
+     * Write the network Quirk Event into mStatsBuilder.
+     */
+    public void setEvent(NetworkQuirkEvent event) {
+        mStatsBuilder.setEvent(event);
+    }
+
+    /**
+     * Write the NetworkStackQuirkReported proto into statsd.
+     */
+    public NetworkStackQuirkReported statsWrite() {
+        final NetworkStackQuirkReported stats = mStatsBuilder.build();
+        mDependencies.writeStats(stats.getEvent().getNumber());
+        return stats;
+    }
+}
diff --git a/src/com/android/networkstack/metrics/stats.proto b/src/com/android/networkstack/metrics/stats.proto
index 2b0a704..c09f082 100644
--- a/src/com/android/networkstack/metrics/stats.proto
+++ b/src/com/android/networkstack/metrics/stats.proto
@@ -172,3 +172,19 @@
     // Record each Quirk event
     optional .android.stats.connectivity.NetworkQuirkEvent event = 2;
 }
+
+/**
+ * Logs Neighbor Unreachability Detection probe event.
+ * Logged from:
+ * src/com/android/networkstack/metrics/IpReachabilityMonitorMetrics.java
+ */
+message NetworkIpReachabilityMonitorReported {
+    // Neighbor Unreachability Detection event.
+    optional .android.stats.connectivity.NudEventType event_type = 1;
+
+    // NUD probe based on IPv4 ARP or IPv6 ND packet.
+    optional .android.stats.connectivity.IpType ip_type = 2;
+
+    // NUD neighbor type, default gateway, DNS server or both.
+    optional .android.stats.connectivity.NudNeighborType neighbor_type = 3;
+}
diff --git a/src/com/android/networkstack/netlink/TcpSocketTracker.java b/src/com/android/networkstack/netlink/TcpSocketTracker.java
index 770e85a..b5eafd6 100644
--- a/src/com/android/networkstack/netlink/TcpSocketTracker.java
+++ b/src/com/android/networkstack/netlink/TcpSocketTracker.java
@@ -15,13 +15,6 @@
  */
 package com.android.networkstack.netlink;
 
-import static android.net.netlink.InetDiagMessage.InetDiagReqV2;
-import static android.net.netlink.NetlinkConstants.INET_DIAG_MEMINFO;
-import static android.net.netlink.NetlinkConstants.NLMSG_DONE;
-import static android.net.netlink.NetlinkConstants.SOCKDIAG_MSG_HEADER_SIZE;
-import static android.net.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY;
-import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
-import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
 import static android.net.util.DataStallUtils.CONFIG_MIN_PACKETS_THRESHOLD;
 import static android.net.util.DataStallUtils.CONFIG_TCP_PACKETS_FAIL_PERCENTAGE;
 import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_MIN_PACKETS_THRESHOLD;
@@ -38,14 +31,18 @@
 import static android.system.OsConstants.SOL_SOCKET;
 import static android.system.OsConstants.SO_SNDTIMEO;
 
+import static com.android.net.module.util.netlink.InetDiagMessage.inetDiagReqV2;
+import static com.android.net.module.util.netlink.NetlinkConstants.INET_DIAG_MEMINFO;
+import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_DONE;
+import static com.android.net.module.util.netlink.NetlinkConstants.SOCKDIAG_MSG_HEADER_SIZE;
+import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+
 import android.content.Context;
 import android.net.INetd;
 import android.net.MarkMaskParcel;
 import android.net.Network;
-import android.net.netlink.NetlinkConstants;
-import android.net.netlink.NetlinkSocket;
-import android.net.netlink.StructInetDiagMsg;
-import android.net.netlink.StructNlMsgHdr;
 import android.net.util.NetworkStackUtils;
 import android.net.util.SocketUtils;
 import android.os.AsyncTask;
@@ -66,6 +63,10 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.net.module.util.DeviceConfigUtils;
+import com.android.net.module.util.netlink.NetlinkConstants;
+import com.android.net.module.util.netlink.NetlinkSocket;
+import com.android.net.module.util.netlink.StructInetDiagMsg;
+import com.android.net.module.util.netlink.StructNlMsgHdr;
 import com.android.networkstack.apishim.NetworkShimImpl;
 import com.android.networkstack.apishim.common.ShimUtils;
 import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
@@ -114,7 +115,7 @@
      * Request to send to kernel to request tcp info.
      *
      *   Key: Ip family type.
-     * Value: Bytes array represent the {@Code InetDiagReqV2}.
+     * Value: Bytes array represent the {@Code inetDiagReqV2}.
      */
     private final SparseArray<byte[]> mSockDiagMsg = new SparseArray<>();
     private final Dependencies mDependencies;
@@ -160,7 +161,7 @@
         for (final int family : ADDRESS_FAMILIES) {
             mSockDiagMsg.put(
                     family,
-                    InetDiagReqV2(IPPROTO_TCP,
+                    inetDiagReqV2(IPPROTO_TCP,
                             null /* local addr */,
                             null /* remote addr */,
                             family,
diff --git a/src/com/android/networkstack/packets/NeighborAdvertisement.java b/src/com/android/networkstack/packets/NeighborAdvertisement.java
new file mode 100644
index 0000000..ef38314
--- /dev/null
+++ b/src/com/android/networkstack/packets/NeighborAdvertisement.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.packets;
+
+import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NA_HEADER_LEN;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
+
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.net.module.util.Ipv6Utils;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Icmpv6Header;
+import com.android.net.module.util.structs.Ipv6Header;
+import com.android.net.module.util.structs.LlaOption;
+import com.android.net.module.util.structs.NaHeader;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+
+/**
+ * Defines basic data and operations needed to build and parse Neighbor Advertisement packet.
+ *
+ * @hide
+ */
+public class NeighborAdvertisement {
+    @NonNull
+    public final EthernetHeader ethHdr;
+    @NonNull
+    public final Ipv6Header ipv6Hdr;
+    @NonNull
+    public final Icmpv6Header icmpv6Hdr;
+    @NonNull
+    public final NaHeader naHdr;
+    @Nullable
+    public final LlaOption tlla;
+
+    public NeighborAdvertisement(@NonNull final EthernetHeader ethHdr,
+            @NonNull final Ipv6Header ipv6Hdr, @NonNull final Icmpv6Header icmpv6Hdr,
+            @NonNull final NaHeader naHdr, @Nullable final LlaOption tlla) {
+        this.ethHdr = ethHdr;
+        this.ipv6Hdr = ipv6Hdr;
+        this.icmpv6Hdr = icmpv6Hdr;
+        this.naHdr = naHdr;
+        this.tlla = tlla;
+    }
+
+    /**
+     * Convert a Neighbor Advertisement instance to ByteBuffer.
+     */
+    public ByteBuffer toByteBuffer() {
+        final int etherHeaderLen = Struct.getSize(EthernetHeader.class);
+        final int ipv6HeaderLen = Struct.getSize(Ipv6Header.class);
+        final int icmpv6HeaderLen = Struct.getSize(Icmpv6Header.class);
+        final int naHeaderLen = Struct.getSize(NaHeader.class);
+        final int tllaOptionLen = (tlla == null) ? 0 : Struct.getSize(LlaOption.class);
+        final ByteBuffer packet = ByteBuffer.allocate(etherHeaderLen + ipv6HeaderLen
+                + icmpv6HeaderLen + naHeaderLen + tllaOptionLen);
+
+        ethHdr.writeToByteBuffer(packet);
+        ipv6Hdr.writeToByteBuffer(packet);
+        icmpv6Hdr.writeToByteBuffer(packet);
+        naHdr.writeToByteBuffer(packet);
+        if (tlla != null) {
+            tlla.writeToByteBuffer(packet);
+        }
+        packet.flip();
+
+        return packet;
+    }
+
+    /**
+     * Build a Neighbor Advertisement packet from the required specified parameters.
+     */
+    public static ByteBuffer build(@NonNull final MacAddress srcMac,
+            @NonNull final MacAddress dstMac, @NonNull final Inet6Address srcIp,
+            @NonNull final Inet6Address dstIp, int flags, @NonNull final Inet6Address target) {
+        final ByteBuffer tlla = LlaOption.build((byte) ICMPV6_ND_OPTION_TLLA, srcMac);
+        return Ipv6Utils.buildNaPacket(srcMac, dstMac, srcIp, dstIp, flags, target, tlla);
+    }
+
+    /**
+     * Parse a Neighbor Advertisement packet from ByteBuffer.
+     */
+    public static NeighborAdvertisement parse(@NonNull final byte[] recvbuf, final int length)
+            throws ParseException {
+        if (length < ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMPV6_NA_HEADER_LEN
+                || recvbuf.length < length) {
+            throw new ParseException("Invalid packet length: " + length);
+        }
+        final ByteBuffer packet = ByteBuffer.wrap(recvbuf, 0, length);
+
+        // Parse each header and option in Neighbor Advertisement packet in order.
+        final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, packet);
+        final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, packet);
+        final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, packet);
+        final NaHeader naHdr = Struct.parse(NaHeader.class, packet);
+        final LlaOption tlla = (packet.remaining() == 0)
+                ? null
+                : Struct.parse(LlaOption.class, packet);
+
+        return new NeighborAdvertisement(ethHdr, ipv6Hdr, icmpv6Hdr, naHdr, tlla);
+    }
+
+    /**
+     * Thrown when parsing Neighbor Advertisement packet failed.
+     */
+    public static class ParseException extends Exception {
+        ParseException(String message) {
+            super(message);
+        }
+    }
+}
diff --git a/src/com/android/networkstack/packets/NeighborSolicitation.java b/src/com/android/networkstack/packets/NeighborSolicitation.java
new file mode 100644
index 0000000..5c3e40a
--- /dev/null
+++ b/src/com/android/networkstack/packets/NeighborSolicitation.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.packets;
+
+import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NS_HEADER_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
+
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.net.module.util.Ipv6Utils;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Icmpv6Header;
+import com.android.net.module.util.structs.Ipv6Header;
+import com.android.net.module.util.structs.LlaOption;
+import com.android.net.module.util.structs.NsHeader;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+
+/**
+ * Defines basic data and operations needed to build and parse Neighbor Solicitation packet.
+ *
+ * @hide
+ */
+public class NeighborSolicitation {
+    @NonNull
+    public final EthernetHeader ethHdr;
+    @NonNull
+    public final Ipv6Header ipv6Hdr;
+    @NonNull
+    public final Icmpv6Header icmpv6Hdr;
+    @NonNull
+    public final NsHeader nsHdr;
+    @Nullable
+    public final LlaOption slla;
+
+    public NeighborSolicitation(@NonNull final EthernetHeader ethHdr,
+            @NonNull final Ipv6Header ipv6Hdr, @NonNull final Icmpv6Header icmpv6Hdr,
+            @NonNull final NsHeader nsHdr, @Nullable final LlaOption slla) {
+        this.ethHdr = ethHdr;
+        this.ipv6Hdr = ipv6Hdr;
+        this.icmpv6Hdr = icmpv6Hdr;
+        this.nsHdr = nsHdr;
+        this.slla = slla;
+    }
+
+    /**
+     * Convert a Neighbor Solicitation instance to ByteBuffer.
+     */
+    public ByteBuffer toByteBuffer() {
+        final int etherHeaderLen = Struct.getSize(EthernetHeader.class);
+        final int ipv6HeaderLen = Struct.getSize(Ipv6Header.class);
+        final int icmpv6HeaderLen = Struct.getSize(Icmpv6Header.class);
+        final int nsHeaderLen = Struct.getSize(NsHeader.class);
+        final int sllaOptionLen = (slla == null) ? 0 : Struct.getSize(LlaOption.class);
+        final ByteBuffer packet = ByteBuffer.allocate(etherHeaderLen + ipv6HeaderLen
+                + icmpv6HeaderLen + nsHeaderLen + sllaOptionLen);
+
+        ethHdr.writeToByteBuffer(packet);
+        ipv6Hdr.writeToByteBuffer(packet);
+        icmpv6Hdr.writeToByteBuffer(packet);
+        nsHdr.writeToByteBuffer(packet);
+        if (slla != null) {
+            slla.writeToByteBuffer(packet);
+        }
+        packet.flip();
+
+        return packet;
+    }
+
+    /**
+     * Build a Neighbor Solicitation packet from the required specified parameters.
+     */
+    public static ByteBuffer build(@NonNull final MacAddress srcMac,
+            @NonNull final MacAddress dstMac, @NonNull final Inet6Address srcIp,
+            @NonNull final Inet6Address dstIp, @NonNull final Inet6Address target) {
+        final ByteBuffer slla = LlaOption.build((byte) ICMPV6_ND_OPTION_SLLA, srcMac);
+        return Ipv6Utils.buildNsPacket(srcMac, dstMac, srcIp, dstIp, target, slla);
+    }
+
+    /**
+     * Parse a Neighbor Solicitation packet from ByteBuffer.
+     */
+    public static NeighborSolicitation parse(@NonNull final byte[] recvbuf, final int length)
+            throws ParseException {
+        if (length < ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMPV6_NS_HEADER_LEN
+                || recvbuf.length < length) {
+            throw new ParseException("Invalid packet length: " + length);
+        }
+        final ByteBuffer packet = ByteBuffer.wrap(recvbuf, 0, length);
+
+        // Parse each header and option in Neighbor Solicitation packet in order.
+        final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, packet);
+        final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, packet);
+        final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, packet);
+        final NsHeader nsHdr = Struct.parse(NsHeader.class, packet);
+        final LlaOption slla = (packet.remaining() == 0)
+                ? null
+                : Struct.parse(LlaOption.class, packet);
+
+        return new NeighborSolicitation(ethHdr, ipv6Hdr, icmpv6Hdr, nsHdr, slla);
+    }
+
+    /**
+     * Thrown when parsing Neighbor Solicitation packet failed.
+     */
+    public static class ParseException extends Exception {
+        ParseException(String message) {
+            super(message);
+        }
+    }
+}
diff --git a/src/com/android/networkstack/util/DnsUtils.java b/src/com/android/networkstack/util/DnsUtils.java
index 83f2daf..622f56a 100644
--- a/src/com/android/networkstack/util/DnsUtils.java
+++ b/src/com/android/networkstack/util/DnsUtils.java
@@ -29,7 +29,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.internal.util.TrafficStatsConstants;
+import com.android.net.module.util.NetworkStackConstants;
 import com.android.server.connectivity.NetworkMonitor.DnsLogFunc;
 
 import java.net.InetAddress;
@@ -126,7 +126,7 @@
         // look at the tag at all. Given that this is a library, the tag should be passed in by the
         // caller.
         final int oldTag = TrafficStats.getAndSetThreadStatsTag(
-                TrafficStatsConstants.TAG_SYSTEM_PROBE);
+                NetworkStackConstants.TAG_SYSTEM_PROBE);
 
         if (type == TYPE_ADDRCONFIG) {
             dnsResolver.query(network, host, flag, r -> r.run(), null /* cancellationSignal */,
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java
index f8a9bab..948ce8d 100755
--- a/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -16,13 +16,12 @@
 
 package com.android.server.connectivity;
 
+import static android.content.Intent.ACTION_CONFIGURATION_CHANGED;
 import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
 import static android.net.CaptivePortal.APP_RETURN_UNWANTED;
 import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS;
 import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC;
 import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL;
-import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.DnsResolver.FLAG_EMPTY;
 import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
 import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
@@ -33,10 +32,10 @@
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_SKIPPED;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.captiveportal.CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs;
 import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE;
 import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS;
@@ -127,12 +126,11 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.UserHandle;
+import android.os.SystemProperties;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.stats.connectivity.ProbeResult;
 import android.stats.connectivity.ProbeType;
-import android.telephony.AccessNetworkConstants;
 import android.telephony.CellIdentityNr;
 import android.telephony.CellInfo;
 import android.telephony.CellInfoGsm;
@@ -141,8 +139,6 @@
 import android.telephony.CellInfoTdscdma;
 import android.telephony.CellInfoWcdma;
 import android.telephony.CellSignalStrength;
-import android.telephony.NetworkRegistrationInfo;
-import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -161,13 +157,15 @@
 import com.android.internal.util.RingBufferIndices;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
-import com.android.internal.util.TrafficStatsConstants;
 import com.android.net.module.util.DeviceConfigUtils;
+import com.android.net.module.util.NetworkStackConstants;
 import com.android.networkstack.NetworkStackNotifier;
 import com.android.networkstack.R;
 import com.android.networkstack.apishim.CaptivePortalDataShimImpl;
 import com.android.networkstack.apishim.NetworkInformationShimImpl;
+import com.android.networkstack.apishim.api29.ConstantsShim;
 import com.android.networkstack.apishim.common.CaptivePortalDataShim;
+import com.android.networkstack.apishim.common.NetworkInformationShim;
 import com.android.networkstack.apishim.common.ShimUtils;
 import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
 import com.android.networkstack.metrics.DataStallDetectionStats;
@@ -224,6 +222,9 @@
     private static final String TAG = NetworkMonitor.class.getSimpleName();
     private static final boolean DBG  = true;
     private static final boolean VDBG = false;
+    // TODO(b/185082309): For flaky test debug only, remove it after fixing.
+    private static final boolean DDBG_STALL = "cf_x86_auto-userdebug".equals(
+            SystemProperties.get("ro.build.flavor", ""));
     private static final boolean VDBG_STALL = Log.isLoggable(TAG, Log.DEBUG);
     private static final String DEFAULT_USER_AGENT    = "Mozilla/5.0 (X11; Linux x86_64) "
                                                       + "AppleWebKit/537.36 (KHTML, like Gecko) "
@@ -388,10 +389,15 @@
     private static final int CMD_BANDWIDTH_CHECK_COMPLETE = 23;
 
     /**
-     * Message to self to know the bandwidth check is timeouted.
+     * Message to self to know the bandwidth check has timed out.
      */
     private static final int CMD_BANDWIDTH_CHECK_TIMEOUT = 24;
 
+    /**
+     * Message to self to notify resource configuration is changed.
+     */
+    private static final int EVENT_RESOURCE_CONFIG_CHANGED = 25;
+
     // Start mReevaluateDelayMs at this value and double.
     @VisibleForTesting
     static final int INITIAL_REEVALUATE_DELAY_MS = 1000;
@@ -416,7 +422,6 @@
     private String mPrivateDnsProviderHostname = "";
 
     private final Context mContext;
-    private final Context mCustomizedContext;
     private final INetworkMonitorCallbacks mCallback;
     private final int mCallbackVersion;
     private final Network mCleartextDnsNetwork;
@@ -431,13 +436,21 @@
     private final TcpSocketTracker mTcpTracker;
     // Configuration values for captive portal detection probes.
     private final String mCaptivePortalUserAgent;
-    private final URL[] mCaptivePortalFallbackUrls;
-    @NonNull
-    private final URL[] mCaptivePortalHttpUrls;
-    @NonNull
-    private final URL[] mCaptivePortalHttpsUrls;
+    // Configuration values in setting providers for captive portal detection probes
+    private final String mCaptivePortalHttpsUrlFromSetting;
+    private final String mCaptivePortalHttpUrlFromSetting;
     @Nullable
     private final CaptivePortalProbeSpec[] mCaptivePortalFallbackSpecs;
+
+    // The probing URLs may be updated after constructor if system notifies configuration changed.
+    // Thus, these probing URLs should only be accessed in the StateMachine thread.
+    @NonNull
+    private URL[] mCaptivePortalFallbackUrls;
+    @NonNull
+    private URL[] mCaptivePortalHttpUrls;
+    @NonNull
+    private URL[] mCaptivePortalHttpsUrls;
+
     // Configuration values for network bandwidth check.
     @Nullable
     private final String mEvaluatingBandwidthUrl;
@@ -511,10 +524,13 @@
     private @EvaluationType int mDataStallTypeToCollect;
     private boolean mAcceptPartialConnectivity = false;
     private final EvaluationState mEvaluationState = new EvaluationState();
-
+    @NonNull
+    private final BroadcastReceiver mConfigurationReceiver;
     private final boolean mPrivateIpNoInternetEnabled;
 
     private final boolean mMetricsEnabled;
+    @NonNull
+    private final NetworkInformationShim mInfoShim = NetworkInformationShimImpl.newInstance();
 
     // The validation metrics are accessed by individual probe threads, and by the StateMachine
     // thread. All accesses must be synchronized to make sure the StateMachine thread can see
@@ -573,7 +589,6 @@
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
         mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
         mNotifier = serviceManager.getNotifier();
-        mCustomizedContext = getCustomizedContextOrDefault();
 
         // CHECKSTYLE:OFF IndentationCheck
         addState(mDefaultState);
@@ -588,16 +603,18 @@
         setInitialState(mDefaultState);
         // CHECKSTYLE:ON IndentationCheck
 
+        mCaptivePortalHttpsUrlFromSetting =
+                mDependencies.getSetting(context, CAPTIVE_PORTAL_HTTPS_URL, null);
+        mCaptivePortalHttpUrlFromSetting =
+                mDependencies.getSetting(context, CAPTIVE_PORTAL_HTTP_URL, null);
         mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled();
         mPrivateIpNoInternetEnabled = getIsPrivateIpNoInternetEnabled();
         mMetricsEnabled = deps.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY,
                 NetworkStackUtils.VALIDATION_METRICS_VERSION, true /* defaultEnabled */);
         mUseHttps = getUseHttpsValidation();
         mCaptivePortalUserAgent = getCaptivePortalUserAgent();
-        mCaptivePortalHttpsUrls = makeCaptivePortalHttpsUrls();
-        mCaptivePortalHttpUrls = makeCaptivePortalHttpUrls();
-        mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls();
-        mCaptivePortalFallbackSpecs = makeCaptivePortalFallbackProbeSpecs();
+        mCaptivePortalFallbackSpecs =
+                makeCaptivePortalFallbackProbeSpecs(getCustomizedContextOrDefault());
         mRandom = deps.getRandom();
         // TODO: Evaluate to move data stall configuration to a specific class.
         mConsecutiveDnsTimeoutThreshold = getConsecutiveDnsTimeoutThreshold();
@@ -616,7 +633,14 @@
         mEvaluatingBandwidthTimeoutMs = getResIntConfig(mContext,
                 R.integer.config_evaluating_bandwidth_timeout_ms,
                 DEFAULT_EVALUATING_BANDWIDTH_TIMEOUT_MS);
-
+        mConfigurationReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) {
+                    sendMessage(EVENT_RESOURCE_CONFIG_CHANGED);
+                }
+            }
+        };
         // Provide empty LinkProperties and NetworkCapabilities to make sure they are never null,
         // even before notifyNetworkConnected.
         mLinkProperties = new LinkProperties();
@@ -634,6 +658,13 @@
 
     /**
      * Request the NetworkMonitor to reevaluate the network.
+     *
+     * TODO : refactor reevaluation to introduce rate limiting. If the system finds a network is
+     * validated but some app can't access their server, or the network is behind a captive portal
+     * that only lets the validation URL through, apps may be calling reportNetworkConnectivity
+     * often, causing many revalidation attempts. Meanwhile, reevaluation attempts that result
+     * from actions that may affect the validation status (e.g. the user just logged in through
+     * the captive portal app) should never be skipped because of the rate limitation.
      */
     public void forceReevaluation(int responsibleUid) {
         sendMessage(CMD_FORCE_REEVALUATION, responsibleUid, 0);
@@ -671,6 +702,7 @@
                 (Pair<LinkProperties, NetworkCapabilities>) connectedMsg.obj;
         mLinkProperties = attrs.first;
         mNetworkCapabilities = attrs.second;
+        suppressNotificationIfNetworkRestricted();
     }
 
     /**
@@ -735,6 +767,12 @@
         return NetworkMonitorUtils.isPrivateDnsValidationRequired(mNetworkCapabilities);
     }
 
+    private void suppressNotificationIfNetworkRestricted() {
+        if (!mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
+            mDontDisplaySigninNotification = true;
+        }
+    }
+
     private void notifyNetworkTested(NetworkTestResultParcelable result) {
         try {
             if (mCallbackVersion <= 5) {
@@ -853,6 +891,18 @@
     // does not entail any real state (hence no enter() or exit() routines).
     private class DefaultState extends State {
         @Override
+        public void enter() {
+            // Register configuration broadcast here instead of constructor to prevent start() was
+            // not called yet when the broadcast is received and cause crash.
+            mContext.registerReceiver(mConfigurationReceiver,
+                    new IntentFilter(ACTION_CONFIGURATION_CHANGED));
+            checkAndRenewResourceConfig();
+            Log.d(TAG, "Starting on network " + mNetwork
+                    + " with capport HTTPS URL " + Arrays.toString(mCaptivePortalHttpsUrls)
+                    + " and HTTP URL " + Arrays.toString(mCaptivePortalHttpUrls));
+        }
+
+        @Override
         public boolean processMessage(Message message) {
             switch (message.what) {
                 case CMD_NETWORK_CONNECTED:
@@ -907,6 +957,18 @@
                             // If the user wants to use this network anyway, there is no need to
                             // perform the bandwidth check even if configured.
                             mIsBandwidthCheckPassedOrIgnored = true;
+                            // If the user wants to use this network anyway, it should always
+                            // be reported as validated, but other checks still need to be
+                            // done. For example, it should still validate strict private DNS and
+                            // show a notification if not available, because the network will
+                            // be unusable for this additional reason.
+                            mEvaluationState.setCaptivePortalWantedAsIs();
+                            // A successful evaluation result should be reported immediately, so
+                            // that the network stack may immediately use the validation in ranking
+                            // without waiting for a possibly long private DNS or bandwidth eval
+                            // step.
+                            mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_VALID,
+                                    null);
                             // TODO: Distinguish this from a network that actually validates.
                             // Displaying the "x" on the system UI icon may still be a good idea.
                             transitionTo(mEvaluatingPrivateDnsState);
@@ -984,12 +1046,29 @@
                     break;
                 case EVENT_NETWORK_CAPABILITIES_CHANGED:
                     mNetworkCapabilities = (NetworkCapabilities) message.obj;
+                    suppressNotificationIfNetworkRestricted();
+                    break;
+                case EVENT_RESOURCE_CONFIG_CHANGED:
+                    // RRO generation does not happen during package installation and instead after
+                    // the OMS receives the PACKAGE_ADDED event, there is a delay where the old
+                    // idmap is used with the new target package resulting in the incorrect overlay
+                    // is used. Renew the resource if a configuration change is received.
+                    // TODO: Remove it once design to generate the idmaps during package
+                    //  installation in overlay manager and package manager is ready.
+                    if (checkAndRenewResourceConfig()) {
+                        sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 1 /* forceAccept */);
+                    }
                     break;
                 default:
                     break;
             }
             return HANDLED;
         }
+
+        @Override
+        public void exit() {
+            mContext.unregisterReceiver(mConfigurationReceiver);
+        }
     }
 
     // Being in the ValidatedState State indicates a Network is:
@@ -1227,6 +1306,22 @@
             if (!mEvaluationTimer.isStarted()) {
                 mEvaluationTimer.start();
             }
+
+            // Check if the network is captive with Terms & Conditions page. The first network
+            // evaluation for captive networks with T&Cs returns early but NetworkMonitor will then
+            // keep checking for connectivity to determine when the T&Cs are cleared.
+            if (isTermsAndConditionsCaptive(mInfoShim.getCaptivePortalData(mLinkProperties))
+                    && mValidations == 0) {
+                mLastPortalProbeResult = new CaptivePortalProbeResult(
+                        CaptivePortalProbeResult.PORTAL_CODE,
+                        mLinkProperties.getCaptivePortalData().getUserPortalUrl()
+                                .toString(), null,
+                        CaptivePortalProbeResult.PROBE_UNKNOWN);
+                mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
+                        mLastPortalProbeResult.redirectUrl);
+                transitionTo(mCaptivePortalState);
+                return;
+            }
             sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
             if (mUidResponsibleForReeval != INVALID_UID) {
                 TrafficStats.setThreadStatsUid(mUidResponsibleForReeval);
@@ -1264,11 +1359,19 @@
                     //    the network so don't bother validating here.  Furthermore sending HTTP
                     //    packets over the network may be undesirable, for example an extremely
                     //    expensive metered network, or unwanted leaking of the User Agent string.
+                    // Also don't bother validating networks that the user already said they
+                    // wanted as-is.
                     //
                     // On networks that need to support private DNS in strict mode (e.g., VPNs, but
                     // not networks that don't provide Internet access), we still need to perform
                     // private DNS server resolution.
-                    if (!isValidationRequired()) {
+                    if (mEvaluationState.isCaptivePortalWantedAsIs()
+                            && isPrivateDnsValidationRequired()) {
+                        // Captive portals can only be detected on networks that validate both
+                        // validation and private DNS validation.
+                        validationLog("Captive portal is used as is, resolving private DNS");
+                        transitionTo(mEvaluatingPrivateDnsState);
+                    } else if (!isValidationRequired()) {
                         if (isPrivateDnsValidationRequired()) {
                             validationLog("Network would not satisfy default request, "
                                     + "resolving private DNS");
@@ -1286,7 +1389,9 @@
                     return HANDLED;
                 case CMD_FORCE_REEVALUATION:
                     // The evaluation process restarts via EvaluatingState#enter.
-                    return shouldAcceptForceRevalidation() ? NOT_HANDLED : HANDLED;
+                    final boolean forceAccept = (message.arg2 != 0);
+                    return forceAccept || shouldAcceptForceRevalidation()
+                            ? NOT_HANDLED : HANDLED;
                 // Disable HTTPS probe and transition to EvaluatingPrivateDnsState because:
                 // 1. Network is connected and finish the network validation.
                 // 2. NetworkMonitor detects network is partial connectivity and user accepts it.
@@ -1538,8 +1643,13 @@
 
             final int token = ++mProbeToken;
             final ValidationProperties deps = new ValidationProperties(mNetworkCapabilities);
+            final URL fallbackUrl = nextFallbackUrl();
+            final URL[] httpsUrls = Arrays.copyOf(
+                    mCaptivePortalHttpsUrls, mCaptivePortalHttpsUrls.length);
+            final URL[] httpUrls = Arrays.copyOf(
+                    mCaptivePortalHttpUrls, mCaptivePortalHttpUrls.length);
             mThread = new Thread(() -> sendMessage(obtainMessage(CMD_PROBE_COMPLETE, token, 0,
-                    isCaptivePortal(deps))));
+                    isCaptivePortal(deps, httpsUrls, httpUrls, fallbackUrl))));
             mThread.start();
         }
 
@@ -1562,6 +1672,16 @@
                         // Transit EvaluatingPrivateDnsState to get to Validated
                         // state (even if no Private DNS validation required).
                         transitionTo(mEvaluatingPrivateDnsState);
+                    } else if (isTermsAndConditionsCaptive(
+                            mInfoShim.getCaptivePortalData(mLinkProperties))) {
+                        mLastPortalProbeResult = new CaptivePortalProbeResult(
+                                CaptivePortalProbeResult.PORTAL_CODE,
+                                mLinkProperties.getCaptivePortalData().getUserPortalUrl()
+                                        .toString(), null,
+                                CaptivePortalProbeResult.PROBE_UNKNOWN);
+                        mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
+                                mLastPortalProbeResult.redirectUrl);
+                        transitionTo(mCaptivePortalState);
                     } else if (probeResult.isPortal()) {
                         mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
                                 probeResult.redirectUrl);
@@ -1919,18 +2039,24 @@
         }
 
         final long now = System.currentTimeMillis();
-        if (expTime < now || (expTime - now) > TEST_URL_EXPIRATION_MS) return null;
+        if (expTime < now || (expTime - now) > TEST_URL_EXPIRATION_MS) {
+            logw("Skipping test URL with expiration " + expTime + ", now " + now);
+            return null;
+        }
 
         final String strUrl = mDependencies.getDeviceConfigProperty(NAMESPACE_CONNECTIVITY,
                 key, null /* defaultValue */);
-        if (!isValidTestUrl(strUrl)) return null;
+        if (!isValidTestUrl(strUrl)) {
+            logw("Skipping invalid test URL " + strUrl);
+            return null;
+        }
         return makeURL(strUrl);
     }
 
-    private String getCaptivePortalServerHttpsUrl() {
-        return getSettingFromResource(mCustomizedContext,
-                R.string.config_captive_portal_https_url, CAPTIVE_PORTAL_HTTPS_URL,
-                mCustomizedContext.getResources().getString(
+    private String getCaptivePortalServerHttpsUrl(@NonNull Context context) {
+        return getSettingFromResource(context,
+                R.string.config_captive_portal_https_url, mCaptivePortalHttpsUrlFromSetting,
+                context.getResources().getString(
                 R.string.default_captive_portal_https_url));
     }
 
@@ -2007,10 +2133,10 @@
      * it has its own updatable strategies to detect captive portals. The framework only advises
      * on one URL that can be used, while NetworkMonitor may implement more complex logic.
      */
-    public String getCaptivePortalServerHttpUrl() {
-        return getSettingFromResource(mCustomizedContext,
-                R.string.config_captive_portal_http_url, CAPTIVE_PORTAL_HTTP_URL,
-                mCustomizedContext.getResources().getString(
+    public String getCaptivePortalServerHttpUrl(@NonNull Context context) {
+        return getSettingFromResource(context,
+                R.string.config_captive_portal_http_url, mCaptivePortalHttpUrlFromSetting,
+                context.getResources().getString(
                 R.string.default_captive_portal_http_url));
     }
 
@@ -2046,13 +2172,13 @@
     }
 
     @VisibleForTesting
-    URL[] makeCaptivePortalFallbackUrls() {
+    URL[] makeCaptivePortalFallbackUrls(@NonNull Context context) {
         try {
             final String firstUrl = mDependencies.getSetting(mContext, CAPTIVE_PORTAL_FALLBACK_URL,
                     null);
             final URL[] settingProviderUrls =
                 combineCaptivePortalUrls(firstUrl, CAPTIVE_PORTAL_OTHER_FALLBACK_URLS);
-            return getProbeUrlArrayConfig(settingProviderUrls,
+            return getProbeUrlArrayConfig(context, settingProviderUrls,
                     R.array.config_captive_portal_fallback_urls,
                     R.array.default_captive_portal_fallback_urls,
                     this::makeURL);
@@ -2063,7 +2189,7 @@
         }
     }
 
-    private CaptivePortalProbeSpec[] makeCaptivePortalFallbackProbeSpecs() {
+    private CaptivePortalProbeSpec[] makeCaptivePortalFallbackProbeSpecs(@NonNull Context context) {
         try {
             final String settingsValue = mDependencies.getDeviceConfigProperty(
                     NAMESPACE_CONNECTIVITY, CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS, null);
@@ -2073,7 +2199,7 @@
                     ? emptySpecs
                     : parseCaptivePortalProbeSpecs(settingsValue).toArray(emptySpecs);
 
-            return getProbeUrlArrayConfig(providerValue,
+            return getProbeUrlArrayConfig(context, providerValue,
                     R.array.config_captive_portal_fallback_probe_specs,
                     DEFAULT_CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS,
                     CaptivePortalProbeSpec::parseSpecOrNull);
@@ -2084,17 +2210,17 @@
         }
     }
 
-    private URL[] makeCaptivePortalHttpsUrls() {
+    private URL[] makeCaptivePortalHttpsUrls(@NonNull Context context) {
         final URL testUrl = getTestUrl(TEST_CAPTIVE_PORTAL_HTTPS_URL);
         if (testUrl != null) return new URL[] { testUrl };
 
-        final String firstUrl = getCaptivePortalServerHttpsUrl();
+        final String firstUrl = getCaptivePortalServerHttpsUrl(context);
         try {
             final URL[] settingProviderUrls =
                 combineCaptivePortalUrls(firstUrl, CAPTIVE_PORTAL_OTHER_HTTPS_URLS);
             // firstUrl will at least be default configuration, so default value in
             // getProbeUrlArrayConfig is actually never used.
-            return getProbeUrlArrayConfig(settingProviderUrls,
+            return getProbeUrlArrayConfig(context, settingProviderUrls,
                     R.array.config_captive_portal_https_urls,
                     DEFAULT_CAPTIVE_PORTAL_HTTPS_URLS, this::makeURL);
         } catch (Exception e) {
@@ -2105,17 +2231,17 @@
         }
     }
 
-    private URL[] makeCaptivePortalHttpUrls() {
+    private URL[] makeCaptivePortalHttpUrls(@NonNull Context context) {
         final URL testUrl = getTestUrl(TEST_CAPTIVE_PORTAL_HTTP_URL);
         if (testUrl != null) return new URL[] { testUrl };
 
-        final String firstUrl = getCaptivePortalServerHttpUrl();
+        final String firstUrl = getCaptivePortalServerHttpUrl(context);
         try {
             final URL[] settingProviderUrls =
                     combineCaptivePortalUrls(firstUrl, CAPTIVE_PORTAL_OTHER_HTTP_URLS);
             // firstUrl will at least be default configuration, so default value in
             // getProbeUrlArrayConfig is actually never used.
-            return getProbeUrlArrayConfig(settingProviderUrls,
+            return getProbeUrlArrayConfig(context, settingProviderUrls,
                     R.array.config_captive_portal_http_urls,
                     DEFAULT_CAPTIVE_PORTAL_HTTP_URLS, this::makeURL);
         } catch (Exception e) {
@@ -2143,21 +2269,20 @@
      * <p>The configuration resource is prioritized, then the provider value.
      * @param context The context
      * @param configResource The resource id for the configuration parameter
-     * @param symbol The symbol in the settings provider
+     * @param settingValue The value in the settings provider
      * @param defaultValue The default value
      * @return The best available value
      */
     @Nullable
     private String getSettingFromResource(@NonNull final Context context,
-            @StringRes int configResource, @NonNull String symbol, @NonNull String defaultValue) {
+            @StringRes int configResource, @NonNull String settingValue,
+            @NonNull String defaultValue) {
         final Resources res = context.getResources();
         String setting = res.getString(configResource);
 
         if (!TextUtils.isEmpty(setting)) return setting;
 
-        setting = mDependencies.getSetting(context, symbol, null);
-
-        if (!TextUtils.isEmpty(setting)) return setting;
+        if (!TextUtils.isEmpty(settingValue)) return settingValue;
 
         return defaultValue;
     }
@@ -2167,17 +2292,20 @@
      *
      * <p>The configuration resource is prioritized, then the provider values, then the default
      * resource values.
+     *
+     * @param context The Context
      * @param providerValue Values obtained from the setting provider.
      * @param configResId ID of the configuration resource.
      * @param defaultResId ID of the default resource.
      * @param resourceConverter Converter from the resource strings to stored setting class. Null
      *                          return values are ignored.
      */
-    private <T> T[] getProbeUrlArrayConfig(@NonNull T[] providerValue, @ArrayRes int configResId,
-            @ArrayRes int defaultResId, @NonNull Function<String, T> resourceConverter) {
-        final Resources res = mCustomizedContext.getResources();
-        return getProbeUrlArrayConfig(providerValue, configResId, res.getStringArray(defaultResId),
-                resourceConverter);
+    private <T> T[] getProbeUrlArrayConfig(@NonNull Context context, @NonNull T[] providerValue,
+            @ArrayRes int configResId, @ArrayRes int defaultResId,
+            @NonNull Function<String, T> resourceConverter) {
+        final Resources res = context.getResources();
+        return getProbeUrlArrayConfig(context, providerValue, configResId,
+                res.getStringArray(defaultResId), resourceConverter);
     }
 
     /**
@@ -2185,15 +2313,18 @@
      *
      * <p>The configuration resource is prioritized, then the provider values, then the default
      * resource values.
+     *
+     * @param context The Context
      * @param providerValue Values obtained from the setting provider.
      * @param configResId ID of the configuration resource.
      * @param defaultConfig Values of default configuration.
      * @param resourceConverter Converter from the resource strings to stored setting class. Null
      *                          return values are ignored.
      */
-    private <T> T[] getProbeUrlArrayConfig(@NonNull T[] providerValue, @ArrayRes int configResId,
-            String[] defaultConfig, @NonNull Function<String, T> resourceConverter) {
-        final Resources res = mCustomizedContext.getResources();
+    private <T> T[] getProbeUrlArrayConfig(@NonNull Context context, @NonNull T[] providerValue,
+            @ArrayRes int configResId, String[] defaultConfig,
+            @NonNull Function<String, T> resourceConverter) {
+        final Resources res = context.getResources();
         String[] configValue = res.getStringArray(configResId);
 
         if (configValue.length == 0) {
@@ -2272,15 +2403,14 @@
         }
     }
 
-    private CaptivePortalProbeResult isCaptivePortal(ValidationProperties properties) {
+    private CaptivePortalProbeResult isCaptivePortal(ValidationProperties properties,
+            URL[] httpsUrls, URL[] httpUrls, URL fallbackUrl) {
         if (!mIsCaptivePortalCheckEnabled) {
             validationLog("Validation disabled.");
             return CaptivePortalProbeResult.success(CaptivePortalProbeResult.PROBE_UNKNOWN);
         }
 
         URL pacUrl = null;
-        final URL[] httpsUrls = mCaptivePortalHttpsUrls;
-        final URL[] httpUrls = mCaptivePortalHttpUrls;
 
         // On networks with a PAC instead of fetching a URL that should result in a 204
         // response, we instead simply fetch the PAC script.  This is done for a few reasons:
@@ -2321,7 +2451,7 @@
         } else if (mUseHttps && httpsUrls.length == 1 && httpUrls.length == 1) {
             // Probe results are reported inside sendHttpAndHttpsParallelWithFallbackProbes.
             result = sendHttpAndHttpsParallelWithFallbackProbes(properties, proxyInfo,
-                    httpsUrls[0], httpUrls[0]);
+                    httpsUrls[0], httpUrls[0], fallbackUrl);
         } else if (mUseHttps) {
             // Support result aggregation from multiple Urls.
             result = sendMultiParallelHttpAndHttpsProbes(properties, proxyInfo, httpsUrls,
@@ -2333,10 +2463,6 @@
 
         long endTime = SystemClock.elapsedRealtime();
 
-        sendNetworkConditionsBroadcast(true /* response received */,
-                result.isPortal() /* isCaptivePortal */,
-                startTime, endTime);
-
         log("isCaptivePortal: isSuccessful()=" + result.isSuccessful()
                 + " isPortal()=" + result.isPortal()
                 + " RedirectUrl=" + result.redirectUrl
@@ -2433,7 +2559,7 @@
         String redirectUrl = null;
         final Stopwatch probeTimer = new Stopwatch().start();
         final int oldTag = TrafficStats.getAndSetThreadStatsTag(
-                TrafficStatsConstants.TAG_SYSTEM_PROBE);
+                NetworkStackConstants.TAG_SYSTEM_PROBE);
         try {
             // Follow redirects for PAC probes as such probes verify connectivity by fetching the
             // PAC proxy file, which may be configured behind a redirect.
@@ -2519,8 +2645,17 @@
 
         final CaptivePortalProbeResult probeResult;
         if (probeSpec == null) {
+            if (CaptivePortalProbeResult.isPortalCode(httpResponseCode)
+                    && TextUtils.isEmpty(redirectUrl)
+                    && ShimUtils.isAtLeastS()) {
+                // If a portal is a non-redirect portal (often portals that return HTTP 200 with a
+                // login page for all HTTP requests), report the probe URL as the login URL starting
+                // from S (b/172048052). This avoids breaking assumptions that
+                // [is a portal] is equivalent to [there is a login URL].
+                redirectUrl = url.toString();
+            }
             probeResult = new CaptivePortalProbeResult(httpResponseCode, redirectUrl,
-                    url.toString(),   1 << probeType);
+                    url.toString(), 1 << probeType);
         } else {
             probeResult = probeSpec.getResult(httpResponseCode, redirectUrl);
         }
@@ -2918,7 +3053,8 @@
     }
 
     private CaptivePortalProbeResult sendHttpAndHttpsParallelWithFallbackProbes(
-            ValidationProperties properties, ProxyInfo proxy, URL httpsUrl, URL httpUrl) {
+            ValidationProperties properties, ProxyInfo proxy, URL httpsUrl, URL httpUrl,
+            URL fallbackUrl) {
         // Number of probes to wait for. If a probe completes with a conclusive answer
         // it shortcuts the latch immediately by forcing the count to 0.
         final CountDownLatch latch = new CountDownLatch(2);
@@ -2964,10 +3100,10 @@
         // If a fallback method exists, use it to retry portal detection.
         // If we have new-style probe specs, use those. Otherwise, use the fallback URLs.
         final CaptivePortalProbeSpec probeSpec = nextFallbackSpec();
-        final URL fallbackUrl = (probeSpec != null) ? probeSpec.getUrl() : nextFallbackUrl();
+        final URL fallback = (probeSpec != null) ? probeSpec.getUrl() : fallbackUrl;
         CaptivePortalProbeResult fallbackProbeResult = null;
-        if (fallbackUrl != null) {
-            fallbackProbeResult = sendHttpProbe(fallbackUrl, PROBE_FALLBACK, probeSpec);
+        if (fallback != null) {
+            fallbackProbeResult = sendHttpProbe(fallback, PROBE_FALLBACK, probeSpec);
             reportHttpProbeResult(NETWORK_VALIDATION_PROBE_FALLBACK, fallbackProbeResult);
             if (fallbackProbeResult.isPortal()) {
                 return fallbackProbeResult;
@@ -3006,74 +3142,6 @@
         return null;
     }
 
-    /**
-     * @param responseReceived - whether or not we received a valid HTTP response to our request.
-     * If false, isCaptivePortal and responseTimestampMs are ignored
-     * TODO: This should be moved to the transports.  The latency could be passed to the transports
-     * along with the captive portal result.  Currently the TYPE_MOBILE broadcasts appear unused so
-     * perhaps this could just be added to the WiFi transport only.
-     */
-    private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal,
-            long requestTimestampMs, long responseTimestampMs) {
-        Intent latencyBroadcast =
-                new Intent(NetworkMonitorUtils.ACTION_NETWORK_CONDITIONS_MEASURED);
-        if (mNetworkCapabilities.hasTransport(TRANSPORT_WIFI)) {
-            if (!mWifiManager.isScanAlwaysAvailable()) {
-                return;
-            }
-
-            WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
-            if (currentWifiInfo != null) {
-                // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
-                // surrounded by double quotation marks (thus violating the Javadoc), but this
-                // was changed to match the Javadoc in API 17. Since clients may have started
-                // sanitizing the output of this method since API 17 was released, we should
-                // not change it here as it would become impossible to tell whether the SSID is
-                // simply being surrounded by quotes due to the API, or whether those quotes
-                // are actually part of the SSID.
-                latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_SSID,
-                        currentWifiInfo.getSSID());
-                latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_BSSID,
-                        currentWifiInfo.getBSSID());
-            } else {
-                if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
-                return;
-            }
-            latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_WIFI);
-        } else if (mNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
-            // TODO(b/123893112): Support multi-sim.
-            latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_NETWORK_TYPE,
-                    mTelephonyManager.getNetworkType());
-            final ServiceState dataSs = mTelephonyManager.getServiceState();
-            if (dataSs == null) {
-                logw("failed to retrieve ServiceState");
-                return;
-            }
-            // See if the data sub is registered for PS services on cell.
-            final NetworkRegistrationInfo nri = dataSs.getNetworkRegistrationInfo(
-                    NetworkRegistrationInfo.DOMAIN_PS,
-                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-            latencyBroadcast.putExtra(
-                    NetworkMonitorUtils.EXTRA_CELL_ID,
-                    nri == null ? null : nri.getCellIdentity());
-            latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_MOBILE);
-        } else {
-            return;
-        }
-        latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_RECEIVED,
-                responseReceived);
-        latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_REQUEST_TIMESTAMP_MS,
-                requestTimestampMs);
-
-        if (responseReceived) {
-            latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_IS_CAPTIVE_PORTAL,
-                    isCaptivePortal);
-            latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_TIMESTAMP_MS,
-                    responseTimestampMs);
-        }
-        mDependencies.sendNetworkConditionsBroadcast(mContext, latencyBroadcast);
-    }
-
     private void logNetworkEvent(int evtype) {
         int[] transports = mNetworkCapabilities.getTransportTypes();
         mMetricsLog.log(mCleartextDnsNetwork, transports, new NetworkEvent(evtype));
@@ -3186,15 +3254,6 @@
         }
 
         /**
-         * Send a broadcast indicating network conditions.
-         */
-        public void sendNetworkConditionsBroadcast(@NonNull Context context,
-                @NonNull Intent broadcast) {
-            context.sendBroadcastAsUser(broadcast, UserHandle.CURRENT,
-                    NetworkMonitorUtils.PERMISSION_ACCESS_NETWORK_CONDITIONS);
-        }
-
-        /**
          * Check whether or not one specific experimental feature for a particular namespace from
          * {@link DeviceConfig} is enabled by comparing NetworkStack module version
          * {@link NetworkStack} with current version of property. If this property version is valid,
@@ -3216,7 +3275,7 @@
          * data to statsd pipeline.
          * @param stats a {@link DataStallDetectionStats} that contains the detection level
          *              information.
-         * @para result the network reevaluation result.
+         * @param result the network reevaluation result.
          */
         public void writeDataStallDetectionStats(@NonNull final DataStallDetectionStats stats,
                 @NonNull final CaptivePortalProbeResult result) {
@@ -3277,6 +3336,10 @@
             // considered in the evaluation happened in defined threshold time.
             final long now = SystemClock.elapsedRealtime();
             final long firstTimeoutTime = now - mDnsEvents[firstConsecutiveTimeoutIndex].mTimeStamp;
+            if (DDBG_STALL) {
+                Log.d(TAG, "DSD.isDataStallSuspected, first="
+                        + firstTimeoutTime + ", valid=" + validTime);
+            }
             return (firstTimeoutTime < validTime);
         }
 
@@ -3331,12 +3394,17 @@
 
         int typeToCollect = 0;
         final int notStall = -1;
-        final StringJoiner msg = (DBG || VDBG_STALL) ? new StringJoiner(", ") : null;
+        final StringJoiner msg = (DBG || VDBG_STALL || DDBG_STALL) ? new StringJoiner(", ") : null;
         // Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the
         // possible traffic cost in metered network.
+        final long currentTime = SystemClock.elapsedRealtime();
         if (!mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)
-                && (SystemClock.elapsedRealtime() - getLastProbeTime()
-                < mDataStallMinEvaluateTime)) {
+                && (currentTime - getLastProbeTime() < mDataStallMinEvaluateTime)) {
+            if (DDBG_STALL) {
+                Log.d(TAG, "isDataStall: false, currentTime=" + currentTime
+                        + ", lastProbeTime=" + getLastProbeTime()
+                        + ", MinEvaluateTime=" + mDataStallMinEvaluateTime);
+            }
             return false;
         }
         // Check TCP signal. Suspect it may be a data stall if :
@@ -3349,7 +3417,7 @@
             } else if (tst.isDataStallSuspected()) {
                 typeToCollect |= DATA_STALL_EVALUATION_TYPE_TCP;
             }
-            if (DBG || VDBG_STALL) {
+            if (DBG || VDBG_STALL || DDBG_STALL) {
                 msg.add("tcp packets received=" + tst.getLatestReceivedCount())
                     .add("latest tcp fail rate=" + tst.getLatestPacketFailPercentage());
             }
@@ -3366,7 +3434,7 @@
                 typeToCollect |= DATA_STALL_EVALUATION_TYPE_DNS;
                 logNetworkEvent(NetworkEvent.NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND);
             }
-            if (DBG || VDBG_STALL) {
+            if (DBG || VDBG_STALL || DDBG_STALL) {
                 msg.add("consecutive dns timeout count=" + dsd.getConsecutiveTimeoutCount());
             }
         }
@@ -3391,7 +3459,7 @@
         }
 
         // log only data stall suspected.
-        if ((DBG && (typeToCollect > 0)) || VDBG_STALL) {
+        if ((DBG && (typeToCollect > 0)) || VDBG_STALL || DDBG_STALL) {
             log("isDataStall: result=" + typeToCollect + ", " + msg);
         }
 
@@ -3417,18 +3485,28 @@
     // NETWORK_VALIDATION_RESULT_VALID. But with this scheme, the first two or three validation
     // reports are all failures, because they are "HTTP succeeded but validation not yet passed",
     // "HTTP and HTTPS succeeded but validation not yet passed", etc.
+    // TODO : rename EvaluationState to not contain "State" in the name, as it makes this class
+    // sound like one of the states of the state machine, which it's not.
     @VisibleForTesting
     protected class EvaluationState {
         // The latest validation result for this network. This is a bitmask of
         // INetworkMonitor.NETWORK_VALIDATION_RESULT_* constants.
         private int mEvaluationResult = NETWORK_VALIDATION_RESULT_INVALID;
+
+
+        // Set when the captive portal app said this network should be used as is as a result
+        // of user interaction. The valid bit represents the user's decision to override automatic
+        // determination of whether the network has access to Internet, so in this case the
+        // network is always reported as validated.
+        // TODO : Make ConnectivityService aware of this state, so that it can use the network as
+        // the default without setting the VALIDATED bit, as it's a bit of a lie. This can't be
+        // done on Android <= R where CS can't be updated, but it is doable on S+.
+        private boolean mCaptivePortalWantedAsIs = false;
         // Indicates which probes have succeeded since clearProbeResults was called.
         // This is a bitmask of INetworkMonitor.NETWORK_VALIDATION_PROBE_* constants.
         private int mProbeResults = 0;
         // A bitmask to record which probes are completed.
         private int mProbeCompleted = 0;
-        // The latest redirect URL.
-        private String mRedirectUrl;
 
         protected void clearProbeResults() {
             mProbeResults = 0;
@@ -3462,9 +3540,26 @@
             });
         }
 
+        protected void setCaptivePortalWantedAsIs() {
+            mCaptivePortalWantedAsIs = true;
+        }
+
+        protected boolean isCaptivePortalWantedAsIs() {
+            return mCaptivePortalWantedAsIs;
+        }
+
         protected void reportEvaluationResult(int result, @Nullable String redirectUrl) {
+            if (mCaptivePortalWantedAsIs) {
+                result = NETWORK_VALIDATION_RESULT_VALID;
+            } else if (!isValidationRequired() && mProbeCompleted == 0 && mCallbackVersion >= 11) {
+                // If validation is not required AND no probes were attempted, the validation was
+                // skipped. Report this to ConnectivityService for ConnectivityDiagnostics, but only
+                // if the platform has callback version 11+, as ConnectivityService must also know
+                // how to understand this bit.
+                result |= NETWORK_VALIDATION_RESULT_SKIPPED;
+            }
+
             mEvaluationResult = result;
-            mRedirectUrl = redirectUrl;
             final NetworkTestResultParcelable p = new NetworkTestResultParcelable();
             p.result = result;
             p.probesSucceeded = mProbeResults;
@@ -3566,4 +3661,50 @@
     private static Uri getCaptivePortalApiUrl(LinkProperties lp) {
         return NetworkInformationShimImpl.newInstance().getCaptivePortalApiUrl(lp);
     }
+
+    /**
+     * Check if the network is captive with terms and conditions page
+     * @return true if network is captive with T&C page, false otherwise
+     */
+    private boolean isTermsAndConditionsCaptive(CaptivePortalDataShim captivePortalDataShim) {
+        return captivePortalDataShim != null
+                && captivePortalDataShim.getUserPortalUrl() != null
+                && !TextUtils.isEmpty(captivePortalDataShim.getUserPortalUrl().toString())
+                && captivePortalDataShim.isCaptive()
+                && captivePortalDataShim.getUserPortalUrlSource()
+                == ConstantsShim.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT;
+    }
+
+    private boolean checkAndRenewResourceConfig() {
+        boolean reevaluationNeeded = false;
+
+        final Context customizedContext = getCustomizedContextOrDefault();
+        final URL[] captivePortalHttpsUrls = makeCaptivePortalHttpsUrls(customizedContext);
+        if (!Arrays.equals(mCaptivePortalHttpsUrls, captivePortalHttpsUrls)) {
+            mCaptivePortalHttpsUrls = captivePortalHttpsUrls;
+            reevaluationNeeded = true;
+            log("checkAndRenewResourceConfig: update captive portal https urls to "
+                    + Arrays.toString(mCaptivePortalHttpsUrls));
+        }
+
+        final URL[] captivePortalHttpUrls = makeCaptivePortalHttpUrls(customizedContext);
+        if (!Arrays.equals(mCaptivePortalHttpUrls, captivePortalHttpUrls)) {
+            mCaptivePortalHttpUrls = captivePortalHttpUrls;
+            reevaluationNeeded = true;
+            log("checkAndRenewResourceConfig: update captive portal http urls to "
+                    + Arrays.toString(mCaptivePortalHttpUrls));
+        }
+
+        final URL[] captivePortalFallbackUrls = makeCaptivePortalFallbackUrls(customizedContext);
+        if (!Arrays.equals(mCaptivePortalFallbackUrls, captivePortalFallbackUrls)) {
+            mCaptivePortalFallbackUrls = captivePortalFallbackUrls;
+            // Reset the index since the array is changed.
+            mNextFallbackUrlIndex = 0;
+            reevaluationNeeded = true;
+            log("checkAndRenewResourceConfig: update captive portal fallback urls to"
+                    + Arrays.toString(mCaptivePortalFallbackUrls));
+        }
+
+        return reevaluationNeeded;
+    }
 }
diff --git a/tests/hostdriven/Android.bp b/tests/hostdriven/Android.bp
index 3509f89..6c3de45 100644
--- a/tests/hostdriven/Android.bp
+++ b/tests/hostdriven/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 java_test_host {
     name: "NetworkStackHostTests",
     srcs: ["host/src/**/*.kt"],
diff --git a/tests/hostlib/Android.bp b/tests/hostlib/Android.bp
index 9a88634..189a88c 100644
--- a/tests/hostlib/Android.bp
+++ b/tests/hostlib/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 java_library_host {
     name: "net-host-tests-utils",
     srcs: [
@@ -26,4 +30,4 @@
         "kotlin-test",
         "cts-install-lib-host",
     ],
-}
\ No newline at end of file
+}
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index 2c30b3c..9137c5c 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 java_defaults {
     name: "NetworkStackIntegrationTestsJniDefaults",
     defaults: ["libnetworkstackutilsjni_deps"],
@@ -46,7 +50,6 @@
         "android.test.base",
         "android.test.mock",
     ],
-    jarjar_rules: ":NetworkStackJarJarRules",
     visibility: ["//visibility:private"],
 }
 
@@ -68,6 +71,8 @@
     platform_apis: true,
     test_suites: ["device-tests"],
     min_sdk_version: "29",
+    target_sdk_version: "30",
+    jarjar_rules: ":NetworkStackJarJarRules",
 }
 
 // Network stack next integration tests.
@@ -83,22 +88,7 @@
     certificate: "networkstack",
     platform_apis: true,
     test_suites: ["device-tests"],
-    enabled: false, // Disabled in mainline-prod
-}
-
-// The static lib needs to be jarjared by each module so they do not conflict with each other
-// (e.g. wifi, system server, network stack need to use different package names when including it).
-// Apply NetworkStack jarjar rules to the tests as well so classes in NetworkStaticLibTests have the
-// same package names as in module code.
-android_library {
-    name: "NetworkStackStaticLibTestsLib",
-    platform_apis: true,
-    min_sdk_version: "29",
     jarjar_rules: ":NetworkStackJarJarRules",
-    static_libs: [
-        "NetworkStaticLibTestsLib",
-        "NetdStaticLibTestsLib",
-    ],
 }
 
 // Special version of the network stack tests that includes all tests necessary for code coverage
@@ -108,14 +98,18 @@
     certificate: "networkstack",
     platform_apis: true,
     min_sdk_version: "29",
+    target_sdk_version: "30",
     test_suites: ["device-tests", "mts"],
     test_config: "AndroidTest_Coverage.xml",
     defaults: ["NetworkStackIntegrationTestsJniDefaults"],
     static_libs: [
+        "modules-utils-native-coverage-listener",
         "NetworkStackTestsLib",
         "NetworkStackIntegrationTestsLib",
-        "NetworkStackStaticLibTestsLib",
+        "NetworkStaticLibTestsLib",
+        "NetdStaticLibTestsLib",
     ],
     compile_multilib: "both",
     manifest: "AndroidManifest_coverage.xml",
+    jarjar_rules: ":NetworkStackJarJarRules",
 }
diff --git a/tests/integration/AndroidManifest.xml b/tests/integration/AndroidManifest.xml
index 12f5d7d..bfd3735 100644
--- a/tests/integration/AndroidManifest.xml
+++ b/tests/integration/AndroidManifest.xml
@@ -16,7 +16,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.server.networkstack.integrationtests"
           android:sharedUserId="android.uid.networkstack">
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
 
     <!-- Note: do not add any privileged or signature permissions that are granted
          to the network stack app. Otherwise, the test APK will install, but when the device is
diff --git a/tests/integration/AndroidManifest_coverage.xml b/tests/integration/AndroidManifest_coverage.xml
index 660e42d..fc91e59 100644
--- a/tests/integration/AndroidManifest_coverage.xml
+++ b/tests/integration/AndroidManifest_coverage.xml
@@ -16,7 +16,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.server.networkstack.coverage"
           android:sharedUserId="android.uid.networkstack">
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
 
     <!-- Note: do not add any privileged or signature permissions that are granted
          to the network stack app. Otherwise, the test APK will install, but when the device is
diff --git a/tests/integration/AndroidTest_Coverage.xml b/tests/integration/AndroidTest_Coverage.xml
index e33fa87..3e7361b 100644
--- a/tests/integration/AndroidTest_Coverage.xml
+++ b/tests/integration/AndroidTest_Coverage.xml
@@ -23,5 +23,6 @@
         <option name="package" value="com.android.server.networkstack.coverage" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="hidden-api-checks" value="false"/>
+        <option name="device-listeners" value="com.android.modules.utils.testing.NativeCoverageHackInstrumentationListener" />
     </test>
 </configuration>
diff --git a/tests/integration/lint-baseline.xml b/tests/integration/lint-baseline.xml
new file mode 100644
index 0000000..eadec6f
--- /dev/null
+++ b/tests/integration/lint-baseline.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `android.net.LinkProperties#getDhcpServerAddress`"
+        errorLine1="        assertEquals(SERVER_ADDR, captor.getValue().getDhcpServerAddress());"
+        errorLine2="                                                    ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java"
+            line="1327"
+            column="53"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `android.net.LinkProperties#getNat64Prefix`"
+        errorLine1="                argThat(lp -> Objects.equals(expected, lp.getNat64Prefix())));"
+        errorLine2="                                                          ~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java"
+            line="1623"
+            column="59"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `android.net.LinkProperties#getNat64Prefix`"
+        errorLine1="                lp -> !Objects.equals(unchanged, lp.getNat64Prefix())));"
+        errorLine2="                                                    ~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java"
+            line="1629"
+            column="53"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `android.net.LinkProperties#getNat64Prefix`"
+        errorLine1="        if (lp.getNat64Prefix() != null) {"
+        errorLine2="               ~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java"
+            line="1660"
+            column="16"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `android.net.LinkProperties#getNat64Prefix`"
+        errorLine1="            assertEquals(prefix, lp.getNat64Prefix());"
+        errorLine2="                                    ~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java"
+            line="1661"
+            column="37"/>
+    </issue>
+
+</issues>
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.kt b/tests/integration/src/android/net/ip/IpClientIntegrationTest.kt
index 9f29a2e..748ee5a 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTest.kt
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.kt
@@ -17,7 +17,14 @@
 package android.net.ip
 
 import android.net.ipmemorystore.NetworkAttributes
+import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener
+import android.net.ipmemorystore.Status
+import android.net.ipmemorystore.Status.SUCCESS
+import android.util.ArrayMap
+import java.net.Inet6Address
+import kotlin.test.assertEquals
 import org.mockito.Mockito.any
+import org.mockito.Mockito.doAnswer
 import org.mockito.ArgumentCaptor
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.never
@@ -28,22 +35,20 @@
  * Tests for IpClient, run with signature permissions.
  */
 class IpClientIntegrationTest : IpClientIntegrationTestCommon() {
+    private val mEnabledFeatures = ArrayMap<String, Boolean>()
+
     override fun makeIIpClient(ifaceName: String, cb: IIpClientCallbacks): IIpClient {
         return mIpc.makeConnector()
     }
 
     override fun useNetworkStackSignature() = true
 
-    override fun setDhcpFeatures(
-        isDhcpLeaseCacheEnabled: Boolean,
-        isRapidCommitEnabled: Boolean,
-        isDhcpIpConflictDetectEnabled: Boolean,
-        isIPv6OnlyPreferredEnabled: Boolean
-    ) {
-        mDependencies.setDhcpLeaseCacheEnabled(isDhcpLeaseCacheEnabled)
-        mDependencies.setDhcpRapidCommitEnabled(isRapidCommitEnabled)
-        mDependencies.setDhcpIpConflictDetectEnabled(isDhcpIpConflictDetectEnabled)
-        mDependencies.setIPv6OnlyPreferredEnabled(isIPv6OnlyPreferredEnabled)
+    override fun isFeatureEnabled(name: String, defaultEnabled: Boolean): Boolean {
+        return mEnabledFeatures.get(name) ?: defaultEnabled
+    }
+
+    override fun setFeatureEnabled(name: String, enabled: Boolean) {
+        mEnabledFeatures.put(name, enabled)
     }
 
     override fun getStoredNetworkAttributes(l2Key: String, timeout: Long): NetworkAttributes {
@@ -57,4 +62,23 @@
     override fun assertIpMemoryNeverStoreNetworkAttributes(l2Key: String, timeout: Long) {
         verify(mIpMemoryStore, never()).storeNetworkAttributes(eq(l2Key), any(), any())
     }
+
+    override fun assertNotifyNeighborLost(targetIp: Inet6Address) {
+        val target = ArgumentCaptor.forClass(Inet6Address::class.java)
+
+        verify(mCallback, timeout(TEST_TIMEOUT_MS)).notifyLost(target.capture(), any())
+        assertEquals(targetIp, target.getValue())
+    }
+
+    override fun assertNeverNotifyNeighborLost() {
+        verify(mCallback, never()).notifyLost(any(), any())
+    }
+
+    override fun storeNetworkAttributes(l2Key: String, na: NetworkAttributes) {
+        doAnswer { inv ->
+            val listener = inv.getArgument<OnNetworkAttributesRetrievedListener>(1)
+            listener.onNetworkAttributesRetrieved(Status(SUCCESS), l2Key, na)
+            true
+        }.`when`(mIpMemoryStore).retrieveNetworkAttributes(eq(l2Key), any())
+    }
 }
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
index 1b5660c..6ade54f 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
@@ -27,31 +27,37 @@
 import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
 import static android.net.dhcp.DhcpPacket.MIN_V6ONLY_WAIT_MS;
 import static android.net.dhcp.DhcpResultsParcelableUtil.fromStableParcelable;
+import static android.net.ip.IpReachabilityMonitor.MIN_NUD_SOLICIT_NUM;
+import static android.net.ip.IpReachabilityMonitor.NUD_MCAST_RESOLICIT_NUM;
 import static android.net.ipmemorystore.Status.SUCCESS;
+import static android.net.shared.ProvisioningConfiguration.VERSION_ADDED_PROVISIONING_ENUM;
 import static android.system.OsConstants.ETH_P_IPV6;
 import static android.system.OsConstants.IFA_F_TEMPORARY;
 import static android.system.OsConstants.IPPROTO_ICMPV6;
-import static android.system.OsConstants.IPPROTO_TCP;
 
 import static com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress;
 import static com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address;
 import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY;
 import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST;
 import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST;
 import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
-import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
 import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_OFFSET;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_CHECKSUM_OFFSET;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_RDNSS;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_RA_HEADER_LEN;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
 import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
 import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
-import static com.android.net.module.util.NetworkStackConstants.IPV6_LEN_OFFSET;
 import static com.android.net.module.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
+import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER;
+import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
+import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS;
+import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_ON_LINK;
+import static com.android.testutils.MiscAsserts.assertThrows;
 
 import static junit.framework.Assert.fail;
 
@@ -102,6 +108,7 @@
 import android.net.LinkProperties;
 import android.net.MacAddress;
 import android.net.NetworkStackIpMemoryStore;
+import android.net.RouteInfo;
 import android.net.TestNetworkInterface;
 import android.net.TestNetworkManager;
 import android.net.Uri;
@@ -111,10 +118,10 @@
 import android.net.dhcp.DhcpPacket;
 import android.net.dhcp.DhcpPacket.ParseException;
 import android.net.dhcp.DhcpRequestPacket;
+import android.net.ip.IpNeighborMonitor.NeighborEventConsumer;
 import android.net.ipmemorystore.NetworkAttributes;
 import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener;
 import android.net.ipmemorystore.Status;
-import android.net.netlink.StructNdOptPref64;
 import android.net.networkstack.TestNetworkStackServiceClient;
 import android.net.networkstack.aidl.dhcp.DhcpOption;
 import android.net.shared.Layer2Information;
@@ -122,6 +129,7 @@
 import android.net.shared.ProvisioningConfiguration.ScanResultInfo;
 import android.net.util.InterfaceParams;
 import android.net.util.NetworkStackUtils;
+import android.net.util.SharedLog;
 import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -131,22 +139,31 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.stats.connectivity.NetworkQuirkEvent;
 import android.system.ErrnoException;
 import android.system.Os;
 
 import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.HexDump;
 import com.android.internal.util.StateMachine;
 import com.android.net.module.util.ArrayTrackRecord;
-import com.android.net.module.util.IpUtils;
+import com.android.net.module.util.Ipv6Utils;
+import com.android.net.module.util.netlink.StructNdOptPref64;
+import com.android.net.module.util.structs.LlaOption;
+import com.android.net.module.util.structs.PrefixInformationOption;
+import com.android.net.module.util.structs.RdnssOption;
 import com.android.networkstack.apishim.CaptivePortalDataShimImpl;
 import com.android.networkstack.apishim.ConstantsShim;
 import com.android.networkstack.apishim.common.ShimUtils;
 import com.android.networkstack.arp.ArpPacket;
 import com.android.networkstack.metrics.IpProvisioningMetrics;
+import com.android.networkstack.metrics.IpReachabilityMonitorMetrics;
+import com.android.networkstack.metrics.NetworkQuirkMetrics;
+import com.android.networkstack.packets.NeighborAdvertisement;
+import com.android.networkstack.packets.NeighborSolicitation;
 import com.android.server.NetworkObserver;
 import com.android.server.NetworkObserverRegistry;
 import com.android.server.NetworkStackService.NetworkStackServiceManager;
@@ -163,6 +180,7 @@
 import org.junit.Test;
 import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
@@ -180,6 +198,7 @@
 import java.lang.annotation.Target;
 import java.lang.reflect.Method;
 import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.nio.ByteBuffer;
@@ -205,7 +224,7 @@
  *
  * Tests in this class can either be run with signature permissions, or with root access.
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 @SmallTest
 public abstract class IpClientIntegrationTestCommon {
     private static final int DATA_BUFFER_LEN = 4096;
@@ -228,6 +247,17 @@
     @Rule
     public final TestName mTestNameRule = new TestName();
 
+    // Indicate whether the flag of parsing netlink event is enabled or not. If it's disabled,
+    // integration test still covers the old codepath(i.e. using NetworkObserver), otherwise,
+    // test goes through the new codepath(i.e. processRtNetlinkxxx).
+    @Parameterized.Parameter(0)
+    public boolean mIsNetlinkEventParseEnabled;
+
+    @Parameterized.Parameters
+    public static Iterable<? extends Object> data() {
+        return Arrays.asList(Boolean.valueOf("false"), Boolean.valueOf("true"));
+    }
+
     /**
      * Indicates that a test requires signature permissions to run.
      *
@@ -258,6 +288,9 @@
     @Mock private IpMemoryStoreService mIpMemoryStoreService;
     @Mock private PowerManager.WakeLock mTimeoutWakeLock;
     @Mock protected NetworkStackIpMemoryStore mIpMemoryStore;
+    @Mock private NetworkQuirkMetrics.Dependencies mNetworkQuirkMetricsDeps;
+    @Mock private IpReachabilityMonitorMetrics mIpReachabilityMonitorMetrics;
+    @Mock protected IpReachabilityMonitor.Callback mCallback;
 
     @Spy private INetd mNetd;
     private NetworkObserverRegistry mNetworkObserverRegistry;
@@ -267,7 +300,7 @@
 
     /***** END signature required test members *****/
 
-    private IIpClientCallbacks mCb;
+    protected IIpClientCallbacks mCb;
     private IIpClient mIIpClient;
     private String mIfaceName;
     private HandlerThread mPacketReaderThread;
@@ -322,7 +355,10 @@
     private static final String HOSTNAME = "testhostname";
     private static final int TEST_DEFAULT_MTU = 1500;
     private static final int TEST_MIN_MTU = 1280;
-    private static final byte[] SERVER_MAC = new byte[] { 0x00, 0x1A, 0x11, 0x22, 0x33, 0x44 };
+    private static final MacAddress ROUTER_MAC = MacAddress.fromString("00:1A:11:22:33:44");
+    private static final byte[] ROUTER_MAC_BYTES = ROUTER_MAC.toByteArray();
+    private static final Inet6Address ROUTER_LINK_LOCAL =
+                (Inet6Address) InetAddresses.parseNumericAddress("fe80::1");
     private static final String TEST_HOST_NAME = "AOSP on Crosshatch";
     private static final String TEST_HOST_NAME_TRANSLITERATION = "AOSP-on-Crosshatch";
     private static final String TEST_CAPTIVE_PORTAL_URL = "https://example.com/capportapi";
@@ -348,32 +384,12 @@
     };
 
     protected class Dependencies extends IpClient.Dependencies {
-        private boolean mIsDhcpLeaseCacheEnabled;
-        private boolean mIsDhcpRapidCommitEnabled;
-        private boolean mIsDhcpIpConflictDetectEnabled;
         // 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<>();
         private DhcpClient mDhcpClient;
         private boolean mIsHostnameConfigurationEnabled;
         private String mHostname;
         private boolean mIsInterfaceRecovered;
-        private boolean mIsIPv6OnlyPreferredEnabled;
-
-        public void setDhcpLeaseCacheEnabled(final boolean enable) {
-            mIsDhcpLeaseCacheEnabled = enable;
-        }
-
-        public void setDhcpRapidCommitEnabled(final boolean enable) {
-            mIsDhcpRapidCommitEnabled = enable;
-        }
-
-        public void setDhcpIpConflictDetectEnabled(final boolean enable) {
-            mIsDhcpIpConflictDetectEnabled = enable;
-        }
-
-        public void setIPv6OnlyPreferredEnabled(final boolean enable) {
-            mIsIPv6OnlyPreferredEnabled = enable;
-        }
 
         public void setHostnameConfiguration(final boolean enable, final String hostname) {
             mIsHostnameConfigurationEnabled = enable;
@@ -416,25 +432,28 @@
         }
 
         @Override
+        public IpReachabilityMonitor getIpReachabilityMonitor(Context context,
+                InterfaceParams ifParams, Handler h, SharedLog log,
+                IpReachabilityMonitor.Callback callback, boolean usingMultinetworkPolicyTracker,
+                IpReachabilityMonitor.Dependencies deps, final INetd netd) {
+            return new IpReachabilityMonitor(context, ifParams, h, log, mCallback,
+                    usingMultinetworkPolicyTracker, deps, netd);
+        }
+
+        @Override
+        public boolean isFeatureEnabled(final Context context, final String name,
+                final boolean defaultEnabled) {
+            return IpClientIntegrationTestCommon.this.isFeatureEnabled(name, defaultEnabled);
+        }
+
+        @Override
         public DhcpClient.Dependencies getDhcpClientDependencies(
                 NetworkStackIpMemoryStore ipMemoryStore, IpProvisioningMetrics metrics) {
             return new DhcpClient.Dependencies(ipMemoryStore, metrics) {
                 @Override
                 public boolean isFeatureEnabled(final Context context, final String name,
                         final boolean defaultEnabled) {
-                    switch (name) {
-                        case NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION:
-                            return mIsDhcpRapidCommitEnabled;
-                        case NetworkStackUtils.DHCP_INIT_REBOOT_VERSION:
-                            return mIsDhcpLeaseCacheEnabled;
-                        case NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION:
-                            return mIsDhcpIpConflictDetectEnabled;
-                        case NetworkStackUtils.DHCP_IPV6_ONLY_PREFERRED_VERSION:
-                            return mIsIPv6OnlyPreferredEnabled;
-                        default:
-                            fail("Invalid experiment flag: " + name);
-                            return false;
-                    }
+                    return Dependencies.this.isFeatureEnabled(context, name, defaultEnabled);
                 }
 
                 @Override
@@ -461,6 +480,32 @@
         }
 
         @Override
+        public IpReachabilityMonitor.Dependencies getIpReachabilityMonitorDeps(Context context,
+                String name) {
+            return new IpReachabilityMonitor.Dependencies() {
+                public void acquireWakeLock(long durationMs) {
+                    // It doesn't matter for the integration test app on whether the wake lock
+                    // is acquired or not.
+                    return;
+                }
+
+                public IpNeighborMonitor makeIpNeighborMonitor(Handler h, SharedLog log,
+                        NeighborEventConsumer cb) {
+                    return new IpNeighborMonitor(h, log, cb);
+                }
+
+                public boolean isFeatureEnabled(final Context context, final String name,
+                        boolean defaultEnabled) {
+                    return Dependencies.this.isFeatureEnabled(context, name, defaultEnabled);
+                }
+
+                public IpReachabilityMonitorMetrics getIpReachabilityMonitorMetrics() {
+                    return mIpReachabilityMonitorMetrics;
+                }
+            };
+        }
+
+        @Override
         public int getDeviceConfigPropertyInt(String name, int defaultValue) {
             Integer value = mIntConfigProperties.get(name);
             if (value == null) {
@@ -472,22 +517,33 @@
         public void setDeviceConfigProperty(String name, int value) {
             mIntConfigProperties.put(name, value);
         }
+
+        @Override
+        public NetworkQuirkMetrics getNetworkQuirkMetrics() {
+            return new NetworkQuirkMetrics(mNetworkQuirkMetricsDeps);
+        }
     }
 
     @NonNull
     protected abstract IIpClient makeIIpClient(
             @NonNull String ifaceName, @NonNull IIpClientCallbacks cb);
 
-    protected abstract void setDhcpFeatures(boolean isDhcpLeaseCacheEnabled,
-            boolean isRapidCommitEnabled, boolean isDhcpIpConflictDetectEnabled,
-            boolean isIPv6OnlyPreferredEnabled);
+    protected abstract void setFeatureEnabled(String name, boolean enabled);
+
+    protected abstract boolean isFeatureEnabled(String name, boolean defaultEnabled);
 
     protected abstract boolean useNetworkStackSignature();
 
     protected abstract NetworkAttributes getStoredNetworkAttributes(String l2Key, long timeout);
 
+    protected abstract void storeNetworkAttributes(String l2Key, NetworkAttributes na);
+
     protected abstract void assertIpMemoryNeverStoreNetworkAttributes(String l2Key, long timeout);
 
+    protected abstract void assertNotifyNeighborLost(Inet6Address targetIp);
+
+    protected abstract void assertNeverNotifyNeighborLost();
+
     protected final boolean testSkipped() {
         // TODO: split out a test suite for root tests, and fail hard instead of skipping the test
         // if it is run on devices where TestNetworkStackServiceClient is not supported
@@ -495,10 +551,27 @@
                 && (mIsSignatureRequiredTest || !TestNetworkStackServiceClient.isSupported());
     }
 
+    protected void setDhcpFeatures(final boolean isDhcpLeaseCacheEnabled,
+            final boolean isRapidCommitEnabled, final boolean isDhcpIpConflictDetectEnabled,
+            final boolean isIPv6OnlyPreferredEnabled) {
+        setFeatureEnabled(NetworkStackUtils.DHCP_INIT_REBOOT_VERSION, isDhcpLeaseCacheEnabled);
+        setFeatureEnabled(NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION, isRapidCommitEnabled);
+        setFeatureEnabled(NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION,
+                isDhcpIpConflictDetectEnabled);
+        setFeatureEnabled(NetworkStackUtils.DHCP_IPV6_ONLY_PREFERRED_VERSION,
+                isIPv6OnlyPreferredEnabled);
+    }
+
     @Before
     public void setUp() throws Exception {
-        final Method testMethod = IpClientIntegrationTestCommon.class.getMethod(
-                mTestNameRule.getMethodName());
+        // Suffix "[0]" or "[1]" is added to the end of test method name after running with
+        // Parameterized.class, that's intended behavior, to iterate each test method with the
+        // parameterize value. However, Class#getMethod() throws NoSuchMethodException when
+        // searching the target test method name due to this change. Just keep the original test
+        // method name to fix NoSuchMethodException, and find the correct annotation associated
+        // to test method.
+        final String testMethodName = mTestNameRule.getMethodName().split("\\[")[0];
+        final Method testMethod = IpClientIntegrationTestCommon.class.getMethod(testMethodName);
         mIsSignatureRequiredTest = testMethod.getAnnotation(SignatureRequiredTest.class) != null;
         assumeFalse(testSkipped());
 
@@ -511,6 +584,12 @@
         }
 
         mIIpClient = makeIIpClient(mIfaceName, mCb);
+
+        // Depend on the parameterized value to enable/disable netlink message refactor flag.
+        // Make sure both of the old codepath(rely on the INetdUnsolicitedEventListener aidl)
+        // and new codepath(parse netlink event from kernel) will be executed.
+        setFeatureEnabled(NetworkStackUtils.IPCLIENT_PARSE_NETLINK_EVENTS_VERSION,
+                mIsNetlinkEventParseEnabled /* default value */);
     }
 
     protected void setUpMocks() throws Exception {
@@ -523,6 +602,7 @@
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
         when(mNetworkStackServiceManager.getIpMemoryStoreService())
                 .thenReturn(mIpMemoryStoreService);
+        when(mCb.getInterfaceVersion()).thenReturn(VERSION_ADDED_PROVISIONING_ENUM);
 
         mDependencies.setDeviceConfigProperty(IpClient.CONFIG_MIN_RDNSS_LIFETIME, 67);
         mDependencies.setDeviceConfigProperty(DhcpClient.DHCP_RESTART_CONFIG_DELAY, 10);
@@ -709,6 +789,22 @@
         }
     }
 
+    private NeighborAdvertisement parseNeighborAdvertisementOrNull(final byte[] packet) {
+        try {
+            return NeighborAdvertisement.parse(packet, packet.length);
+        } catch (NeighborAdvertisement.ParseException e) {
+            return null;
+        }
+    }
+
+    private NeighborSolicitation parseNeighborSolicitationOrNull(final byte[] packet) {
+        try {
+            return NeighborSolicitation.parse(packet, packet.length);
+        } catch (NeighborSolicitation.ParseException e) {
+            return null;
+        }
+    }
+
     private static ByteBuffer buildDhcpOfferPacket(final DhcpPacket packet,
             final Inet4Address clientAddress, final Integer leaseTimeSec, final short mtu,
             final String captivePortalUrl, final Integer ipv6OnlyWaitTime) {
@@ -758,7 +854,7 @@
 
     private void sendArpReply(final byte[] clientMac) throws IOException {
         final ByteBuffer packet = ArpPacket.buildArpPacket(clientMac /* dst */,
-                SERVER_MAC /* src */, INADDR_ANY.getAddress() /* target IP */,
+                ROUTER_MAC_BYTES /* srcMac */, INADDR_ANY.getAddress() /* target IP */,
                 clientMac /* target HW address */, CLIENT_ADDR.getAddress() /* sender IP */,
                 (short) ARP_REPLY);
         mPacketReader.sendResponse(packet);
@@ -766,7 +862,7 @@
 
     private void sendArpProbe() throws IOException {
         final ByteBuffer packet = ArpPacket.buildArpPacket(DhcpPacket.ETHER_BROADCAST /* dst */,
-                SERVER_MAC /* src */, CLIENT_ADDR.getAddress() /* target IP */,
+                ROUTER_MAC_BYTES /* srcMac */, CLIENT_ADDR.getAddress() /* target IP */,
                 new byte[ETHER_ADDR_LEN] /* target HW address */,
                 INADDR_ANY.getAddress() /* sender IP */, (short) ARP_REQUEST);
         mPacketReader.sendResponse(packet);
@@ -779,11 +875,14 @@
     private void startIpClientProvisioning(final boolean isDhcpLeaseCacheEnabled,
             final boolean shouldReplyRapidCommitAck, final boolean isPreconnectionEnabled,
             final boolean isDhcpIpConflictDetectEnabled, final boolean isIPv6OnlyPreferredEnabled,
-            final String displayName, final ScanResultInfo scanResultInfo) throws Exception {
+            final String displayName, final ScanResultInfo scanResultInfo,
+            final Layer2Information layer2Info) throws Exception {
         ProvisioningConfiguration.Builder prov = new ProvisioningConfiguration.Builder()
                 .withoutIpReachabilityMonitor()
-                .withLayer2Information(new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
-                        MacAddress.fromString(TEST_DEFAULT_BSSID)))
+                .withLayer2Information(layer2Info == null
+                        ? new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+                              MacAddress.fromString(TEST_DEFAULT_BSSID))
+                        : layer2Info)
                 .withoutIPv6();
         if (isPreconnectionEnabled) prov.withPreconnection();
         if (displayName != null) prov.withDisplayName(displayName);
@@ -805,7 +904,7 @@
             throws Exception {
         startIpClientProvisioning(isDhcpLeaseCacheEnabled, isDhcpRapidCommitEnabled,
                 isPreconnectionEnabled, isDhcpIpConflictDetectEnabled, isIPv6OnlyPreferredEnabled,
-                null /* displayName */, null /* ScanResultInfo */);
+                null /* displayName */, null /* ScanResultInfo */, null /* layer2Info */);
     }
 
     private void assertIpMemoryStoreNetworkAttributes(final Integer leaseTimeSec,
@@ -860,10 +959,11 @@
             final boolean isDhcpIpConflictDetectEnabled,
             final boolean isIPv6OnlyPreferredEnabled,
             final String captivePortalApiUrl, final String displayName,
-            final ScanResultInfo scanResultInfo) throws Exception {
+            final ScanResultInfo scanResultInfo, final Layer2Information layer2Info)
+            throws Exception {
         startIpClientProvisioning(isDhcpLeaseCacheEnabled, shouldReplyRapidCommitAck,
                 false /* isPreconnectionEnabled */, isDhcpIpConflictDetectEnabled,
-                isIPv6OnlyPreferredEnabled, displayName, scanResultInfo);
+                isIPv6OnlyPreferredEnabled, displayName, scanResultInfo, layer2Info);
         return handleDhcpPackets(isSuccessLease, leaseTimeSec, shouldReplyRapidCommitAck, mtu,
                 captivePortalApiUrl);
     }
@@ -909,7 +1009,8 @@
         return performDhcpHandshake(isSuccessLease, leaseTimeSec, isDhcpLeaseCacheEnabled,
                 isDhcpRapidCommitEnabled, mtu, isDhcpIpConflictDetectEnabled,
                 false /* isIPv6OnlyPreferredEnabled */,
-                null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */);
+                null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */,
+                null /* layer2Info */);
     }
 
     private List<DhcpPacket> performDhcpHandshake() throws Exception {
@@ -918,10 +1019,21 @@
                 TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
     }
 
-    private DhcpPacket getNextDhcpPacket() throws ParseException {
-        byte[] packet = mDhcpPacketReadHead.getValue().poll(PACKET_TIMEOUT_MS, this::isDhcpPacket);
+    private DhcpPacket getNextDhcpPacket(final long timeout) throws Exception {
+        byte[] packet;
+        while ((packet = mDhcpPacketReadHead.getValue()
+                .poll(timeout, this::isDhcpPacket)) != null) {
+            final DhcpPacket dhcpPacket = DhcpPacket.decodeFullPacket(packet, packet.length,
+                    ENCAP_L2);
+            if (dhcpPacket != null) return dhcpPacket;
+        }
+        return null;
+    }
+
+    private DhcpPacket getNextDhcpPacket() throws Exception {
+        final DhcpPacket packet = getNextDhcpPacket(PACKET_TIMEOUT_MS);
         assertNotNull("No expected DHCP packet received on interface within timeout", packet);
-        return DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L2);
+        return packet;
     }
 
     private DhcpPacket getReplyFromDhcpLease(final NetworkAttributes na, boolean timeout)
@@ -1127,6 +1239,14 @@
         assertEquals(packet.senderIp, CLIENT_ADDR);
     }
 
+    private void assertGratuitousARP(final ArpPacket packet) {
+        assertEquals(packet.opCode, ARP_REPLY);
+        assertEquals(packet.senderIp, CLIENT_ADDR);
+        assertEquals(packet.targetIp, CLIENT_ADDR);
+        assertTrue(Arrays.equals(packet.senderHwAddress.toByteArray(), mClientMac));
+        assertTrue(Arrays.equals(packet.targetHwAddress.toByteArray(), ETHER_BROADCAST));
+    }
+
     private void doIpAddressConflictDetectionTest(final boolean causeIpAddressConflict,
             final boolean shouldReplyRapidCommitAck, final boolean isDhcpIpConflictDetectEnabled,
             final boolean shouldResponseArpReply) throws Exception {
@@ -1431,12 +1551,43 @@
         HandlerUtils.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
     }
 
-    private boolean isRouterSolicitation(final byte[] packetBytes) {
+    private boolean isIcmpv6PacketOfType(final byte[] packetBytes, int type) {
         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;
+                && packet.get(ETHER_HEADER_LEN + IPV6_HEADER_LEN) == (byte) type;
+    }
+
+    private boolean isRouterSolicitation(final byte[] packetBytes) {
+        return isIcmpv6PacketOfType(packetBytes, ICMPV6_ROUTER_SOLICITATION);
+    }
+
+    private boolean isNeighborAdvertisement(final byte[] packetBytes) {
+        return isIcmpv6PacketOfType(packetBytes, ICMPV6_NEIGHBOR_ADVERTISEMENT);
+    }
+
+    private boolean isNeighborSolicitation(final byte[] packetBytes) {
+        return isIcmpv6PacketOfType(packetBytes, ICMPV6_NEIGHBOR_SOLICITATION);
+    }
+
+    private NeighborAdvertisement getNextNeighborAdvertisement() throws ParseException {
+        final byte[] packet = mPacketReader.popPacket(PACKET_TIMEOUT_MS,
+                this::isNeighborAdvertisement);
+        if (packet == null) return null;
+
+        final NeighborAdvertisement na = parseNeighborAdvertisementOrNull(packet);
+        assertNotNull("Invalid neighbour advertisement received", na);
+        return na;
+    }
+
+    private NeighborSolicitation getNextNeighborSolicitation() throws ParseException {
+        final byte[] packet = mPacketReader.popPacket(PACKET_TIMEOUT_MS,
+                this::isNeighborSolicitation);
+        if (packet == null) return null;
+
+        final NeighborSolicitation ns = parseNeighborSolicitationOrNull(packet);
+        assertNotNull("Invalid neighbour solicitation received", ns);
+        return ns;
     }
 
     private void waitForRouterSolicitation() throws ParseException {
@@ -1468,111 +1619,26 @@
     // 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;
+        return PrefixInformationOption.build(new IpPrefix(prefixString),
+                (byte) (PIO_FLAG_ON_LINK | PIO_FLAG_AUTONOMOUS), valid, preferred);
     }
 
     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;
+        return RdnssOption.build(lifetime, servers);
     }
 
-    // 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;
-    }
-
-    public static int uint16(short s) {
-        return s & 0xffff;
-    }
-
-    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 buildSllaOption() throws Exception {
+        return LlaOption.build((byte) ICMPV6_ND_OPTION_SLLA, ROUTER_MAC);
     }
 
     private static ByteBuffer buildRaPacket(short lifetime, 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(lifetime);                       // 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;
+        final MacAddress dstMac =
+                NetworkStackUtils.ipv6MulticastToEthernetMulticast(IPV6_ADDR_ALL_ROUTERS_MULTICAST);
+        return Ipv6Utils.buildRaPacket(ROUTER_MAC /* srcMac */, dstMac,
+                ROUTER_LINK_LOCAL /* srcIp */, IPV6_ADDR_ALL_NODES_MULTICAST /* dstIp */,
+                (byte) 0 /* M=0, O=0 */, lifetime, 0 /* Reachable time, unspecified */,
+                100 /* Retrans time 100ms */, options);
     }
 
     private static ByteBuffer buildRaPacket(ByteBuffer... options) throws Exception {
@@ -1611,6 +1677,17 @@
         return addr.isGlobalPreferred() && hasFlag(addr, flag);
     }
 
+    private LinkProperties doIpv6OnlyProvisioning() throws Exception {
+        final InOrder inOrder = inOrder(mCb);
+        final String dnsServer = "2001:4860:4860::64";
+        final ByteBuffer pio = buildPioOption(3600, 1800, "2001:db8:1::/64");
+        final ByteBuffer rdnss = buildRdnssOption(3600, dnsServer);
+        final ByteBuffer slla = buildSllaOption();
+        final ByteBuffer ra = buildRaPacket(pio, rdnss, slla);
+
+        return doIpv6OnlyProvisioning(inOrder, ra);
+    }
+
     private LinkProperties doIpv6OnlyProvisioning(InOrder inOrder, ByteBuffer ra) throws Exception {
         waitForRouterSolicitation();
         mPacketReader.sendResponse(ra);
@@ -2031,6 +2108,52 @@
     }
 
     @Test
+    @SignatureRequiredTest(reason = "needs mocked alarm and access to IpClient handler thread")
+    public void testDhcpClientPreconnection_DelayedAbortAndTransitToStoppedState()
+            throws Exception {
+        ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+                .withoutIpReachabilityMonitor()
+                .withPreconnection()
+                .build();
+        setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */,
+                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
+        startIpClientProvisioning(config);
+        assertDiscoverPacketOnPreconnectionStart();
+
+        // IpClient is in the PreconnectingState, simulate provisioning timeout event
+        // and force IpClient state machine transit to StoppingState.
+        final ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class);
+        final OnAlarmListener alarm = expectAlarmSet(null /* inOrder */, "TIMEOUT", 18,
+                mIpc.getHandler());
+        mIpc.getHandler().post(() -> alarm.onAlarm());
+
+        verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningFailure(captor.capture());
+        final LinkProperties lp = captor.getValue();
+        assertNotNull(lp);
+        assertEquals(mIfaceName, lp.getInterfaceName());
+        assertEquals(0, lp.getLinkAddresses().size());
+        assertEquals(0, lp.getRoutes().size());
+        assertEquals(0, lp.getMtu());
+        assertEquals(0, lp.getDnsServers().size());
+
+        // Send preconnection abort message, but IpClient should ignore it at this moment and
+        // transit to StoppedState finally.
+        mIpc.notifyPreconnectionComplete(false /* abort */);
+        mIpc.stop();
+        HandlerUtils.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
+
+        reset(mCb);
+
+        // Start provisioning again to verify IpClient can process CMD_START correctly at
+        // StoppedState.
+        startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */,
+                false /* shouldReplyRapidCommitAck */, false /* isPreConnectionEnabled */,
+                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
+        final DhcpPacket discover = getNextDhcpPacket();
+        assertTrue(discover instanceof DhcpDiscoverPacket);
+    }
+
+    @Test
     public void testDhcpDecline_conflictByArpReply() throws Exception {
         doIpAddressConflictDetectionTest(true /* causeIpAddressConflict */,
                 false /* shouldReplyRapidCommitAck */, true /* isDhcpIpConflictDetectEnabled */,
@@ -2095,9 +2218,8 @@
         final List<DhcpPacket> sentPackets = performDhcpHandshake(true /* isSuccessLease */,
                 TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */,
                 false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU,
-                false /* isDhcpIpConflictDetectEnabled */,
-                false /* isIPv6OnlyPreferredEnabled */,
-                null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */);
+                false /* isDhcpIpConflictDetectEnabled */);
+
         assertEquals(2, sentPackets.size());
         verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
         assertHostname(true, TEST_HOST_NAME, TEST_HOST_NAME_TRANSLITERATION, sentPackets);
@@ -2113,9 +2235,8 @@
         final List<DhcpPacket> sentPackets = performDhcpHandshake(true /* isSuccessLease */,
                 TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */,
                 false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU,
-                false /* isDhcpIpConflictDetectEnabled */,
-                false /* isIPv6OnlyPreferredEnabled */,
-                null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */);
+                false /* isDhcpIpConflictDetectEnabled */);
+
         assertEquals(2, sentPackets.size());
         verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
         assertHostname(false, TEST_HOST_NAME, TEST_HOST_NAME_TRANSLITERATION, sentPackets);
@@ -2131,9 +2252,8 @@
         final List<DhcpPacket> sentPackets = performDhcpHandshake(true /* isSuccessLease */,
                 TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */,
                 false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU,
-                false /* isDhcpIpConflictDetectEnabled */,
-                false /* isIPv6OnlyPreferredEnabled */,
-                null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */);
+                false /* isDhcpIpConflictDetectEnabled */);
+
         assertEquals(2, sentPackets.size());
         verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
         assertHostname(true, null /* hostname */, null /* hostnameAfterTransliteration */,
@@ -2230,7 +2350,8 @@
                 false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU,
                 false /* isDhcpIpConflictDetectEnabled */,
                 false /* isIPv6OnlyPreferredEnabled */,
-                null /* captivePortalApiUrl */, displayName, info /* scanResultInfo */);
+                null /* captivePortalApiUrl */, displayName, info /* scanResultInfo */,
+                null /* layer2Info */);
         assertEquals(2, sentPackets.size());
         verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
 
@@ -2308,11 +2429,18 @@
                 true /* expectMetered */);
     }
 
+    private void forceLayer2Roaming() throws Exception {
+        final Layer2InformationParcelable roamingInfo = new Layer2InformationParcelable();
+        roamingInfo.bssid = MacAddress.fromString(TEST_DHCP_ROAM_BSSID);
+        roamingInfo.l2Key = TEST_DHCP_ROAM_L2KEY;
+        roamingInfo.cluster = TEST_DHCP_ROAM_CLUSTER;
+        mIIpClient.updateLayer2Information(roamingInfo);
+    }
+
     private void doDhcpRoamingTest(final boolean hasMismatchedIpAddress, final String displayName,
-            final String ssid, final String bssid, final boolean expectRoaming) throws Exception {
+            final MacAddress bssid, final boolean expectRoaming) throws Exception {
         long currentTime = System.currentTimeMillis();
-        final ScanResultInfo scanResultInfo = (ssid == null || bssid == null)
-                ? null : makeScanResultInfo(ssid, bssid);
+        final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER, bssid);
 
         doAnswer(invocation -> {
             // we don't rely on the Init-Reboot state to renew previous cached IP lease.
@@ -2329,16 +2457,13 @@
                 true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */,
                 TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */,
                 false /* isIPv6OnlyPreferredEnabled */,
-                null /* captivePortalApiUrl */, displayName, scanResultInfo);
+                null /* captivePortalApiUrl */, displayName, null /* scanResultInfo */,
+                layer2Info);
         verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
         assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
 
         // simulate the roaming by updating bssid.
-        final Layer2InformationParcelable roamingInfo = new Layer2InformationParcelable();
-        roamingInfo.bssid = MacAddress.fromString(TEST_DHCP_ROAM_BSSID);
-        roamingInfo.l2Key = TEST_DHCP_ROAM_L2KEY;
-        roamingInfo.cluster = TEST_DHCP_ROAM_CLUSTER;
-        mIpc.updateLayer2Information(roamingInfo);
+        forceLayer2Roaming();
 
         currentTime = System.currentTimeMillis();
         reset(mIpMemoryStore);
@@ -2379,57 +2504,41 @@
     @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpRoaming() throws Exception {
         doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
-                TEST_DHCP_ROAM_SSID, TEST_DEFAULT_BSSID, true /* expectRoaming */);
+                MacAddress.fromString(TEST_DEFAULT_BSSID), true /* expectRoaming */);
     }
 
     @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpRoaming_invalidBssid() throws Exception {
         doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
-                TEST_DHCP_ROAM_SSID, TEST_DHCP_ROAM_BSSID, false /* expectRoaming */);
+                MacAddress.fromString(TEST_DHCP_ROAM_BSSID), false /* expectRoaming */);
     }
 
     @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
-    public void testDhcpRoaming_nullScanResultInfo() throws Exception {
+    public void testDhcpRoaming_nullBssid() throws Exception {
         doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
-                null /* SSID */, null /* BSSID */, false /* expectRoaming */);
-    }
-
-    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
-    public void testDhcpRoaming_invalidSsid() throws Exception {
-        doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
-                TEST_DEFAULT_SSID, TEST_DEFAULT_BSSID, false /* expectRoaming */);
+                null /* BSSID */, false /* expectRoaming */);
     }
 
     @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpRoaming_invalidDisplayName() throws Exception {
         doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"test-ssid\"" /* display name */,
-                TEST_DHCP_ROAM_SSID, TEST_DEFAULT_BSSID, false /* expectRoaming */);
+                MacAddress.fromString(TEST_DEFAULT_BSSID), false /* expectRoaming */);
     }
 
     @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpRoaming_mismatchedLeasedIpAddress() throws Exception {
         doDhcpRoamingTest(true /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
-                TEST_DHCP_ROAM_SSID, TEST_DEFAULT_BSSID, true /* expectRoaming */);
+                MacAddress.fromString(TEST_DEFAULT_BSSID), true /* expectRoaming */);
     }
 
-    private void doDualStackProvisioning() throws Exception {
-        when(mCm.shouldAvoidBadWifi()).thenReturn(true);
-
-        final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
-                .withoutIpReachabilityMonitor()
-                .build();
-        // Enable rapid commit to accelerate DHCP handshake to shorten test duration,
-        // not strictly necessary.
-        setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */,
-                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
-        mIpc.startProvisioning(config);
-
+    private void performDualStackProvisioning() throws Exception {
         final InOrder inOrder = inOrder(mCb);
         final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>();
         final String dnsServer = "2001:4860:4860::64";
         final ByteBuffer pio = buildPioOption(3600, 1800, "2001:db8:1::/64");
         final ByteBuffer rdnss = buildRdnssOption(3600, dnsServer);
-        final ByteBuffer ra = buildRaPacket(pio, rdnss);
+        final ByteBuffer slla = buildSllaOption();
+        final ByteBuffer ra = buildRaPacket(pio, rdnss, slla);
 
         doIpv6OnlyProvisioning(inOrder, ra);
 
@@ -2450,9 +2559,27 @@
         reset(mCb);
     }
 
-    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
-    public void testIgnoreIpv6ProvisioningLoss() throws Exception {
-        doDualStackProvisioning();
+    private void doDualStackProvisioning(boolean shouldDisableAcceptRa) throws Exception {
+        when(mCm.shouldAvoidBadWifi()).thenReturn(true);
+
+        final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+                .withoutIpReachabilityMonitor()
+                .build();
+
+        setFeatureEnabled(NetworkStackUtils.IPCLIENT_DISABLE_ACCEPT_RA_VERSION,
+                shouldDisableAcceptRa);
+        // Enable rapid commit to accelerate DHCP handshake to shorten test duration,
+        // not strictly necessary.
+        setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */,
+                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
+        mIpc.startProvisioning(config);
+
+        performDualStackProvisioning();
+    }
+
+    @Test @SignatureRequiredTest(reason = "signature perms are required due to mocked callabck")
+    public void testIgnoreIpv6ProvisioningLoss_disableIPv6Stack() throws Exception {
+        doDualStackProvisioning(false /* shouldDisableAcceptRa */);
 
         final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>();
 
@@ -2474,11 +2601,52 @@
         assertNotNull(lp);
         assertEquals(lp.getAddresses().get(0), CLIENT_ADDR);
         assertEquals(lp.getDnsServers().get(0), SERVER_ADDR);
+
+        final ArgumentCaptor<Integer> quirkEvent = ArgumentCaptor.forClass(Integer.class);
+        verify(mNetworkQuirkMetricsDeps, timeout(TEST_TIMEOUT_MS)).writeStats(quirkEvent.capture());
+        assertEquals((long) quirkEvent.getValue(),
+                (long) NetworkQuirkEvent.QE_IPV6_PROVISIONING_ROUTER_LOST.ordinal());
+    }
+
+    @Test @SignatureRequiredTest(reason = "signature perms are required due to mocked callabck")
+    public void testIgnoreIpv6ProvisioningLoss_disableAcceptRa() throws Exception {
+        doDualStackProvisioning(true /* shouldDisableAcceptRa */);
+
+        final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>();
+
+        // Send RA with 0-lifetime and wait until all global IPv6 addresses, IPv6-related default
+        // route and DNS servers have been removed, then verify if there is IPv4-only, IPv6 link
+        // local address and route to fe80::/64 info left in the LinkProperties.
+        sendRouterAdvertisementWithZeroLifetime();
+        verify(mCb, timeout(TEST_TIMEOUT_MS).atLeastOnce()).onLinkPropertiesChange(
+                argThat(x -> {
+                    // Only IPv4 provisioned and IPv6 link-local address
+                    final boolean isIPv6LinkLocalAndIPv4OnlyProvisioned =
+                            (x.getLinkAddresses().size() == 2
+                                    && x.getDnsServers().size() == 1
+                                    && x.getAddresses().get(0) instanceof Inet4Address
+                                    && x.getDnsServers().get(0) instanceof Inet4Address);
+
+                    if (!isIPv6LinkLocalAndIPv4OnlyProvisioned) return false;
+                    lpFuture.complete(x);
+                    return true;
+                }));
+        final LinkProperties lp = lpFuture.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        assertNotNull(lp);
+        assertEquals(lp.getAddresses().get(0), CLIENT_ADDR);
+        assertEquals(lp.getDnsServers().get(0), SERVER_ADDR);
+        assertTrue(lp.getAddresses().get(1).isLinkLocalAddress());
+
+        reset(mCb);
+
+        // Send an RA to verify that global IPv6 addresses won't be configured on the interface.
+        sendBasicRouterAdvertisement(false /* waitForRs */);
+        verify(mCb, timeout(TEST_TIMEOUT_MS).times(0)).onLinkPropertiesChange(any());
     }
 
     @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDualStackProvisioning() throws Exception {
-        doDualStackProvisioning();
+        doDualStackProvisioning(false /* shouldDisableAcceptRa */);
 
         verify(mCb, never()).onProvisioningFailure(any());
     }
@@ -2594,20 +2762,20 @@
         verifyDhcpPacketRequestsIPv6OnlyPreferredOption(DhcpRequestPacket.class);
     }
 
+    private void setUpRetrievedNetworkAttributesForInitRebootState() {
+        final NetworkAttributes na = new NetworkAttributes.Builder()
+                .setAssignedV4Address(CLIENT_ADDR)
+                .setAssignedV4AddressExpiry(Long.MAX_VALUE) // lease is always valid
+                .setMtu(new Integer(TEST_DEFAULT_MTU))
+                .setCluster(TEST_CLUSTER)
+                .setDnsAddresses(Collections.singletonList(SERVER_ADDR))
+                .build();
+        storeNetworkAttributes(TEST_L2KEY, na);
+    }
+
     private void startFromInitRebootStateWithIPv6OnlyPreferredOption(final Integer ipv6OnlyWaitTime,
             final long expectedWaitSecs) throws Exception {
-        doAnswer(invocation -> {
-            ((OnNetworkAttributesRetrievedListener) invocation.getArgument(1))
-                    .onNetworkAttributesRetrieved(new Status(SUCCESS), TEST_L2KEY,
-                            new NetworkAttributes.Builder()
-                                .setAssignedV4Address(CLIENT_ADDR)
-                                .setAssignedV4AddressExpiry(Long.MAX_VALUE) // lease is always valid
-                                .setMtu(new Integer(TEST_DEFAULT_MTU))
-                                .setCluster(TEST_CLUSTER)
-                                .setDnsAddresses(Collections.singletonList(SERVER_ADDR))
-                                .build());
-            return null;
-        }).when(mIpMemoryStore).retrieveNetworkAttributes(eq(TEST_L2KEY), any());
+        setUpRetrievedNetworkAttributesForInitRebootState();
 
         final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
                 .withoutIpReachabilityMonitor()
@@ -2698,7 +2866,7 @@
     public void testNoFdLeaks() throws Exception {
         // Shut down and restart IpClient once to ensure that any fds that are opened the first
         // time it runs do not cause the test to fail.
-        doDualStackProvisioning();
+        doDualStackProvisioning(false /* shouldDisableAcceptRa */);
         shutdownAndRecreateIpClient();
 
         // Unfortunately we cannot use a large number of iterations as it would make the test run
@@ -2706,7 +2874,7 @@
         final int iterations = 10;
         final int before = getNumOpenFds();
         for (int i = 0; i < iterations; i++) {
-            doDualStackProvisioning();
+            doDualStackProvisioning(false /* shouldDisableAcceptRa */);
             shutdownAndRecreateIpClient();
             // The last time this loop runs, mIpc will be shut down in tearDown.
         }
@@ -2734,7 +2902,7 @@
     );
 
     private DhcpPacket doCustomizedDhcpOptionsTest(final List<DhcpOption> options,
-             final ScanResultInfo info) throws Exception {
+             final ScanResultInfo info, boolean isDhcpLeaseCacheEnabled) throws Exception {
         ProvisioningConfiguration.Builder prov = new ProvisioningConfiguration.Builder()
                 .withoutIpReachabilityMonitor()
                 .withLayer2Information(new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
@@ -2743,7 +2911,7 @@
                 .withDhcpOptions(options)
                 .withoutIPv6();
 
-        setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, false /* isRapidCommitEnabled */,
+        setDhcpFeatures(isDhcpLeaseCacheEnabled, false /* isRapidCommitEnabled */,
                 false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
 
         startIpClientProvisioning(prov.build());
@@ -2754,10 +2922,11 @@
     }
 
     @Test
-    public void testCustomizedDhcpOptions() throws Exception {
+    public void testDiscoverCustomizedDhcpOptions() throws Exception {
         final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specificIE */, TEST_OEM_OUI,
                 (byte) 0x17 /* vendor-specific IE type */);
-        final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info);
+        final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info,
+                false /* isDhcpLeaseCacheEnabled */);
 
         assertTrue(packet instanceof DhcpDiscoverPacket);
         assertEquals(packet.mVendorId, TEST_OEM_VENDOR_ID);
@@ -2765,10 +2934,11 @@
     }
 
     @Test
-    public void testCustomizedDhcpOptions_nullDhcpOptions() throws Exception {
+    public void testDiscoverCustomizedDhcpOptions_nullDhcpOptions() throws Exception {
         final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specificIE */, TEST_OEM_OUI,
                 (byte) 0x17 /* vendor-specific IE type */);
-        final DhcpPacket packet = doCustomizedDhcpOptionsTest(null /* options */, info);
+        final DhcpPacket packet = doCustomizedDhcpOptionsTest(null /* options */, info,
+                false /* isDhcpLeaseCacheEnabled */);
 
         assertTrue(packet instanceof DhcpDiscoverPacket);
         assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE));
@@ -2776,9 +2946,9 @@
     }
 
     @Test
-    public void testCustomizedDhcpOptions_nullScanResultInfo() throws Exception {
+    public void testDiscoverCustomizedDhcpOptions_nullScanResultInfo() throws Exception {
         final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS,
-                null /* scanResultInfo */);
+                null /* scanResultInfo */, false /* isDhcpLeaseCacheEnabled */);
 
         assertTrue(packet instanceof DhcpDiscoverPacket);
         assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE));
@@ -2786,10 +2956,11 @@
     }
 
     @Test
-    public void testCustomizedDhcpOptions_disallowedOui() throws Exception {
+    public void testDiscoverCustomizedDhcpOptions_disallowedOui() throws Exception {
         final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specificIE */,
                 new byte[]{ 0x00, 0x11, 0x22} /* oui */, (byte) 0x17 /* vendor-specific IE type */);
-        final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info);
+        final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info,
+                false /* isDhcpLeaseCacheEnabled */);
 
         assertTrue(packet instanceof DhcpDiscoverPacket);
         assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE));
@@ -2797,10 +2968,11 @@
     }
 
     @Test
-    public void testCustomizedDhcpOptions_invalidIeId() throws Exception {
+    public void testDiscoverCustomizedDhcpOptions_invalidIeId() throws Exception {
         final ScanResultInfo info = makeScanResultInfo(0xde /* vendor-specificIE */, TEST_OEM_OUI,
                 (byte) 0x17 /* vendor-specific IE type */);
-        final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info);
+        final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info,
+                false /* isDhcpLeaseCacheEnabled */);
 
         assertTrue(packet instanceof DhcpDiscoverPacket);
         assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE));
@@ -2808,10 +2980,11 @@
     }
 
     @Test
-    public void testCustomizedDhcpOptions_invalidVendorSpecificType() throws Exception {
+    public void testDiscoverCustomizedDhcpOptions_invalidVendorSpecificType() throws Exception {
         final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specificIE */, TEST_OEM_OUI,
                 (byte) 0x10 /* vendor-specific IE type */);
-        final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info);
+        final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info,
+                false /* isDhcpLeaseCacheEnabled */);
 
         assertTrue(packet instanceof DhcpDiscoverPacket);
         assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE));
@@ -2819,24 +2992,25 @@
     }
 
     @Test
-    public void testCustomizedDhcpOptions_disallowedOption() throws Exception {
+    public void testDisoverCustomizedDhcpOptions_disallowedOption() throws Exception {
         final List<DhcpOption> options = Arrays.asList(
                 makeDhcpOption((byte) 60, TEST_OEM_VENDOR_ID.getBytes()),
                 makeDhcpOption((byte) 77, TEST_OEM_USER_CLASS_INFO),
-                // DHCP_HOST_NAME
-                makeDhcpOption((byte) 12, new String("Pixel 3 XL").getBytes()));
+                // Option 26: MTU
+                makeDhcpOption((byte) 26, HexDump.toByteArray(TEST_DEFAULT_MTU)));
         final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specificIE */, TEST_OEM_OUI,
                 (byte) 0x17 /* vendor-specific IE type */);
-        final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info);
+        final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info,
+                false /* isDhcpLeaseCacheEnabled */);
 
         assertTrue(packet instanceof DhcpDiscoverPacket);
         assertEquals(packet.mVendorId, TEST_OEM_VENDOR_ID);
         assertArrayEquals(packet.mUserClass, TEST_OEM_USER_CLASS_INFO);
-        assertNull(packet.mHostName);
+        assertNull(packet.mMtu);
     }
 
     @Test
-    public void testCustomizedDhcpOptions_disallowedParamRequestOption() throws Exception {
+    public void testDiscoverCustomizedDhcpOptions_disallowedParamRequestOption() throws Exception {
         final List<DhcpOption> options = Arrays.asList(
                 makeDhcpOption((byte) 60, TEST_OEM_VENDOR_ID.getBytes()),
                 makeDhcpOption((byte) 77, TEST_OEM_USER_CLASS_INFO),
@@ -2844,11 +3018,512 @@
                 makeDhcpOption((byte) 42, null));
         final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specificIE */, TEST_OEM_OUI,
                 (byte) 0x17 /* vendor-specific IE type */);
-        final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info);
+        final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info,
+                false /* isDhcpLeaseCacheEnabled */);
 
         assertTrue(packet instanceof DhcpDiscoverPacket);
         assertEquals(packet.mVendorId, TEST_OEM_VENDOR_ID);
         assertArrayEquals(packet.mUserClass, TEST_OEM_USER_CLASS_INFO);
         assertFalse(packet.hasRequestedParam((byte) 42 /* NTP_SERVER */));
     }
+
+    @Test
+    public void testDiscoverCustomizedDhcpOptions_ParameterRequestListOnly() throws Exception {
+        final List<DhcpOption> options = Arrays.asList(
+                // DHCP_USER_CLASS
+                makeDhcpOption((byte) 77, null));
+        final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specificIE */, TEST_OEM_OUI,
+                (byte) 0x17 /* vendor-specific IE type */);
+        final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info,
+                false /* isDhcpLeaseCacheEnabled */);
+
+        assertTrue(packet instanceof DhcpDiscoverPacket);
+        assertTrue(packet.hasRequestedParam((byte) 77 /* DHCP_USER_CLASS */));
+        assertNull(packet.mUserClass);
+    }
+
+    @Test
+    public void testRequestCustomizedDhcpOptions() throws Exception {
+        setUpRetrievedNetworkAttributesForInitRebootState();
+
+        final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specificIE */, TEST_OEM_OUI,
+                (byte) 0x17 /* vendor-specific IE type */);
+        final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info,
+                true /* isDhcpLeaseCacheEnabled */);
+
+        assertTrue(packet instanceof DhcpRequestPacket);
+        assertEquals(packet.mVendorId, TEST_OEM_VENDOR_ID);
+        assertArrayEquals(packet.mUserClass, TEST_OEM_USER_CLASS_INFO);
+    }
+
+    @Test
+    public void testRequestCustomizedDhcpOptions_nullDhcpOptions() throws Exception {
+        setUpRetrievedNetworkAttributesForInitRebootState();
+
+        final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specificIE */, TEST_OEM_OUI,
+                (byte) 0x17 /* vendor-specific IE type */);
+        final DhcpPacket packet = doCustomizedDhcpOptionsTest(null /* options */, info,
+                true /* isDhcpLeaseCacheEnabled */);
+
+        assertTrue(packet instanceof DhcpRequestPacket);
+        assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE));
+        assertNull(packet.mUserClass);
+    }
+
+    @Test
+    public void testRequestCustomizedDhcpOptions_nullScanResultInfo() throws Exception {
+        setUpRetrievedNetworkAttributesForInitRebootState();
+
+        final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS,
+                null /* scanResultInfo */, true /* isDhcpLeaseCacheEnabled */);
+
+        assertTrue(packet instanceof DhcpRequestPacket);
+        assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE));
+        assertNull(packet.mUserClass);
+    }
+
+    @Test
+    public void testRequestCustomizedDhcpOptions_disallowedOui() throws Exception {
+        setUpRetrievedNetworkAttributesForInitRebootState();
+
+        final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specificIE */,
+                new byte[]{ 0x00, 0x11, 0x22} /* oui */, (byte) 0x17 /* vendor-specific IE type */);
+        final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info,
+                true /* isDhcpLeaseCacheEnabled */);
+
+        assertTrue(packet instanceof DhcpRequestPacket);
+        assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE));
+        assertNull(packet.mUserClass);
+    }
+
+    @Test
+    public void testRequestCustomizedDhcpOptions_invalidIeId() throws Exception {
+        setUpRetrievedNetworkAttributesForInitRebootState();
+
+        final ScanResultInfo info = makeScanResultInfo(0xde /* vendor-specificIE */, TEST_OEM_OUI,
+                (byte) 0x17 /* vendor-specific IE type */);
+        final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info,
+                true /* isDhcpLeaseCacheEnabled */);
+
+        assertTrue(packet instanceof DhcpRequestPacket);
+        assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE));
+        assertNull(packet.mUserClass);
+    }
+
+    @Test
+    public void testRequestCustomizedDhcpOptions_invalidVendorSpecificType() throws Exception {
+        setUpRetrievedNetworkAttributesForInitRebootState();
+
+        final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specificIE */, TEST_OEM_OUI,
+                (byte) 0x10 /* vendor-specific IE type */);
+        final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info,
+                true /* isDhcpLeaseCacheEnabled */);
+
+        assertTrue(packet instanceof DhcpRequestPacket);
+        assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE));
+        assertNull(packet.mUserClass);
+    }
+
+    @Test
+    public void testRequestCustomizedDhcpOptions_disallowedOption() throws Exception {
+        setUpRetrievedNetworkAttributesForInitRebootState();
+
+        final List<DhcpOption> options = Arrays.asList(
+                makeDhcpOption((byte) 60, TEST_OEM_VENDOR_ID.getBytes()),
+                makeDhcpOption((byte) 77, TEST_OEM_USER_CLASS_INFO),
+                // Option 26: MTU
+                makeDhcpOption((byte) 26, HexDump.toByteArray(TEST_DEFAULT_MTU)));
+        final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specificIE */, TEST_OEM_OUI,
+                (byte) 0x17 /* vendor-specific IE type */);
+        final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info,
+                true /* isDhcpLeaseCacheEnabled */);
+
+        assertTrue(packet instanceof DhcpRequestPacket);
+        assertEquals(packet.mVendorId, TEST_OEM_VENDOR_ID);
+        assertArrayEquals(packet.mUserClass, TEST_OEM_USER_CLASS_INFO);
+        assertNull(packet.mMtu);
+    }
+
+    @Test
+    public void testRequestCustomizedDhcpOptions_disallowedParamRequestOption() throws Exception {
+        setUpRetrievedNetworkAttributesForInitRebootState();
+
+        final List<DhcpOption> options = Arrays.asList(
+                makeDhcpOption((byte) 60, TEST_OEM_VENDOR_ID.getBytes()),
+                makeDhcpOption((byte) 77, TEST_OEM_USER_CLASS_INFO),
+                // NTP_SERVER
+                makeDhcpOption((byte) 42, null));
+        final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specificIE */, TEST_OEM_OUI,
+                (byte) 0x17 /* vendor-specific IE type */);
+        final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info,
+                true /* isDhcpLeaseCacheEnabled */);
+
+        assertTrue(packet instanceof DhcpRequestPacket);
+        assertEquals(packet.mVendorId, TEST_OEM_VENDOR_ID);
+        assertArrayEquals(packet.mUserClass, TEST_OEM_USER_CLASS_INFO);
+        assertFalse(packet.hasRequestedParam((byte) 42 /* NTP_SERVER */));
+    }
+
+    @Test
+    public void testRequestCustomizedDhcpOptions_ParameterRequestListOnly() throws Exception {
+        setUpRetrievedNetworkAttributesForInitRebootState();
+
+        final List<DhcpOption> options = Arrays.asList(
+                // DHCP_USER_CLASS
+                makeDhcpOption((byte) 77, null));
+        final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specificIE */, TEST_OEM_OUI,
+                (byte) 0x17 /* vendor-specific IE type */);
+        final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info,
+                true /* isDhcpLeaseCacheEnabled */);
+
+        assertTrue(packet instanceof DhcpRequestPacket);
+        assertTrue(packet.hasRequestedParam((byte) 77 /* DHCP_USER_CLASS */));
+        assertNull(packet.mUserClass);
+    }
+
+    private void assertGratuitousNa(final NeighborAdvertisement na) throws Exception {
+        final MacAddress etherMulticast =
+                NetworkStackUtils.ipv6MulticastToEthernetMulticast(IPV6_ADDR_ALL_ROUTERS_MULTICAST);
+        final LinkAddress target = new LinkAddress(na.naHdr.target, 64);
+
+        assertEquals(etherMulticast, na.ethHdr.dstMac);
+        assertEquals(ETH_P_IPV6, na.ethHdr.etherType);
+        assertEquals(IPPROTO_ICMPV6, na.ipv6Hdr.nextHeader);
+        assertEquals(0xff, na.ipv6Hdr.hopLimit);
+        assertTrue(na.ipv6Hdr.srcIp.isLinkLocalAddress());
+        assertEquals(IPV6_ADDR_ALL_ROUTERS_MULTICAST, na.ipv6Hdr.dstIp);
+        assertEquals(ICMPV6_NEIGHBOR_ADVERTISEMENT, na.icmpv6Hdr.type);
+        assertEquals(0, na.icmpv6Hdr.code);
+        assertEquals(0, na.naHdr.flags);
+        assertTrue(target.isGlobalPreferred());
+    }
+
+    @Test
+    public void testGratuitousNaForNewGlobalUnicastAddresses() throws Exception {
+        final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+                .withoutIpReachabilityMonitor()
+                .withoutIPv4()
+                .build();
+
+        setFeatureEnabled(NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION,
+                true /* isGratuitousNaEnabled */);
+        assertTrue(isFeatureEnabled(NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION, false));
+        startIpClientProvisioning(config);
+
+        doIpv6OnlyProvisioning();
+
+        final List<NeighborAdvertisement> naList = new ArrayList<>();
+        NeighborAdvertisement packet;
+        while ((packet = getNextNeighborAdvertisement()) != null) {
+            assertGratuitousNa(packet);
+            naList.add(packet);
+        }
+        assertEquals(2, naList.size()); // privacy address and stable privacy address
+    }
+
+    private void startGratuitousArpAndNaAfterRoamingTest(boolean isGratuitousArpNaRoamingEnabled,
+            boolean hasIpv4, boolean hasIpv6) throws Exception {
+        final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+                MacAddress.fromString(TEST_DEFAULT_BSSID));
+        final ScanResultInfo scanResultInfo =
+                makeScanResultInfo(TEST_DEFAULT_SSID, TEST_DEFAULT_BSSID);
+        final ProvisioningConfiguration.Builder prov = new ProvisioningConfiguration.Builder()
+                .withoutIpReachabilityMonitor()
+                .withLayer2Information(layer2Info)
+                .withScanResultInfo(scanResultInfo)
+                .withDisplayName("ssid");
+        if (!hasIpv4) prov.withoutIPv4();
+        if (!hasIpv6) prov.withoutIPv6();
+
+        // Enable rapid commit to accelerate DHCP handshake to shorten test duration,
+        // not strictly necessary.
+        setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */,
+                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
+        if (isGratuitousArpNaRoamingEnabled) {
+            setFeatureEnabled(NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION, true);
+            assertTrue(isFeatureEnabled(NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION, false));
+        }
+        startIpClientProvisioning(prov.build());
+    }
+
+    private void waitForGratuitousArpAndNaPacket(final List<ArpPacket> arpList,
+            final List<NeighborAdvertisement> naList) throws Exception {
+        NeighborAdvertisement na;
+        ArpPacket garp;
+        do {
+            na = getNextNeighborAdvertisement();
+            if (na != null) {
+                assertGratuitousNa(na);
+                naList.add(na);
+            }
+            garp = getNextArpPacket(TEST_TIMEOUT_MS);
+            if (garp != null) {
+                assertGratuitousARP(garp);
+                arpList.add(garp);
+            }
+        } while (na != null || garp != null);
+    }
+
+    @Test
+    public void testGratuitousArpAndNaAfterRoaming() throws Exception {
+        startGratuitousArpAndNaAfterRoamingTest(true /* isGratuitousArpNaRoamingEnabled */,
+                true /* hasIpv4 */, true /* hasIpv6 */);
+        performDualStackProvisioning();
+        forceLayer2Roaming();
+
+        final List<ArpPacket> arpList = new ArrayList<>();
+        final List<NeighborAdvertisement> naList = new ArrayList<>();
+        waitForGratuitousArpAndNaPacket(arpList, naList);
+        assertEquals(2, naList.size()); // privacy address and stable privacy address
+        assertEquals(1, arpList.size()); // IPv4 address
+    }
+
+    @Test
+    public void testGratuitousArpAndNaAfterRoaming_disableExpFlag() throws Exception {
+        startGratuitousArpAndNaAfterRoamingTest(false /* isGratuitousArpNaRoamingEnabled */,
+                true /* hasIpv4 */, true /* hasIpv6 */);
+        performDualStackProvisioning();
+        forceLayer2Roaming();
+
+        final List<ArpPacket> arpList = new ArrayList<>();
+        final List<NeighborAdvertisement> naList = new ArrayList<>();
+        waitForGratuitousArpAndNaPacket(arpList, naList);
+        assertEquals(0, naList.size());
+        assertEquals(0, arpList.size());
+    }
+
+    @Test
+    public void testGratuitousArpAndNaAfterRoaming_IPv6OnlyNetwork() throws Exception {
+        startGratuitousArpAndNaAfterRoamingTest(true /* isGratuitousArpNaRoamingEnabled */,
+                false /* hasIpv4 */, true /* hasIpv6 */);
+        doIpv6OnlyProvisioning();
+        forceLayer2Roaming();
+
+        final List<ArpPacket> arpList = new ArrayList<>();
+        final List<NeighborAdvertisement> naList = new ArrayList<>();
+        waitForGratuitousArpAndNaPacket(arpList, naList);
+        assertEquals(2, naList.size());
+        assertEquals(0, arpList.size());
+    }
+
+    @Test
+    public void testGratuitousArpAndNaAfterRoaming_IPv4OnlyNetwork() throws Exception {
+        startGratuitousArpAndNaAfterRoamingTest(true /* isGratuitousArpNaRoamingEnabled */,
+                true /* hasIpv4 */, false /* hasIpv6 */);
+
+        // Start IPv4 provisioning and wait until entire provisioning completes.
+        handleDhcpPackets(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
+                true /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, null /* serverSentUrl */);
+        verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
+        forceLayer2Roaming();
+
+        final List<ArpPacket> arpList = new ArrayList<>();
+        final List<NeighborAdvertisement> naList = new ArrayList<>();
+        waitForGratuitousArpAndNaPacket(arpList, naList);
+        assertEquals(0, naList.size());
+        assertEquals(1, arpList.size());
+    }
+
+    private void assertNeighborSolicitation(final NeighborSolicitation ns,
+            final Inet6Address target) {
+        assertEquals(ETH_P_IPV6, ns.ethHdr.etherType);
+        assertEquals(IPPROTO_ICMPV6, ns.ipv6Hdr.nextHeader);
+        assertEquals(0xff, ns.ipv6Hdr.hopLimit);
+        assertTrue(ns.ipv6Hdr.srcIp.isLinkLocalAddress());
+        assertEquals(ICMPV6_NEIGHBOR_SOLICITATION, ns.icmpv6Hdr.type);
+        assertEquals(0, ns.icmpv6Hdr.code);
+        assertEquals(0, ns.nsHdr.reserved);
+        assertEquals(target, ns.nsHdr.target);
+        assertEquals(ns.slla.linkLayerAddress, ns.ethHdr.srcMac);
+    }
+
+    private void assertUnicastNeighborSolicitation(final NeighborSolicitation ns,
+            final MacAddress dstMac, final Inet6Address dstIp, final Inet6Address target) {
+        assertEquals(dstMac, ns.ethHdr.dstMac);
+        assertEquals(dstIp, ns.ipv6Hdr.dstIp);
+        assertNeighborSolicitation(ns, target);
+    }
+
+    private void assertMulticastNeighborSolicitation(final NeighborSolicitation ns,
+            final Inet6Address target) {
+        final MacAddress etherMulticast =
+                NetworkStackUtils.ipv6MulticastToEthernetMulticast(ns.ipv6Hdr.dstIp);
+        assertEquals(etherMulticast, ns.ethHdr.dstMac);
+        assertTrue(ns.ipv6Hdr.dstIp.isMulticastAddress());
+        assertNeighborSolicitation(ns, target);
+    }
+
+    private NeighborSolicitation waitForUnicastNeighborSolicitation(final MacAddress dstMac,
+            final Inet6Address dstIp, final Inet6Address targetIp) throws Exception {
+        NeighborSolicitation ns;
+        while ((ns = getNextNeighborSolicitation()) != null) {
+            // Filter out the NSes used for duplicate address detetction, the target address
+            // is the global IPv6 address inside these NSes.
+            if (ns.nsHdr.target.isLinkLocalAddress()) break;
+        }
+        assertNotNull("No unicast Neighbor solicitation received on interface within timeout", ns);
+        assertUnicastNeighborSolicitation(ns, dstMac, dstIp, targetIp);
+        return ns;
+    }
+
+    private List<NeighborSolicitation> waitForMultipleNeighborSolicitations() throws Exception {
+        NeighborSolicitation ns;
+        final List<NeighborSolicitation> nsList = new ArrayList<NeighborSolicitation>();
+        while ((ns = getNextNeighborSolicitation()) != null) {
+            // Filter out the NSes used for duplicate address detetction, the target address
+            // is the global IPv6 address inside these NSes.
+            if (ns.nsHdr.target.isLinkLocalAddress()) {
+                nsList.add(ns);
+            }
+        }
+        assertFalse(nsList.isEmpty());
+        return nsList;
+    }
+
+    // Override this function with disabled experiment flag by default, in order not to
+    // affect those tests which are just related to basic IpReachabilityMonitor infra.
+    private void prepareIpReachabilityMonitorTest() throws Exception {
+        prepareIpReachabilityMonitorTest(false /* isMulticastResolicitEnabled */);
+    }
+
+    private void prepareIpReachabilityMonitorTest(boolean isMulticastResolicitEnabled)
+            throws Exception {
+        final ScanResultInfo info = makeScanResultInfo(TEST_DEFAULT_SSID, TEST_DEFAULT_BSSID);
+        ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+                .withLayer2Information(new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+                       MacAddress.fromString(TEST_DEFAULT_BSSID)))
+                .withScanResultInfo(info)
+                .withDisplayName(TEST_DEFAULT_SSID)
+                .withoutIPv4()
+                .build();
+        setFeatureEnabled(NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION,
+                isMulticastResolicitEnabled);
+        startIpClientProvisioning(config);
+        verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(false);
+        doIpv6OnlyProvisioning();
+
+        // Simulate the roaming.
+        forceLayer2Roaming();
+    }
+
+    @Test
+    public void testIpReachabilityMonitor_probeFailed() throws Exception {
+        prepareIpReachabilityMonitorTest();
+
+        final List<NeighborSolicitation> nsList = waitForMultipleNeighborSolicitations();
+        assertEquals(MIN_NUD_SOLICIT_NUM, nsList.size());
+        for (NeighborSolicitation ns : nsList) {
+            assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */,
+                    ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
+        }
+        assertNotifyNeighborLost(ROUTER_LINK_LOCAL /* targetIp */);
+    }
+
+    @Test
+    public void testIpReachabilityMonitor_probeReachable() throws Exception {
+        prepareIpReachabilityMonitorTest();
+
+        final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */,
+                ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
+
+        // Reply Neighbor Advertisement and check notifyLost callback won't be triggered.
+        int flag = NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER | NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
+        final ByteBuffer na = NeighborAdvertisement.build(ROUTER_MAC /* srcMac */,
+                ns.ethHdr.srcMac /* dstMac */, ROUTER_LINK_LOCAL /* srcIp */,
+                ns.ipv6Hdr.srcIp /* dstIp */, flag, ROUTER_LINK_LOCAL /* target */);
+        mPacketReader.sendResponse(na);
+        assertNeverNotifyNeighborLost();
+    }
+
+    @Test
+    public void testIpReachabilityMonitor_mcastResoclicitProbeFailed() throws Exception {
+        prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */);
+
+        final List<NeighborSolicitation> nsList = waitForMultipleNeighborSolicitations();
+        int expectedSize = MIN_NUD_SOLICIT_NUM + NUD_MCAST_RESOLICIT_NUM;
+        assertEquals(expectedSize, nsList.size()); // 5 unicast NSes + 3 multicast NSes
+        for (NeighborSolicitation ns : nsList.subList(0, MIN_NUD_SOLICIT_NUM)) {
+            assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */,
+                    ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
+        }
+        for (NeighborSolicitation ns : nsList.subList(MIN_NUD_SOLICIT_NUM, nsList.size())) {
+            assertMulticastNeighborSolicitation(ns, ROUTER_LINK_LOCAL /* targetIp */);
+        }
+        assertNotifyNeighborLost(ROUTER_LINK_LOCAL /* targetIp */);
+    }
+
+    @Test
+    public void testIpReachabilityMonitor_mcastResoclicitProbeReachableWithSameLinkLayerAddress()
+            throws Exception {
+        prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */);
+
+        final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */,
+                ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
+
+        // Reply Neighbor Advertisement and check notifyLost callback won't be triggered.
+        int flag = NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER | NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
+        final ByteBuffer na = NeighborAdvertisement.build(ROUTER_MAC /* srcMac */,
+                ns.ethHdr.srcMac /* dstMac */, ROUTER_LINK_LOCAL /* srcIp */,
+                ns.ipv6Hdr.srcIp /* dstIp */, flag, ROUTER_LINK_LOCAL /* target */);
+        mPacketReader.sendResponse(na);
+        assertNeverNotifyNeighborLost();
+    }
+
+    @Test
+    public void testIpReachabilityMonitor_mcastResoclicitProbeReachableWithDiffLinkLayerAddress()
+            throws Exception {
+        prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */);
+
+        final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */,
+                ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
+
+        // Reply Neighbor Advertisement with a different link-layer address and check notifyLost
+        // callback will be triggered. Override flag must be set, which indicates that the
+        // advertisement should override an existing cache entry and update the cached link-layer
+        // address, otherwise, kernel won't transit to REACHABLE state with a different link-layer
+        // address.
+        int flag = NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER | NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED
+                | NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
+        final MacAddress newMac = MacAddress.fromString("00:1a:11:22:33:55");
+        final ByteBuffer na = NeighborAdvertisement.build(newMac /* srcMac */,
+                ns.ethHdr.srcMac /* dstMac */, ROUTER_LINK_LOCAL /* srcIp */,
+                ns.ipv6Hdr.srcIp /* dstIp */, flag, ROUTER_LINK_LOCAL /* target */);
+        mPacketReader.sendResponse(na);
+        assertNotifyNeighborLost(ROUTER_LINK_LOCAL /* targetIp */);
+    }
+
+    @Test
+    public void testIPv6LinkLocalOnly() throws Exception {
+        ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+                .withoutIPv4()
+                .withIpv6LinkLocalOnly()
+                .withRandomMacAddress()
+                .build();
+        startIpClientProvisioning(config);
+
+        final ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class);
+        verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture());
+        final LinkProperties lp = captor.getValue();
+        assertNotNull(lp);
+        assertEquals(0, lp.getDnsServers().size());
+        final List<LinkAddress> addresses = lp.getLinkAddresses();
+        assertEquals(1, addresses.size());
+        assertTrue(addresses.get(0).getAddress().isLinkLocalAddress());
+        assertEquals(1, lp.getRoutes().size());
+        final RouteInfo route = lp.getRoutes().get(0);
+        assertNotNull(route);
+        assertTrue(route.getDestination().equals(new IpPrefix("fe80::/64")));
+        assertTrue(route.getGateway().isAnyLocalAddress());
+    }
+
+    @Test
+    public void testIPv6LinkLocalOnly_enableBothIPv4andIPv6LinkLocalOnly() throws Exception {
+        assertThrows(IllegalArgumentException.class,
+                () -> new ProvisioningConfiguration.Builder()
+                        .withoutIpReachabilityMonitor()
+                        .withIpv6LinkLocalOnly()
+                        .withRandomMacAddress()
+                        .build()
+        );
+    }
 }
diff --git a/tests/integration/src/android/net/ip/IpClientRootTest.kt b/tests/integration/src/android/net/ip/IpClientRootTest.kt
index ea2ec11..d861639 100644
--- a/tests/integration/src/android/net/ip/IpClientRootTest.kt
+++ b/tests/integration/src/android/net/ip/IpClientRootTest.kt
@@ -26,13 +26,14 @@
 import android.net.ipmemorystore.NetworkAttributes
 import android.net.ipmemorystore.Status
 import android.net.networkstack.TestNetworkStackServiceClient
-import android.net.util.NetworkStackUtils
 import android.os.Process
 import android.provider.DeviceConfig
 import android.util.ArrayMap
 import android.util.Log
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.net.module.util.DeviceConfigUtils
 import java.lang.System.currentTimeMillis
+import java.net.Inet6Address
 import java.util.concurrent.CompletableFuture
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
@@ -45,6 +46,8 @@
 import org.junit.AfterClass
 import org.junit.BeforeClass
 import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.never
 import org.mockito.Mockito.timeout
 import org.mockito.Mockito.verify
 
@@ -150,6 +153,7 @@
             IIpClientCallbacks.Stub(), IIpClientCallbacks by base {
         // asBinder is implemented by both base class and delegate: specify explicitly
         override fun asBinder() = super.asBinder()
+        override fun getInterfaceVersion() = IIpClientCallbacks.VERSION
     }
 
     @After
@@ -194,37 +198,33 @@
         return ipClientCaptor.value
     }
 
-    override fun setDhcpFeatures(
-        isDhcpLeaseCacheEnabled: Boolean,
-        isRapidCommitEnabled: Boolean,
-        isDhcpIpConflictDetectEnabled: Boolean,
-        isIPv6OnlyPreferredEnabled: Boolean
-    ) {
+    override fun setFeatureEnabled(feature: String, enabled: Boolean) {
         automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG)
         try {
-            setFeatureEnabled(NetworkStackUtils.DHCP_INIT_REBOOT_VERSION, isDhcpLeaseCacheEnabled)
-            setFeatureEnabled(NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION, isRapidCommitEnabled)
-            setFeatureEnabled(NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION,
-                    isDhcpIpConflictDetectEnabled)
-            setFeatureEnabled(NetworkStackUtils.DHCP_IPV6_ONLY_PREFERRED_VERSION,
-                    isIPv6OnlyPreferredEnabled)
+            // Do not use computeIfAbsent as it would overwrite null values (flag originally unset)
+            if (!originalFlagValues.containsKey(feature)) {
+                originalFlagValues[feature] =
+                        DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, feature)
+            }
+            // The feature is enabled if the flag is lower than the package version.
+            // Package versions follow a standard format with 9 digits.
+            // TODO: consider resetting flag values on reboot when set to special values like "1" or
+            // "999999999"
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, feature,
+                    if (enabled) "1" else "999999999", false)
         } finally {
             automation.dropShellPermissionIdentity()
         }
     }
 
-    private fun setFeatureEnabled(feature: String, enabled: Boolean) {
-        // Do not use computeIfAbsent as it would overwrite null values (flag originally unset)
-        if (!originalFlagValues.containsKey(feature)) {
-            originalFlagValues[feature] =
-                    DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, feature)
+    override fun isFeatureEnabled(name: String, defaultEnabled: Boolean): Boolean {
+        automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG)
+        try {
+            return DeviceConfigUtils.isFeatureEnabled(mContext, DeviceConfig.NAMESPACE_CONNECTIVITY,
+                    name, defaultEnabled)
+        } finally {
+            automation.dropShellPermissionIdentity()
         }
-        // The feature is enabled if the flag is lower than the package version.
-        // Package versions follow a standard format with 9 digits.
-        // TODO: consider resetting flag values on reboot when set to special values like "1" or
-        // "999999999"
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, feature,
-                if (enabled) "1" else "999999999", false)
     }
 
     private class TestAttributesRetrievedListener : OnNetworkAttributesRetrievedListener {
@@ -264,4 +264,16 @@
         mStore.retrieveNetworkAttributes(l2Key, listener)
         assertNull(listener.getBlockingNetworkAttributes(timeout))
     }
+
+    override fun assertNotifyNeighborLost(targetIp: Inet6Address) {
+        verify(mCb, timeout(TEST_TIMEOUT_MS)).onReachabilityLost(anyString())
+    }
+
+    override fun assertNeverNotifyNeighborLost() {
+        verify(mCb, never()).onReachabilityLost(anyString())
+    }
+
+    override fun storeNetworkAttributes(l2Key: String, na: NetworkAttributes) {
+        mStore.storeNetworkAttributes(l2Key, na, null /* listener */)
+    }
 }
diff --git a/tests/integration/src/android/net/netlink/InetDiagSocketIntegrationTest.java b/tests/integration/src/android/net/netlink/InetDiagSocketIntegrationTest.java
index e474d8a..0329fab 100644
--- a/tests/integration/src/android/net/netlink/InetDiagSocketIntegrationTest.java
+++ b/tests/integration/src/android/net/netlink/InetDiagSocketIntegrationTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.net.netlink;
+package com.android.net.module.util.netlink;
 
 import static android.system.OsConstants.AF_INET;
 import static android.system.OsConstants.AF_INET6;
diff --git a/tests/integration/src/android/net/util/NetworkStackUtilsIntegrationTest.kt b/tests/integration/src/android/net/util/NetworkStackUtilsIntegrationTest.kt
index 7e544ea..0ec43a5 100644
--- a/tests/integration/src/android/net/util/NetworkStackUtilsIntegrationTest.kt
+++ b/tests/integration/src/android/net/util/NetworkStackUtilsIntegrationTest.kt
@@ -19,6 +19,7 @@
 import android.Manifest.permission.MANAGE_TEST_NETWORKS
 import android.content.Context
 import android.net.InetAddresses.parseNumericAddress
+import android.net.IpPrefix
 import android.net.MacAddress
 import android.net.TestNetworkInterface
 import android.net.TestNetworkManager
@@ -26,12 +27,22 @@
 import android.os.HandlerThread
 import android.system.Os
 import android.system.OsConstants.AF_INET
+import android.system.OsConstants.AF_PACKET
+import android.system.OsConstants.ARPHRD_ETHER
+import android.system.OsConstants.ETH_P_IPV6
 import android.system.OsConstants.IPPROTO_UDP
 import android.system.OsConstants.SOCK_DGRAM
 import android.system.OsConstants.SOCK_NONBLOCK
 import androidx.test.platform.app.InstrumentationRegistry
+import android.system.OsConstants.SOCK_RAW
+import android.system.OsConstants.SOL_SOCKET
+import android.system.OsConstants.SO_RCVTIMEO
+import android.system.StructTimeval
+import com.android.net.module.util.Ipv6Utils
 import com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN
 import com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY
+import com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST
+import com.android.net.module.util.structs.PrefixInformationOption
 import com.android.testutils.ArpRequestFilter
 import com.android.testutils.ETHER_HEADER_LENGTH
 import com.android.testutils.IPV4_HEADER_LENGTH
@@ -42,9 +53,13 @@
 import org.junit.Assert.assertArrayEquals
 import org.junit.Before
 import org.junit.Test
+import java.io.FileDescriptor
 import java.net.Inet4Address
 import kotlin.reflect.KClass
+import java.net.Inet6Address
+import java.nio.ByteBuffer
 import kotlin.test.assertEquals
+import kotlin.test.assertTrue
 import kotlin.test.fail
 
 class NetworkStackUtilsIntegrationTest {
@@ -52,8 +67,12 @@
     private val context by lazy { inst.context }
 
     private val TEST_TIMEOUT_MS = 10_000L
+    private val TEST_MTU = 1500
     private val TEST_TARGET_IPV4_ADDR = parseNumericAddress("192.0.2.42") as Inet4Address
+    private val TEST_SRC_MAC = MacAddress.fromString("BA:98:76:54:32:10")
     private val TEST_TARGET_MAC = MacAddress.fromString("01:23:45:67:89:0A")
+    private val TEST_INET6ADDR_1 = parseNumericAddress("2001:db8::1") as Inet6Address
+    private val TEST_INET6ADDR_2 = parseNumericAddress("2001:db8::2") as Inet6Address
 
     private val readerHandler = HandlerThread(
             NetworkStackUtilsIntegrationTest::class.java.simpleName)
@@ -103,8 +122,7 @@
                 null /* hostname */, false /* metered */, 1500 /* mtu */,
                 null /* captivePortalUrl */)
         // Not using .array as per errorprone "ByteBufferBackingArray" recommendation
-        val originalPacket = ByteArray(buffer.limit())
-        buffer.get(originalPacket)
+        val originalPacket = buffer.readAsArray()
 
         Os.sendto(socket, originalPacket, 0 /* bytesOffset */, originalPacket.size /* bytesCount */,
                 0 /* flags */, TEST_TARGET_IPV4_ADDR, DhcpPacket.DHCP_CLIENT.toInt() /* port */)
@@ -112,7 +130,7 @@
         // Verify the packet was sent to the mac address specified in the ARP entry
         // Also accept ARP requests, but expect that none is sent before the UDP packet
         // IPv6 NS may be sent on the interface but will be filtered out
-        val sentPacket = reader.popPacket(TEST_TIMEOUT_MS, IPv4UdpFilter().or(ArpRequestFilter()))
+        val sentPacket = reader.poll(TEST_TIMEOUT_MS, IPv4UdpFilter().or(ArpRequestFilter()))
                 ?: fail("Packet was not sent on the interface")
 
         val sentTargetAddr = MacAddress.fromBytes(sentPacket.copyOfRange(0, ETHER_ADDR_LEN))
@@ -123,7 +141,55 @@
 
         assertArrayEquals("Sent packet != original packet", originalPacket, sentDhcpPacket)
     }
+
+    @Test
+    fun testAttachRaFilter() {
+        val socket = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IPV6)
+        val ifParams = InterfaceParams.getByName(iface.interfaceName)
+                ?: fail("Could not obtain interface params for ${iface.interfaceName}")
+        val socketAddr = SocketUtils.makePacketSocketAddress(ETH_P_IPV6, ifParams.index)
+        Os.bind(socket, socketAddr)
+        Os.setsockoptTimeval(socket, SOL_SOCKET, SO_RCVTIMEO,
+                StructTimeval.fromMillis(TEST_TIMEOUT_MS))
+
+        // Verify that before setting any filter, the socket receives pings
+        val echo = Ipv6Utils.buildEchoRequestPacket(TEST_SRC_MAC, TEST_TARGET_MAC, TEST_INET6ADDR_1,
+                TEST_INET6ADDR_2)
+        reader.sendResponse(echo)
+        echo.rewind()
+        assertNextPacketEquals(socket, echo.readAsArray(), "ICMPv6 echo")
+
+        NetworkStackUtils.attachRaFilter(socket, ARPHRD_ETHER)
+        // Send another echo, then an RA. After setting the filter expect only the RA.
+        echo.rewind()
+        reader.sendResponse(echo)
+        val pio = PrefixInformationOption.build(IpPrefix("2001:db8:1::/64"),
+                0.toByte() /* flags */, 3600 /* validLifetime */, 1800 /* preferredLifetime */)
+        val ra = Ipv6Utils.buildRaPacket(TEST_SRC_MAC, TEST_TARGET_MAC,
+                TEST_INET6ADDR_1 /* routerAddr */, IPV6_ADDR_ALL_NODES_MULTICAST,
+                0.toByte() /* flags */, 1800 /* lifetime */, 0 /* reachableTime */,
+                0 /* retransTimer */, pio)
+        reader.sendResponse(ra)
+        ra.rewind()
+
+        assertNextPacketEquals(socket, ra.readAsArray(), "ICMPv6 RA")
+    }
+
+    private fun assertNextPacketEquals(socket: FileDescriptor, expected: ByteArray, descr: String) {
+        val buffer = ByteArray(TEST_MTU)
+        val readPacket = Os.read(socket, buffer, 0 /* byteOffset */, buffer.size)
+        assertTrue(readPacket > 0, "$descr not received")
+        assertEquals(expected.size, readPacket, "Received packet size does not match for $descr")
+        assertArrayEquals("Received packet != expected $descr",
+                expected, buffer.copyOfRange(0, readPacket))
+    }
+}
+
+private fun ByteBuffer.readAsArray(): ByteArray {
+    val out = ByteArray(remaining())
+    get(out)
+    return out
 }
 
 private fun <T : Any> Context.assertHasService(manager: KClass<T>) = getSystemService(manager.java)
-        ?: fail("Could not find service $manager")
\ No newline at end of file
+        ?: fail("Could not find service $manager")
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index eb52f92..fa054fe 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 java_defaults {
     name: "NetworkStackTestsDefaults",
     platform_apis: true,
@@ -32,7 +36,10 @@
         "android.test.base",
         "android.test.mock",
     ],
-    defaults: ["libnetworkstackutilsjni_deps"],
+    defaults: [
+        "framework-connectivity-test-defaults",
+        "libnetworkstackutilsjni_deps"
+    ],
     jni_libs: [
         // For mockito extended
         "libdexmakerjvmtiagent",
@@ -40,7 +47,6 @@
         "libnetworkstackutilsjni",
     ],
     jni_uses_sdk_apis: true,
-    jarjar_rules: ":NetworkStackJarJarRules",
 }
 
 // Tests for NetworkStackNext.
@@ -54,7 +60,7 @@
     defaults: ["NetworkStackTestsDefaults"],
     static_libs: ["NetworkStackApiCurrentLib"],
     compile_multilib: "both", // Workaround for b/147785146 for mainline-presubmit
-    enabled: false, // Disabled in mainline-prod
+    jarjar_rules: ":NetworkStackJarJarRules",
 }
 
 // Library containing the unit tests. This is used by the coverage test target to pull in the
@@ -67,8 +73,8 @@
     static_libs: ["NetworkStackApiStableLib"],
     visibility: [
         "//packages/modules/NetworkStack/tests/integration",
-        "//frameworks/base/packages/Tethering/tests/integration",
-        "//packages/modules/Connectivity/Tethering/tests/integration",
+        "//packages/modules/Connectivity/tests:__subpackages__",
+        "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
     ]
 }
 
@@ -81,6 +87,7 @@
     defaults: ["NetworkStackTestsDefaults"],
     static_libs: ["NetworkStackApiStableLib"],
     compile_multilib: "both",
+    jarjar_rules: ":NetworkStackJarJarRules",
 }
 
 // Additional dependencies of libnetworkstackutilsjni that are not provided by the system when
diff --git a/tests/unit/jni/Android.bp b/tests/unit/jni/Android.bp
index fa1f420..0c9087f 100644
--- a/tests/unit/jni/Android.bp
+++ b/tests/unit/jni/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_shared {
     name: "libnetworkstacktestsjni",
     srcs: [
diff --git a/tests/unit/lint-baseline.xml b/tests/unit/lint-baseline.xml
new file mode 100644
index 0000000..0bfcaa9
--- /dev/null
+++ b/tests/unit/lint-baseline.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
+        errorLine1="    private val EMPTY_CAPABILITIES = NetworkCapabilities()"
+        errorLine2="                                     ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt"
+            line="134"
+            column="38"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
+        errorLine1="    private val VALIDATED_CAPABILITIES = NetworkCapabilities()"
+        errorLine2="                                         ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt"
+            line="135"
+            column="42"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `new android.net.NetworkCapabilities`"
+        errorLine1="            new NetworkCapabilities()"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java"
+            line="57"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `new android.net.NetworkCapabilities`"
+        errorLine1="        NetworkCapabilities nc = new NetworkCapabilities();"
+        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java"
+            line="109"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `new android.net.NetworkCapabilities`"
+        errorLine1="        nc = new NetworkCapabilities();"
+        errorLine2="             ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java"
+            line="117"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `new android.net.NetworkCapabilities`"
+        errorLine1="        nc = new NetworkCapabilities();"
+        errorLine2="             ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java"
+            line="123"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `new android.net.NetworkCapabilities`"
+        errorLine1="        nc = new NetworkCapabilities();"
+        errorLine2="             ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/NetworkStack/tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java"
+            line="129"
+            column="14"/>
+    </issue>
+
+</issues>
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java
index 416a6c3..b6de3a1 100644
--- a/tests/unit/src/android/net/apf/ApfTest.java
+++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -1056,6 +1056,10 @@
             { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
     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 byte[] IPV6_SOLICITED_NODE_MULTICAST_ADDRESS = {
+            (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+            (byte) 0xff, (byte) 0xab, (byte) 0xcd, (byte) 0xef,
+    };
 
     private static final int ICMP6_TYPE_OFFSET           = IP_HEADER_OFFSET + IPV6_HEADER_LEN;
     private static final int ICMP6_ROUTER_SOLICITATION   = 133;
@@ -1241,6 +1245,14 @@
         put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_NODES_ADDRESS);
         assertDrop(program, packet.array());
 
+        // Verify ICMPv6 NA to ff02::2 is dropped
+        put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_ROUTERS_ADDRESS);
+        assertDrop(program, packet.array());
+
+        // Verify ICMPv6 NA to Solicited-Node Multicast is passed
+        put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_SOLICITED_NODE_MULTICAST_ADDRESS);
+        assertPass(program, packet.array());
+
         // Verify ICMPv6 RS to any is dropped
         packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_SOLICITATION);
         assertDrop(program, packet.array());
diff --git a/tests/unit/src/android/net/dhcp/DhcpPacketTest.java b/tests/unit/src/android/net/dhcp/DhcpPacketTest.java
index d0c49d3..1a1f6c3 100644
--- a/tests/unit/src/android/net/dhcp/DhcpPacketTest.java
+++ b/tests/unit/src/android/net/dhcp/DhcpPacketTest.java
@@ -96,12 +96,12 @@
 
     @Before
     public void setUp() {
-        DhcpPacket.testOverrideVendorId = "android-dhcp-???";
+        DhcpPacket.sTestOverrideVendorId = "android-dhcp-???";
     }
 
     @After
     public void tearDown() {
-        DhcpPacket.testOverrideVendorId = null;
+        DhcpPacket.sTestOverrideVendorId = null;
     }
 
     class TestDhcpPacket extends DhcpPacket {
diff --git a/tests/unit/src/android/net/ip/ConntrackMonitorTest.java b/tests/unit/src/android/net/ip/ConntrackMonitorTest.java
index 6e9078e..fc0d52b 100644
--- a/tests/unit/src/android/net/ip/ConntrackMonitorTest.java
+++ b/tests/unit/src/android/net/ip/ConntrackMonitorTest.java
@@ -16,15 +16,16 @@
 package android.net.ip;
 
 import static android.net.ip.ConntrackMonitor.ConntrackEvent;
-import static android.net.netlink.ConntrackMessage.Tuple;
-import static android.net.netlink.ConntrackMessage.TupleIpv4;
-import static android.net.netlink.ConntrackMessage.TupleProto;
-import static android.net.netlink.NetlinkConstants.IPCTNL_MSG_CT_DELETE;
-import static android.net.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW;
 import static android.system.OsConstants.AF_UNIX;
 import static android.system.OsConstants.IPPROTO_TCP;
 import static android.system.OsConstants.SOCK_DGRAM;
 
+import static com.android.net.module.util.netlink.ConntrackMessage.Tuple;
+import static com.android.net.module.util.netlink.ConntrackMessage.TupleIpv4;
+import static com.android.net.module.util.netlink.ConntrackMessage.TupleProto;
+import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_DELETE;
+import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.fail;
@@ -33,8 +34,6 @@
 import static org.mockito.Mockito.verify;
 
 import android.net.InetAddresses;
-import android.net.netlink.NetlinkConstants;
-import android.net.netlink.NetlinkSocket;
 import android.net.util.SharedLog;
 import android.os.ConditionVariable;
 import android.os.Handler;
@@ -46,6 +45,9 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.net.module.util.netlink.NetlinkConstants;
+import com.android.net.module.util.netlink.NetlinkSocket;
+
 import libcore.util.HexEncoding;
 
 import org.junit.After;
diff --git a/tests/unit/src/android/net/ip/IpClientTest.java b/tests/unit/src/android/net/ip/IpClientTest.java
index e991ea7..95fe93a 100644
--- a/tests/unit/src/android/net/ip/IpClientTest.java
+++ b/tests/unit/src/android/net/ip/IpClientTest.java
@@ -18,12 +18,15 @@
 
 import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -48,22 +51,32 @@
 import android.net.MacAddress;
 import android.net.NetworkStackIpMemoryStore;
 import android.net.RouteInfo;
+import android.net.apf.ApfCapabilities;
+import android.net.apf.ApfFilter.ApfConfiguration;
 import android.net.ipmemorystore.NetworkAttributes;
 import android.net.metrics.IpConnectivityLog;
 import android.net.shared.InitialConfiguration;
+import android.net.shared.Layer2Information;
 import android.net.shared.ProvisioningConfiguration;
+import android.net.shared.ProvisioningConfiguration.ScanResultInfo;
 import android.net.util.InterfaceParams;
+import android.os.Build;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.networkstack.R;
 import com.android.server.NetworkObserver;
 import com.android.server.NetworkObserverRegistry;
 import com.android.server.NetworkStackService;
 import com.android.server.connectivity.ipmemorystore.IpMemoryStoreService;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.HandlerUtils;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -73,9 +86,12 @@
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.nio.ByteBuffer;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Random;
 import java.util.Set;
 
 
@@ -85,6 +101,9 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class IpClientTest {
+    @Rule
+    public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
     private static final String VALID = "VALID";
     private static final String INVALID = "INVALID";
     private static final String TEST_IFNAME = "test_wlan0";
@@ -94,6 +113,9 @@
     private static final int TEST_TIMEOUT_MS = 400;
     private static final String TEST_L2KEY = "some l2key";
     private static final String TEST_CLUSTER = "some cluster";
+    private static final String TEST_SSID = "test_ssid";
+    private static final String TEST_BSSID = "00:11:22:33:44:55";
+    private static final String TEST_BSSID2 = "00:1A:11:22:33:44";
 
     private static final String TEST_GLOBAL_ADDRESS = "1234:4321::548d:2db2:4fcf:ef75/64";
     private static final String[] TEST_LOCAL_ADDRESSES = {
@@ -409,6 +431,7 @@
 
     @Test
     public void testIsProvisioned() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
         InitialConfiguration empty = conf(links(), prefixes());
         IsProvisionedTestCase[] testcases = {
             // nothing
@@ -440,7 +463,7 @@
         };
 
         for (IsProvisionedTestCase testcase : testcases) {
-            if (IpClient.isProvisioned(testcase.lp, testcase.config) != testcase.isProvisioned) {
+            if (ipc.isProvisioned(testcase.lp, testcase.config) != testcase.isProvisioned) {
                 fail(testcase.errorMessage());
             }
         }
@@ -631,6 +654,183 @@
         return out;
     }
 
+    private ApfConfiguration verifyApfFilterCreatedOnStart(IpClient ipc) {
+        ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+                .withoutIPv4()
+                .withoutIpReachabilityMonitor()
+                .withInitialConfiguration(
+                        conf(links(TEST_LOCAL_ADDRESSES), prefixes(TEST_PREFIXES), ips()))
+                .withApfCapabilities(new ApfCapabilities(
+                        4 /* version */, 4096 /* maxProgramSize */, 4 /* format */))
+                .build();
+
+        ipc.startProvisioning(config);
+        final ArgumentCaptor<ApfConfiguration> configCaptor = ArgumentCaptor.forClass(
+                ApfConfiguration.class);
+        verify(mDependencies, timeout(TEST_TIMEOUT_MS)).maybeCreateApfFilter(
+                any(), configCaptor.capture(), any(), any());
+
+        return configCaptor.getValue();
+    }
+
+    @Test @IgnoreAfter(Build.VERSION_CODES.R)
+    public void testApfConfiguration_R() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final ApfConfiguration config = verifyApfFilterCreatedOnStart(ipc);
+
+        assertEquals(ApfCapabilities.getApfDrop8023Frames(), config.ieee802_3Filter);
+        assertArrayEquals(ApfCapabilities.getApfEtherTypeBlackList(), config.ethTypeBlackList);
+
+        verify(mResources, never()).getBoolean(R.bool.config_apfDrop802_3Frames);
+        verify(mResources, never()).getIntArray(R.array.config_apfEthTypeDenyList);
+
+        verifyShutdown(ipc);
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testApfConfiguration() throws Exception {
+        doReturn(true).when(mResources).getBoolean(R.bool.config_apfDrop802_3Frames);
+        final int[] ethTypeDenyList = new int[] { 0x88A2, 0x88A4 };
+        doReturn(ethTypeDenyList).when(mResources).getIntArray(
+                R.array.config_apfEthTypeDenyList);
+
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final ApfConfiguration config = verifyApfFilterCreatedOnStart(ipc);
+
+        assertTrue(config.ieee802_3Filter);
+        assertArrayEquals(ethTypeDenyList, config.ethTypeBlackList);
+
+        verifyShutdown(ipc);
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testApfConfiguration_NoApfDrop8023Frames() throws Exception {
+        doReturn(false).when(mResources).getBoolean(R.bool.config_apfDrop802_3Frames);
+        final int[] ethTypeDenyList = new int[] { 0x88A3, 0x88A5 };
+        doReturn(ethTypeDenyList).when(mResources).getIntArray(
+                R.array.config_apfEthTypeDenyList);
+
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final ApfConfiguration config = verifyApfFilterCreatedOnStart(ipc);
+
+        assertFalse(config.ieee802_3Filter);
+        assertArrayEquals(ethTypeDenyList, config.ethTypeBlackList);
+
+        verifyShutdown(ipc);
+    }
+
+    private ScanResultInfo makeScanResultInfo(final String ssid, final String bssid) {
+        final ByteBuffer payload = ByteBuffer.allocate(14 /* oui + type + data */);
+        final byte[] data = new byte[10];
+        new Random().nextBytes(data);
+        payload.put(new byte[] { 0x00, 0x1A, 0x11 });
+        payload.put((byte) 0x06);
+        payload.put(data);
+
+        final ScanResultInfo.InformationElement ie =
+                new ScanResultInfo.InformationElement(0xdd /* IE id */, payload);
+        return new ScanResultInfo(ssid, bssid, Collections.singletonList(ie));
+    }
+
+    @Test
+    public void testGetInitialBssidOnSOrAbove() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+                MacAddress.fromString(TEST_BSSID));
+        final ScanResultInfo scanResultInfo = makeScanResultInfo(TEST_SSID, TEST_BSSID2);
+        final MacAddress bssid = ipc.getInitialBssid(layer2Info, scanResultInfo,
+                true /* isAtLeastS */);
+        assertEquals(bssid, MacAddress.fromString(TEST_BSSID));
+    }
+
+    @Test
+    public void testGetInitialBssidOnSOrAbove_NullScanReqsultInfo() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+                MacAddress.fromString(TEST_BSSID));
+        final MacAddress bssid = ipc.getInitialBssid(layer2Info, null /* ScanResultInfo */,
+                true /* isAtLeastS */);
+        assertEquals(bssid, MacAddress.fromString(TEST_BSSID));
+    }
+
+    @Test
+    public void testGetInitialBssidOnSOrAbove_NullBssid() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+                null /* bssid */);
+        final ScanResultInfo scanResultInfo = makeScanResultInfo(TEST_SSID, TEST_BSSID);
+        final MacAddress bssid = ipc.getInitialBssid(layer2Info, scanResultInfo,
+                true /* isAtLeastS */);
+        assertNull(bssid);
+    }
+
+    @Test
+    public void testGetInitialBssidOnSOrAbove_NullLayer2Info() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final ScanResultInfo scanResultInfo = makeScanResultInfo(TEST_SSID, TEST_BSSID);
+        final MacAddress bssid = ipc.getInitialBssid(null /* layer2Info */, scanResultInfo,
+                true /* isAtLeastS */);
+        assertNull(bssid);
+    }
+
+    @Test
+    public void testGetInitialBssidBeforeS() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+                MacAddress.fromString(TEST_BSSID2));
+        final ScanResultInfo scanResultInfo = makeScanResultInfo(TEST_SSID, TEST_BSSID);
+        final MacAddress bssid = ipc.getInitialBssid(layer2Info, scanResultInfo,
+                false /* isAtLeastS */);
+        assertEquals(bssid, MacAddress.fromString(TEST_BSSID));
+    }
+
+    @Test
+    public void testGetInitialBssidBeforeS_NullLayer2Info() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final ScanResultInfo scanResultInfo = makeScanResultInfo(TEST_SSID, TEST_BSSID);
+        final MacAddress bssid = ipc.getInitialBssid(null /* layer2Info */, scanResultInfo,
+                false /* isAtLeastS */);
+        assertEquals(bssid, MacAddress.fromString(TEST_BSSID));
+    }
+
+    @Test
+    public void testGetInitialBssidBeforeS_BrokenInitialBssid() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final ScanResultInfo scanResultInfo = makeScanResultInfo(TEST_SSID, "00:11:22:33:44:");
+        final MacAddress bssid = ipc.getInitialBssid(null /* layer2Info */, scanResultInfo,
+                false /* isAtLeastS */);
+        assertNull(bssid);
+    }
+
+    @Test
+    public void testGetInitialBssidBeforeS_BrokenInitialBssidFallback() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+                MacAddress.fromString(TEST_BSSID));
+        final ScanResultInfo scanResultInfo = makeScanResultInfo(TEST_SSID, "00:11:22:33:44:");
+        final MacAddress bssid = ipc.getInitialBssid(layer2Info, scanResultInfo,
+                false /* isAtLeastS */);
+        assertEquals(bssid, MacAddress.fromString(TEST_BSSID));
+    }
+
+    @Test
+    public void testGetInitialBssidBeforeS_NullScanResultInfoFallback() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+                MacAddress.fromString(TEST_BSSID));
+        final MacAddress bssid = ipc.getInitialBssid(layer2Info, null /* scanResultInfo */,
+                false /* isAtLeastS */);
+        assertEquals(bssid, MacAddress.fromString(TEST_BSSID));
+    }
+
+    @Test
+    public void testGetInitialBssidBeforeS_NullScanResultInfoAndLayer2Info() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final MacAddress bssid = ipc.getInitialBssid(null /* layer2Info */,
+                null /* scanResultInfo */, false /* isAtLeastS */);
+        assertNull(bssid);
+    }
+
     interface Fn<A,B> {
         B call(A a) throws Exception;
     }
diff --git a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
index 863e268..ea8f1da 100644
--- a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
+++ b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
@@ -16,6 +16,7 @@
 package android.net.ip
 
 import android.content.Context
+import android.net.ip.IpNeighborMonitor.NeighborEventConsumer
 import android.net.INetd
 import android.net.InetAddresses.parseNumericAddress
 import android.net.IpPrefix
@@ -23,19 +24,37 @@
 import android.net.LinkProperties
 import android.net.RouteInfo
 import android.net.metrics.IpConnectivityLog
-import android.net.netlink.StructNdMsg.NUD_FAILED
-import android.net.netlink.StructNdMsg.NUD_STALE
-import android.net.netlink.makeNewNeighMessage
 import android.net.util.InterfaceParams
+import android.net.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION
 import android.net.util.SharedLog
 import android.os.Handler
 import android.os.HandlerThread
 import android.os.MessageQueue
 import android.os.MessageQueue.OnFileDescriptorEventListener
+import android.stats.connectivity.IpType
+import android.stats.connectivity.IpType.IPV4
+import android.stats.connectivity.IpType.IPV6
+import android.stats.connectivity.NudEventType
+import android.stats.connectivity.NudEventType.NUD_CONFIRM_FAILED
+import android.stats.connectivity.NudEventType.NUD_CONFIRM_FAILED_CRITICAL
+import android.stats.connectivity.NudEventType.NUD_MAC_ADDRESS_CHANGED
+import android.stats.connectivity.NudEventType.NUD_POST_ROAMING_FAILED
+import android.stats.connectivity.NudEventType.NUD_POST_ROAMING_FAILED_CRITICAL
+import android.stats.connectivity.NudEventType.NUD_ORGANIC_FAILED
+import android.stats.connectivity.NudEventType.NUD_ORGANIC_FAILED_CRITICAL
+import android.stats.connectivity.NudNeighborType
+import android.stats.connectivity.NudNeighborType.NUD_NEIGHBOR_BOTH
+import android.stats.connectivity.NudNeighborType.NUD_NEIGHBOR_DNS
+import android.stats.connectivity.NudNeighborType.NUD_NEIGHBOR_GATEWAY
 import android.system.ErrnoException
 import android.system.OsConstants.EAGAIN
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
+import com.android.networkstack.metrics.IpReachabilityMonitorMetrics
+import com.android.net.module.util.netlink.StructNdMsg.NUD_FAILED
+import com.android.net.module.util.netlink.StructNdMsg.NUD_REACHABLE
+import com.android.net.module.util.netlink.StructNdMsg.NUD_STALE
+import com.android.testutils.makeNewNeighMessage
 import com.android.testutils.waitForIdle
 import org.junit.After
 import org.junit.Before
@@ -43,12 +62,15 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyObject
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mockito.doAnswer
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
 import org.mockito.Mockito.timeout
 import org.mockito.Mockito.verify
 import java.io.FileDescriptor
@@ -66,12 +88,20 @@
 private val TEST_IPV4_GATEWAY = parseNumericAddress("192.168.222.3") as Inet4Address
 private val TEST_IPV6_GATEWAY = parseNumericAddress("2001:db8::1") as Inet6Address
 
+// IPv4 gateway is also DNS server.
+private val TEST_IPV4_GATEWAY_DNS = parseNumericAddress("192.168.222.100") as Inet4Address
+
 private val TEST_IPV4_LINKADDR = LinkAddress("192.168.222.123/24")
 private val TEST_IPV6_LINKADDR = LinkAddress("2001:db8::123/64")
 
+private val TEST_IPV6_LINKLOCAL_LINKADDR = LinkAddress("fe80::123/64")
+private val TEST_IPV6_LINKLOCAL_GATEWAY = parseNumericAddress("fe80::1") as Inet6Address
+private val TEST_IPV6_LINKLOCAL_SCOPED_GATEWAY = parseNumericAddress("fe80::1%21") as Inet6Address
+
 // DNSes inside IP prefix
 private val TEST_IPV4_DNS = parseNumericAddress("192.168.222.1") as Inet4Address
 private val TEST_IPV6_DNS = parseNumericAddress("2001:db8::321") as Inet6Address
+private val TEST_IPV6_DNS2 = parseNumericAddress("2001:db8::456") as Inet6Address
 
 private val TEST_IFACE = InterfaceParams("fake0", 21, null)
 private val TEST_LINK_PROPERTIES = LinkProperties().apply {
@@ -91,6 +121,54 @@
     addDnsServer(TEST_IPV6_DNS)
 }
 
+private val TEST_IPV4_ONLY_LINK_PROPERTIES = LinkProperties().apply {
+    interfaceName = TEST_IFACE.name
+    addLinkAddress(TEST_IPV4_LINKADDR)
+
+    // Add on link routes
+    addRoute(RouteInfo(TEST_IPV4_LINKADDR, null /* gateway */, TEST_IFACE.name))
+
+    // Add default routes
+    addRoute(RouteInfo(IpPrefix(parseNumericAddress("0.0.0.0"), 0), TEST_IPV4_GATEWAY_DNS))
+
+    addDnsServer(TEST_IPV4_GATEWAY_DNS)
+}
+
+private val TEST_IPV6_LINKLOCAL_SCOPED_LINK_PROPERTIES = LinkProperties().apply {
+    interfaceName = TEST_IFACE.name
+    addLinkAddress(TEST_IPV6_LINKADDR)
+    addLinkAddress(TEST_IPV6_LINKLOCAL_LINKADDR)
+
+    // Add on link routes
+    addRoute(RouteInfo(TEST_IPV6_LINKADDR, null /* gateway */, TEST_IFACE.name))
+    addRoute(RouteInfo(TEST_IPV6_LINKLOCAL_LINKADDR, null /* gateway */, TEST_IFACE.name))
+
+    // Add default routes
+    addRoute(RouteInfo(IpPrefix(parseNumericAddress("::"), 0), TEST_IPV6_LINKLOCAL_SCOPED_GATEWAY))
+
+    addDnsServer(TEST_IPV6_DNS)
+}
+
+private val TEST_DUAL_LINK_PROPERTIES = LinkProperties().apply {
+    interfaceName = TEST_IFACE.name
+    addLinkAddress(TEST_IPV4_LINKADDR)
+    addLinkAddress(TEST_IPV6_LINKADDR)
+    addLinkAddress(TEST_IPV6_LINKLOCAL_LINKADDR)
+
+    // Add on link routes
+    addRoute(RouteInfo(TEST_IPV4_LINKADDR, null /* gateway */, TEST_IFACE.name))
+    addRoute(RouteInfo(TEST_IPV6_LINKADDR, null /* gateway */, TEST_IFACE.name))
+    addRoute(RouteInfo(TEST_IPV6_LINKLOCAL_LINKADDR, null /* gateway */, TEST_IFACE.name))
+
+    // Add default routes
+    addRoute(RouteInfo(IpPrefix(parseNumericAddress("0.0.0.0"), 0), TEST_IPV4_GATEWAY))
+    addRoute(RouteInfo(IpPrefix(parseNumericAddress("::"), 0), TEST_IPV6_LINKLOCAL_SCOPED_GATEWAY))
+
+    addDnsServer(TEST_IPV4_DNS)
+    addDnsServer(TEST_IPV6_DNS)
+    addDnsServer(TEST_IPV6_DNS2)
+}
+
 /**
  * Tests for IpReachabilityMonitor.
  */
@@ -104,6 +182,7 @@
     private val netd = mock(INetd::class.java)
     private val fd = mock(FileDescriptor::class.java)
     private val metricsLog = mock(IpConnectivityLog::class.java)
+    private val mIpReachabilityMonitorMetrics = mock(IpReachabilityMonitorMetrics::class.java)
 
     private val handlerThread = HandlerThread(IpReachabilityMonitorTest::class.simpleName)
     private val handler by lazy { Handler(handlerThread.looper) }
@@ -171,6 +250,8 @@
             neighborMonitor = TestIpNeighborMonitor(handler, log, cb, fd)
             neighborMonitor
         }.`when`(dependencies).makeIpNeighborMonitor(any(), any(), any())
+        doReturn(mIpReachabilityMonitorMetrics)
+                .`when`(dependencies).getIpReachabilityMonitorMetrics()
 
         val monitorFuture = CompletableFuture<IpReachabilityMonitor>()
         // IpReachabilityMonitor needs to be started from the handler thread
@@ -207,8 +288,8 @@
         verify(callback, timeout(TEST_TIMEOUT_MS)).notifyLost(eq(TEST_IPV4_DNS), anyString())
     }
 
-    private fun runLoseProvisioningTest(lostNeighbor: InetAddress) {
-        reachabilityMonitor.updateLinkProperties(TEST_LINK_PROPERTIES)
+    private fun runLoseProvisioningTest(newLp: LinkProperties, lostNeighbor: InetAddress) {
+        reachabilityMonitor.updateLinkProperties(newLp)
 
         neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV4_GATEWAY, NUD_STALE))
         neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV6_GATEWAY, NUD_STALE))
@@ -219,23 +300,261 @@
         verify(callback, timeout(TEST_TIMEOUT_MS)).notifyLost(eq(lostNeighbor), anyString())
     }
 
+    private fun verifyNudFailureMetrics(
+        eventType: NudEventType,
+        ipType: IpType,
+        lostNeighborType: NudNeighborType
+    ) {
+        verify(mIpReachabilityMonitorMetrics, timeout(TEST_TIMEOUT_MS)).setNudIpType(eq(ipType))
+        verify(mIpReachabilityMonitorMetrics, timeout(TEST_TIMEOUT_MS))
+                .setNudEventType(eq(eventType))
+        verify(mIpReachabilityMonitorMetrics, timeout(TEST_TIMEOUT_MS))
+                .setNudNeighborType(eq(lostNeighborType))
+    }
+
+    // Verify if the notifyLost will be called when one neighbor has lost but it's still
+    // provisioned.
+    private fun runLoseNeighborStillProvisionedTest(
+        newLp: LinkProperties,
+        lostNeighbor: InetAddress,
+        eventType: NudEventType,
+        ipType: IpType,
+        lostNeighborType: NudNeighborType
+    ) {
+        reachabilityMonitor.updateLinkProperties(newLp)
+
+        neighborMonitor.enqueuePacket(makeNewNeighMessage(lostNeighbor, NUD_FAILED))
+        handlerThread.waitForIdle(TEST_TIMEOUT_MS)
+        verify(callback, never()).notifyLost(any(), anyString())
+        verifyNudFailureMetrics(eventType, ipType, lostNeighborType)
+    }
+
+    private fun runNeighborReachableButMacAddrChangedTest(
+        newLp: LinkProperties,
+        neighbor: InetAddress,
+        ipType: IpType
+    ) {
+        doReturn(true).`when`(dependencies).isFeatureEnabled(anyObject(),
+                eq(IP_REACHABILITY_MCAST_RESOLICIT_VERSION), anyBoolean())
+
+        reachabilityMonitor.updateLinkProperties(newLp)
+
+        neighborMonitor.enqueuePacket(makeNewNeighMessage(neighbor, NUD_REACHABLE,
+                "001122334455" /* oldMac */))
+        handlerThread.waitForIdle(TEST_TIMEOUT_MS)
+        verify(callback, never()).notifyLost(eq(neighbor), anyString())
+
+        reachabilityMonitor.probeAll(true /* dueToRoam */)
+
+        neighborMonitor.enqueuePacket(makeNewNeighMessage(neighbor, NUD_REACHABLE,
+                "1122334455aa" /* newMac */))
+        verify(callback, timeout(TEST_TIMEOUT_MS)).notifyLost(eq(neighbor), anyString())
+        verifyNudFailureMetrics(NUD_MAC_ADDRESS_CHANGED, ipType, NUD_NEIGHBOR_GATEWAY)
+    }
+
     @Test
     fun testLoseProvisioning_Ipv4DnsLost() {
-        runLoseProvisioningTest(TEST_IPV4_DNS)
+        runLoseProvisioningTest(TEST_LINK_PROPERTIES, TEST_IPV4_DNS)
     }
 
     @Test
     fun testLoseProvisioning_Ipv6DnsLost() {
-        runLoseProvisioningTest(TEST_IPV6_DNS)
+        runLoseProvisioningTest(TEST_LINK_PROPERTIES, TEST_IPV6_DNS)
     }
 
     @Test
     fun testLoseProvisioning_Ipv4GatewayLost() {
-        runLoseProvisioningTest(TEST_IPV4_GATEWAY)
+        runLoseProvisioningTest(TEST_LINK_PROPERTIES, TEST_IPV4_GATEWAY)
     }
 
     @Test
     fun testLoseProvisioning_Ipv6GatewayLost() {
-        runLoseProvisioningTest(TEST_IPV6_GATEWAY)
+        runLoseProvisioningTest(TEST_LINK_PROPERTIES, TEST_IPV6_GATEWAY)
     }
-}
\ No newline at end of file
+
+    private fun runNudProbeFailureMetricsTest(
+        lp: LinkProperties,
+        lostNeighbor: InetAddress,
+        eventType: NudEventType,
+        ipType: IpType,
+        lostNeighborType: NudNeighborType
+    ) {
+        runLoseProvisioningTest(lp, lostNeighbor)
+        verifyNudFailureMetrics(eventType, ipType, lostNeighborType)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_Ipv6GatewayLostPostRoaming() {
+        reachabilityMonitor.probeAll(true /* dueToRoam */)
+        runNudProbeFailureMetricsTest(TEST_LINK_PROPERTIES, TEST_IPV6_GATEWAY,
+                NUD_POST_ROAMING_FAILED_CRITICAL, IPV6, NUD_NEIGHBOR_GATEWAY)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_Ipv4GatewayLostPostRoaming() {
+        reachabilityMonitor.probeAll(true /* dueToRoam */)
+        runNudProbeFailureMetricsTest(TEST_LINK_PROPERTIES, TEST_IPV4_GATEWAY,
+                NUD_POST_ROAMING_FAILED_CRITICAL, IPV4, NUD_NEIGHBOR_GATEWAY)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_Ipv6DnsLostPostRoaming() {
+        reachabilityMonitor.probeAll(true /* dueToRoam */)
+        runNudProbeFailureMetricsTest(TEST_LINK_PROPERTIES, TEST_IPV6_DNS,
+                NUD_POST_ROAMING_FAILED_CRITICAL, IPV6, NUD_NEIGHBOR_DNS)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_Ipv4DnsLostPostRoaming() {
+        reachabilityMonitor.probeAll(true /* dueToRoam */)
+        runNudProbeFailureMetricsTest(TEST_LINK_PROPERTIES, TEST_IPV4_DNS,
+                NUD_POST_ROAMING_FAILED_CRITICAL, IPV4, NUD_NEIGHBOR_DNS)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_IPv4BothGatewayAndDnsLostPostRoaming() {
+        reachabilityMonitor.probeAll(true /* dueToRoam */)
+        runNudProbeFailureMetricsTest(TEST_IPV4_ONLY_LINK_PROPERTIES, TEST_IPV4_GATEWAY_DNS,
+                NUD_POST_ROAMING_FAILED_CRITICAL, IPV4, NUD_NEIGHBOR_BOTH)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_IPv6LinklocalScopedGatewayLostPostRoaming() {
+        reachabilityMonitor.probeAll(true /* dueToRoam */)
+        runNudProbeFailureMetricsTest(TEST_IPV6_LINKLOCAL_SCOPED_LINK_PROPERTIES,
+                TEST_IPV6_LINKLOCAL_SCOPED_GATEWAY, NUD_POST_ROAMING_FAILED_CRITICAL, IPV6,
+                NUD_NEIGHBOR_GATEWAY)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_Ipv6GatewayLostAfterConfirm() {
+        reachabilityMonitor.probeAll(false /* dueToRoam */)
+        runNudProbeFailureMetricsTest(TEST_LINK_PROPERTIES, TEST_IPV6_GATEWAY,
+                NUD_CONFIRM_FAILED_CRITICAL, IPV6, NUD_NEIGHBOR_GATEWAY)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_Ipv4GatewayLostAfterConfirm() {
+        reachabilityMonitor.probeAll(false /* dueToRoam */)
+        runNudProbeFailureMetricsTest(TEST_LINK_PROPERTIES, TEST_IPV4_GATEWAY,
+                NUD_CONFIRM_FAILED_CRITICAL, IPV4, NUD_NEIGHBOR_GATEWAY)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_Ipv6DnsLostAfterConfirm() {
+        reachabilityMonitor.probeAll(false /* dueToRoam */)
+        runNudProbeFailureMetricsTest(TEST_LINK_PROPERTIES, TEST_IPV6_DNS,
+                NUD_CONFIRM_FAILED_CRITICAL, IPV6, NUD_NEIGHBOR_DNS)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_Ipv4DnsLostAfterConfirm() {
+        reachabilityMonitor.probeAll(false /* dueToRoam */)
+        runNudProbeFailureMetricsTest(TEST_LINK_PROPERTIES, TEST_IPV4_DNS,
+                NUD_CONFIRM_FAILED_CRITICAL, IPV4, NUD_NEIGHBOR_DNS)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_IPv4BothGatewayAndDnsLostAfterConfirm() {
+        reachabilityMonitor.probeAll(false /* dueToRoam */)
+        runNudProbeFailureMetricsTest(TEST_IPV4_ONLY_LINK_PROPERTIES, TEST_IPV4_GATEWAY_DNS,
+                NUD_CONFIRM_FAILED_CRITICAL, IPV4, NUD_NEIGHBOR_BOTH)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_IPv6LinklocalScopedGatewayLostAfterConfirm() {
+        reachabilityMonitor.probeAll(false /* dueToRoam */)
+        runNudProbeFailureMetricsTest(TEST_IPV6_LINKLOCAL_SCOPED_LINK_PROPERTIES,
+                TEST_IPV6_LINKLOCAL_SCOPED_GATEWAY, NUD_CONFIRM_FAILED_CRITICAL, IPV6,
+                NUD_NEIGHBOR_GATEWAY)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_IPv6GatewayLostOrganic() {
+        runNudProbeFailureMetricsTest(TEST_LINK_PROPERTIES, TEST_IPV6_GATEWAY,
+                NUD_ORGANIC_FAILED_CRITICAL, IPV6, NUD_NEIGHBOR_GATEWAY)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_IPv4GatewayLostOrganic() {
+        runNudProbeFailureMetricsTest(TEST_LINK_PROPERTIES, TEST_IPV4_GATEWAY,
+                NUD_ORGANIC_FAILED_CRITICAL, IPV4, NUD_NEIGHBOR_GATEWAY)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_IPv6DnsLostOrganic() {
+        runNudProbeFailureMetricsTest(TEST_LINK_PROPERTIES, TEST_IPV6_DNS,
+                NUD_ORGANIC_FAILED_CRITICAL, IPV6, NUD_NEIGHBOR_DNS)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_IPv4DnsLostOrganic() {
+        runNudProbeFailureMetricsTest(TEST_LINK_PROPERTIES, TEST_IPV4_DNS,
+                NUD_ORGANIC_FAILED_CRITICAL, IPV4, NUD_NEIGHBOR_DNS)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_IPv4BothGatewayAndDnsLostOrganic() {
+        runNudProbeFailureMetricsTest(TEST_IPV4_ONLY_LINK_PROPERTIES, TEST_IPV4_GATEWAY_DNS,
+                NUD_ORGANIC_FAILED_CRITICAL, IPV4, NUD_NEIGHBOR_BOTH)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_IPv6LinklocalScopedGatewayLostOrganic() {
+        runNudProbeFailureMetricsTest(TEST_IPV6_LINKLOCAL_SCOPED_LINK_PROPERTIES,
+                TEST_IPV6_LINKLOCAL_SCOPED_GATEWAY, NUD_ORGANIC_FAILED_CRITICAL, IPV6,
+                NUD_NEIGHBOR_GATEWAY)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_IPv6OneDnsNeighborLostPostRoaming() {
+        reachabilityMonitor.probeAll(true /* dueToRoam */)
+        runLoseNeighborStillProvisionedTest(TEST_DUAL_LINK_PROPERTIES, TEST_IPV6_DNS,
+                NUD_POST_ROAMING_FAILED, IPV6, NUD_NEIGHBOR_DNS)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_IPv6OneDnsNeighborLostAfterConfirm() {
+        reachabilityMonitor.probeAll(false /* dueToRoam */)
+        runLoseNeighborStillProvisionedTest(TEST_DUAL_LINK_PROPERTIES, TEST_IPV6_DNS,
+                NUD_CONFIRM_FAILED, IPV6, NUD_NEIGHBOR_DNS)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_IPv6OneDnsNeighborLostOrganic() {
+        runLoseNeighborStillProvisionedTest(TEST_DUAL_LINK_PROPERTIES, TEST_IPV6_DNS,
+                NUD_ORGANIC_FAILED, IPV6, NUD_NEIGHBOR_DNS)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_multipleProbesFromRoamFirst() {
+        reachabilityMonitor.probeAll(true /* dueToRoam */)
+        handlerThread.waitForIdle(TEST_TIMEOUT_MS)
+        Thread.sleep(2)
+        reachabilityMonitor.probeAll(false /* dueToRoam */)
+        runLoseProvisioningTest(TEST_LINK_PROPERTIES, TEST_IPV6_GATEWAY)
+
+        verifyNudFailureMetrics(NUD_POST_ROAMING_FAILED_CRITICAL, IPV6, NUD_NEIGHBOR_GATEWAY)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_multipleProbesFromConfirmFirst() {
+        reachabilityMonitor.probeAll(false /* dueToRoam */)
+        handlerThread.waitForIdle(TEST_TIMEOUT_MS)
+        Thread.sleep(2)
+        reachabilityMonitor.probeAll(true /* dueToRoam */)
+        runLoseProvisioningTest(TEST_LINK_PROPERTIES, TEST_IPV6_GATEWAY)
+
+        verifyNudFailureMetrics(NUD_CONFIRM_FAILED_CRITICAL, IPV6, NUD_NEIGHBOR_GATEWAY)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_defaultIPv6GatewayMacAddrChanged() {
+        runNeighborReachableButMacAddrChangedTest(TEST_LINK_PROPERTIES, TEST_IPV6_GATEWAY, IPV6)
+    }
+
+    @Test
+    fun testNudProbeFailedMetrics_defaultIPv4GatewayMacAddrChanged() {
+        runNeighborReachableButMacAddrChangedTest(TEST_LINK_PROPERTIES, TEST_IPV4_GATEWAY, IPV4)
+    }
+}
diff --git a/tests/unit/src/android/net/netlink/ConntrackMessageTest.java b/tests/unit/src/android/net/netlink/ConntrackMessageTest.java
deleted file mode 100644
index 2e8d184..0000000
--- a/tests/unit/src/android/net/netlink/ConntrackMessageTest.java
+++ /dev/null
@@ -1,433 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.net.netlink;
-
-import static android.net.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW;
-import static android.net.netlink.NetlinkConstants.NFNL_SUBSYS_CTNETLINK;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import android.system.OsConstants;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import libcore.util.HexEncoding;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.net.Inet4Address;
-import java.net.InetAddress;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Arrays;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ConntrackMessageTest {
-    private static final boolean USING_LE = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN);
-
-    private short makeCtType(short msgType) {
-        return (short) (NFNL_SUBSYS_CTNETLINK << 8 | (byte) msgType);
-    }
-
-    // Example 1: TCP (192.168.43.209, 44333) -> (23.211.13.26, 443)
-    public static final String CT_V4UPDATE_TCP_HEX =
-            // struct nlmsghdr
-            "50000000" +      // length = 80
-            "0001" +          // type = (1 << 8) | 0
-            "0501" +          // flags
-            "01000000" +      // seqno = 1
-            "00000000" +      // pid = 0
-            // struct nfgenmsg
-            "02" +            // nfgen_family  = AF_INET
-            "00" +            // version = NFNETLINK_V0
-            "0000" +          // res_id
-            // struct nlattr
-            "3400" +          // nla_len = 52
-            "0180" +          // nla_type = nested CTA_TUPLE_ORIG
-                // struct nlattr
-                "1400" +      // nla_len = 20
-                "0180" +      // nla_type = nested CTA_TUPLE_IP
-                    "0800 0100 C0A82BD1" +  // nla_type=CTA_IP_V4_SRC, ip=192.168.43.209
-                    "0800 0200 17D30D1A" +  // nla_type=CTA_IP_V4_DST, ip=23.211.13.26
-                // struct nlattr
-                "1C00" +      // nla_len = 28
-                "0280" +      // nla_type = nested CTA_TUPLE_PROTO
-                    "0500 0100 06 000000" +  // nla_type=CTA_PROTO_NUM, proto=6
-                    "0600 0200 AD2D 0000" +  // nla_type=CTA_PROTO_SRC_PORT, port=44333 (big endian)
-                    "0600 0300 01BB 0000" +  // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian)
-            // struct nlattr
-            "0800" +          // nla_len = 8
-            "0700" +          // nla_type = CTA_TIMEOUT
-            "00069780";       // nla_value = 432000 (big endian)
-    public static final byte[] CT_V4UPDATE_TCP_BYTES =
-            HexEncoding.decode(CT_V4UPDATE_TCP_HEX.replaceAll(" ", "").toCharArray(), false);
-
-    private byte[] makeIPv4TimeoutUpdateRequestTcp() throws Exception {
-        return ConntrackMessage.newIPv4TimeoutUpdateRequest(
-                OsConstants.IPPROTO_TCP,
-                (Inet4Address) InetAddress.getByName("192.168.43.209"), 44333,
-                (Inet4Address) InetAddress.getByName("23.211.13.26"), 443,
-                432000);
-    }
-
-    // Example 2: UDP (100.96.167.146, 37069) -> (216.58.197.10, 443)
-    public static final String CT_V4UPDATE_UDP_HEX =
-            // struct nlmsghdr
-            "50000000" +      // length = 80
-            "0001" +          // type = (1 << 8) | 0
-            "0501" +          // flags
-            "01000000" +      // seqno = 1
-            "00000000" +      // pid = 0
-            // struct nfgenmsg
-            "02" +            // nfgen_family  = AF_INET
-            "00" +            // version = NFNETLINK_V0
-            "0000" +          // res_id
-            // struct nlattr
-            "3400" +          // nla_len = 52
-            "0180" +          // nla_type = nested CTA_TUPLE_ORIG
-                // struct nlattr
-                "1400" +      // nla_len = 20
-                "0180" +      // nla_type = nested CTA_TUPLE_IP
-                    "0800 0100 6460A792" +  // nla_type=CTA_IP_V4_SRC, ip=100.96.167.146
-                    "0800 0200 D83AC50A" +  // nla_type=CTA_IP_V4_DST, ip=216.58.197.10
-                // struct nlattr
-                "1C00" +      // nla_len = 28
-                "0280" +      // nla_type = nested CTA_TUPLE_PROTO
-                    "0500 0100 11 000000" +  // nla_type=CTA_PROTO_NUM, proto=17
-                    "0600 0200 90CD 0000" +  // nla_type=CTA_PROTO_SRC_PORT, port=37069 (big endian)
-                    "0600 0300 01BB 0000" +  // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian)
-            // struct nlattr
-            "0800" +          // nla_len = 8
-            "0700" +          // nla_type = CTA_TIMEOUT
-            "000000B4";       // nla_value = 180 (big endian)
-    public static final byte[] CT_V4UPDATE_UDP_BYTES =
-            HexEncoding.decode(CT_V4UPDATE_UDP_HEX.replaceAll(" ", "").toCharArray(), false);
-
-    private byte[] makeIPv4TimeoutUpdateRequestUdp() throws Exception {
-        return ConntrackMessage.newIPv4TimeoutUpdateRequest(
-                OsConstants.IPPROTO_UDP,
-                (Inet4Address) InetAddress.getByName("100.96.167.146"), 37069,
-                (Inet4Address) InetAddress.getByName("216.58.197.10"), 443,
-                180);
-    }
-
-    @Test
-    public void testConntrackMakeIPv4TcpTimeoutUpdate() throws Exception {
-        assumeTrue(USING_LE);
-
-        final byte[] tcp = makeIPv4TimeoutUpdateRequestTcp();
-        assertArrayEquals(CT_V4UPDATE_TCP_BYTES, tcp);
-    }
-
-    @Test
-    public void testConntrackParseIPv4TcpTimeoutUpdate() throws Exception {
-        assumeTrue(USING_LE);
-
-        final byte[] tcp = makeIPv4TimeoutUpdateRequestTcp();
-        final ByteBuffer byteBuffer = ByteBuffer.wrap(tcp);
-        byteBuffer.order(ByteOrder.nativeOrder());
-        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER);
-        assertNotNull(msg);
-        assertTrue(msg instanceof ConntrackMessage);
-        final ConntrackMessage conntrackMessage = (ConntrackMessage) msg;
-
-        final StructNlMsgHdr hdr = conntrackMessage.getHeader();
-        assertNotNull(hdr);
-        assertEquals(80, hdr.nlmsg_len);
-        assertEquals(makeCtType(IPCTNL_MSG_CT_NEW), hdr.nlmsg_type);
-        assertEquals((short) (StructNlMsgHdr.NLM_F_REPLACE | StructNlMsgHdr.NLM_F_REQUEST
-                | StructNlMsgHdr.NLM_F_ACK), hdr.nlmsg_flags);
-        assertEquals(1, hdr.nlmsg_seq);
-        assertEquals(0, hdr.nlmsg_pid);
-
-        final StructNfGenMsg nfmsgHdr = conntrackMessage.nfGenMsg;
-        assertNotNull(nfmsgHdr);
-        assertEquals((byte) OsConstants.AF_INET, nfmsgHdr.nfgen_family);
-        assertEquals((byte) StructNfGenMsg.NFNETLINK_V0, nfmsgHdr.version);
-        assertEquals((short) 0, nfmsgHdr.res_id);
-
-        assertEquals(InetAddress.parseNumericAddress("192.168.43.209"),
-                conntrackMessage.tupleOrig.srcIp);
-        assertEquals(InetAddress.parseNumericAddress("23.211.13.26"),
-                conntrackMessage.tupleOrig.dstIp);
-        assertEquals((byte) OsConstants.IPPROTO_TCP, conntrackMessage.tupleOrig.protoNum);
-        assertEquals((short) 44333, conntrackMessage.tupleOrig.srcPort);
-        assertEquals((short) 443, conntrackMessage.tupleOrig.dstPort);
-
-        assertNull(conntrackMessage.tupleReply);
-
-        assertEquals(0 /* absent */, conntrackMessage.status);
-        assertEquals(432000, conntrackMessage.timeoutSec);
-    }
-
-    @Test
-    public void testConntrackMakeIPv4UdpTimeoutUpdate() throws Exception {
-        assumeTrue(USING_LE);
-
-        final byte[] udp = makeIPv4TimeoutUpdateRequestUdp();
-        assertArrayEquals(CT_V4UPDATE_UDP_BYTES, udp);
-    }
-
-    @Test
-    public void testConntrackParseIPv4UdpTimeoutUpdate() throws Exception {
-        assumeTrue(USING_LE);
-
-        final byte[] udp = makeIPv4TimeoutUpdateRequestUdp();
-        final ByteBuffer byteBuffer = ByteBuffer.wrap(udp);
-        byteBuffer.order(ByteOrder.nativeOrder());
-        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER);
-        assertNotNull(msg);
-        assertTrue(msg instanceof ConntrackMessage);
-        final ConntrackMessage conntrackMessage = (ConntrackMessage) msg;
-
-        final StructNlMsgHdr hdr = conntrackMessage.getHeader();
-        assertNotNull(hdr);
-        assertEquals(80, hdr.nlmsg_len);
-        assertEquals(makeCtType(IPCTNL_MSG_CT_NEW), hdr.nlmsg_type);
-        assertEquals((short) (StructNlMsgHdr.NLM_F_REPLACE | StructNlMsgHdr.NLM_F_REQUEST
-                | StructNlMsgHdr.NLM_F_ACK), hdr.nlmsg_flags);
-        assertEquals(1, hdr.nlmsg_seq);
-        assertEquals(0, hdr.nlmsg_pid);
-
-        final StructNfGenMsg nfmsgHdr = conntrackMessage.nfGenMsg;
-        assertNotNull(nfmsgHdr);
-        assertEquals((byte) OsConstants.AF_INET, nfmsgHdr.nfgen_family);
-        assertEquals((byte) StructNfGenMsg.NFNETLINK_V0, nfmsgHdr.version);
-        assertEquals((short) 0, nfmsgHdr.res_id);
-
-        assertEquals(InetAddress.parseNumericAddress("100.96.167.146"),
-                conntrackMessage.tupleOrig.srcIp);
-        assertEquals(InetAddress.parseNumericAddress("216.58.197.10"),
-                conntrackMessage.tupleOrig.dstIp);
-        assertEquals((byte) OsConstants.IPPROTO_UDP, conntrackMessage.tupleOrig.protoNum);
-        assertEquals((short) 37069, conntrackMessage.tupleOrig.srcPort);
-        assertEquals((short) 443, conntrackMessage.tupleOrig.dstPort);
-
-        assertNull(conntrackMessage.tupleReply);
-
-        assertEquals(0 /* absent */, conntrackMessage.status);
-        assertEquals(180, conntrackMessage.timeoutSec);
-    }
-
-    public static final String CT_V4NEW_TCP_HEX =
-            // CHECKSTYLE:OFF IndentationCheck
-            // struct nlmsghdr
-            "8C000000" +      // length = 140
-            "0001" +          // type = NFNL_SUBSYS_CTNETLINK (1) << 8 | IPCTNL_MSG_CT_NEW (0)
-            "0006" +          // flags = NLM_F_CREATE (1 << 10) | NLM_F_EXCL (1 << 9)
-            "00000000" +      // seqno = 0
-            "00000000" +      // pid = 0
-            // struct nfgenmsg
-            "02" +            // nfgen_family = AF_INET
-            "00" +            // version = NFNETLINK_V0
-            "1234" +          // res_id = 0x1234 (big endian)
-             // struct nlattr
-            "3400" +          // nla_len = 52
-            "0180" +          // nla_type = nested CTA_TUPLE_ORIG
-                // struct nlattr
-                "1400" +      // nla_len = 20
-                "0180" +      // nla_type = nested CTA_TUPLE_IP
-                    "0800 0100 C0A8500C" +  // nla_type=CTA_IP_V4_SRC, ip=192.168.80.12
-                    "0800 0200 8C700874" +  // nla_type=CTA_IP_V4_DST, ip=140.112.8.116
-                // struct nlattr
-                "1C00" +      // nla_len = 28
-                "0280" +      // nla_type = nested CTA_TUPLE_PROTO
-                    "0500 0100 06 000000" +  // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)
-                    "0600 0200 F3F1 0000" +  // nla_type=CTA_PROTO_SRC_PORT, port=62449 (big endian)
-                    "0600 0300 01BB 0000" +  // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian)
-            // struct nlattr
-            "3400" +          // nla_len = 52
-            "0280" +          // nla_type = nested CTA_TUPLE_REPLY
-                // struct nlattr
-                "1400" +      // nla_len = 20
-                "0180" +      // nla_type = nested CTA_TUPLE_IP
-                    "0800 0100 8C700874" +  // nla_type=CTA_IP_V4_SRC, ip=140.112.8.116
-                    "0800 0200 6451B301" +  // nla_type=CTA_IP_V4_DST, ip=100.81.179.1
-                // struct nlattr
-                "1C00" +      // nla_len = 28
-                "0280" +      // nla_type = nested CTA_TUPLE_PROTO
-                    "0500 0100 06 000000" +  // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)
-                    "0600 0200 01BB 0000" +  // nla_type=CTA_PROTO_SRC_PORT, port=443 (big endian)
-                    "0600 0300 F3F1 0000" +  // nla_type=CTA_PROTO_DST_PORT, port=62449 (big endian)
-            // struct nlattr
-            "0800" +          // nla_len = 8
-            "0300" +          // nla_type = CTA_STATUS
-            "00000198" +      // nla_value = 0b110011000 (big endian)
-                              // IPS_CONFIRMED (1 << 3) | IPS_SRC_NAT (1 << 4) |
-                              // IPS_SRC_NAT_DONE (1 << 7) | IPS_DST_NAT_DONE (1 << 8)
-            // struct nlattr
-            "0800" +          // nla_len = 8
-            "0700" +          // nla_type = CTA_TIMEOUT
-            "00000078";       // nla_value = 120 (big endian)
-            // CHECKSTYLE:ON IndentationCheck
-    public static final byte[] CT_V4NEW_TCP_BYTES =
-            HexEncoding.decode(CT_V4NEW_TCP_HEX.replaceAll(" ", "").toCharArray(), false);
-
-    @Test
-    public void testParseCtNew() {
-        assumeTrue(USING_LE);
-
-        final ByteBuffer byteBuffer = ByteBuffer.wrap(CT_V4NEW_TCP_BYTES);
-        byteBuffer.order(ByteOrder.nativeOrder());
-        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER);
-        assertNotNull(msg);
-        assertTrue(msg instanceof ConntrackMessage);
-        final ConntrackMessage conntrackMessage = (ConntrackMessage) msg;
-
-        final StructNlMsgHdr hdr = conntrackMessage.getHeader();
-        assertNotNull(hdr);
-        assertEquals(140, hdr.nlmsg_len);
-        assertEquals(makeCtType(IPCTNL_MSG_CT_NEW), hdr.nlmsg_type);
-        assertEquals((short) (StructNlMsgHdr.NLM_F_CREATE | StructNlMsgHdr.NLM_F_EXCL),
-                hdr.nlmsg_flags);
-        assertEquals(0, hdr.nlmsg_seq);
-        assertEquals(0, hdr.nlmsg_pid);
-
-        final StructNfGenMsg nfmsgHdr = conntrackMessage.nfGenMsg;
-        assertNotNull(nfmsgHdr);
-        assertEquals((byte) OsConstants.AF_INET, nfmsgHdr.nfgen_family);
-        assertEquals((byte) StructNfGenMsg.NFNETLINK_V0, nfmsgHdr.version);
-        assertEquals((short) 0x1234, nfmsgHdr.res_id);
-
-        assertEquals(InetAddress.parseNumericAddress("192.168.80.12"),
-                conntrackMessage.tupleOrig.srcIp);
-        assertEquals(InetAddress.parseNumericAddress("140.112.8.116"),
-                conntrackMessage.tupleOrig.dstIp);
-        assertEquals((byte) OsConstants.IPPROTO_TCP, conntrackMessage.tupleOrig.protoNum);
-        assertEquals((short) 62449, conntrackMessage.tupleOrig.srcPort);
-        assertEquals((short) 443, conntrackMessage.tupleOrig.dstPort);
-
-        assertEquals(InetAddress.parseNumericAddress("140.112.8.116"),
-                conntrackMessage.tupleReply.srcIp);
-        assertEquals(InetAddress.parseNumericAddress("100.81.179.1"),
-                conntrackMessage.tupleReply.dstIp);
-        assertEquals((byte) OsConstants.IPPROTO_TCP, conntrackMessage.tupleReply.protoNum);
-        assertEquals((short) 443, conntrackMessage.tupleReply.srcPort);
-        assertEquals((short) 62449, conntrackMessage.tupleReply.dstPort);
-
-        assertEquals(0x198, conntrackMessage.status);
-        assertEquals(120, conntrackMessage.timeoutSec);
-    }
-
-    @Test
-    public void testParseTruncation() {
-        assumeTrue(USING_LE);
-
-        // Expect no crash while parsing the truncated message which has been truncated to every
-        // length between 0 and its full length - 1.
-        for (int len = 0; len < CT_V4NEW_TCP_BYTES.length; len++) {
-            final byte[] truncated = Arrays.copyOfRange(CT_V4NEW_TCP_BYTES, 0, len);
-
-            final ByteBuffer byteBuffer = ByteBuffer.wrap(truncated);
-            byteBuffer.order(ByteOrder.nativeOrder());
-            final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer,
-                    OsConstants.NETLINK_NETFILTER);
-        }
-    }
-
-    @Test
-    public void testParseTruncationWithInvalidByte() {
-        assumeTrue(USING_LE);
-
-        // Expect no crash while parsing the message which is truncated by invalid bytes. The
-        // message has been truncated to every length between 0 and its full length - 1.
-        for (byte invalid : new byte[]{(byte) 0x00, (byte) 0xff}) {
-            for (int len = 0; len < CT_V4NEW_TCP_BYTES.length; len++) {
-                final byte[] truncated = new byte[CT_V4NEW_TCP_BYTES.length];
-                Arrays.fill(truncated, (byte) invalid);
-                System.arraycopy(CT_V4NEW_TCP_BYTES, 0, truncated, 0, len);
-
-                final ByteBuffer byteBuffer = ByteBuffer.wrap(truncated);
-                byteBuffer.order(ByteOrder.nativeOrder());
-                final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer,
-                        OsConstants.NETLINK_NETFILTER);
-            }
-        }
-    }
-
-    // Malformed conntrack messages.
-    public static final String CT_MALFORMED_HEX =
-            // CHECKSTYLE:OFF IndentationCheck
-            // <--           nlmsghr           -->|<-nfgenmsg->|<--    CTA_TUPLE_ORIG     -->|
-            // CTA_TUPLE_ORIG has no nla_value.
-            "18000000 0001 0006 00000000 00000000   02 00 0000 0400 0180"
-            // nested CTA_TUPLE_IP has no nla_value.
-            + "1C000000 0001 0006 00000000 00000000 02 00 0000 0800 0180 0400 0180"
-            // nested CTA_IP_V4_SRC has no nla_value.
-            + "20000000 0001 0006 00000000 00000000 02 00 0000 0C00 0180 0800 0180 0400 0100"
-            // nested CTA_TUPLE_PROTO has no nla_value.
-            // <--           nlmsghr           -->|<-nfgenmsg->|<--    CTA_TUPLE_ORIG
-            + "30000000 0001 0006 00000000 00000000 02 00 0000 1C00 0180 1400 0180 0800 0100"
-            //                                  -->|
-            + "C0A8500C 0800 0200 8C700874 0400 0280";
-            // CHECKSTYLE:ON IndentationCheck
-    public static final byte[] CT_MALFORMED_BYTES =
-            HexEncoding.decode(CT_MALFORMED_HEX.replaceAll(" ", "").toCharArray(), false);
-
-    @Test
-    public void testParseMalformation() {
-        assumeTrue(USING_LE);
-
-        final ByteBuffer byteBuffer = ByteBuffer.wrap(CT_MALFORMED_BYTES);
-        byteBuffer.order(ByteOrder.nativeOrder());
-
-        // Expect no crash while parsing the malformed message.
-        int messageCount = 0;
-        while (byteBuffer.remaining() > 0) {
-            final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer,
-                    OsConstants.NETLINK_NETFILTER);
-            messageCount++;
-        }
-        assertEquals(4, messageCount);
-    }
-
-    @Test
-    public void testToString() {
-        assumeTrue(USING_LE);
-
-        final ByteBuffer byteBuffer = ByteBuffer.wrap(CT_V4NEW_TCP_BYTES);
-        byteBuffer.order(ByteOrder.nativeOrder());
-        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER);
-        assertNotNull(msg);
-        assertTrue(msg instanceof ConntrackMessage);
-        final ConntrackMessage conntrackMessage = (ConntrackMessage) msg;
-
-        // Bug: "nlmsg_flags{1536(NLM_F_MATCH))" is not correct because StructNlMsgHdr
-        // #stringForNlMsgFlags can't convert all flags (ex: NLM_F_CREATE) and can't distinguish
-        // the flags which have the same value (ex: NLM_F_MATCH <0x200> and NLM_F_EXCL <0x200>).
-        // The flags output string should be "NLM_F_CREATE|NLM_F_EXCL" in this case.
-        // TODO: correct the flag converted string once #stringForNlMsgFlags does.
-        final String expected = ""
-                + "ConntrackMessage{"
-                + "nlmsghdr{StructNlMsgHdr{ nlmsg_len{140}, nlmsg_type{256(IPCTNL_MSG_CT_NEW)}, "
-                + "nlmsg_flags{1536(NLM_F_MATCH))}, nlmsg_seq{0}, nlmsg_pid{0} }}, "
-                + "nfgenmsg{NfGenMsg{ nfgen_family{AF_INET}, version{0}, res_id{4660} }}, "
-                + "tuple_orig{Tuple{IPPROTO_TCP: 192.168.80.12:62449 -> 140.112.8.116:443}}, "
-                + "tuple_reply{Tuple{IPPROTO_TCP: 140.112.8.116:443 -> 100.81.179.1:62449}}, "
-                + "status{408(IPS_CONFIRMED|IPS_SRC_NAT|IPS_SRC_NAT_DONE|IPS_DST_NAT_DONE)}, "
-                + "timeout_sec{120}}";
-        assertEquals(expected, conntrackMessage.toString());
-    }
-}
diff --git a/tests/unit/src/android/net/netlink/InetDiagSocketTest.java b/tests/unit/src/android/net/netlink/InetDiagSocketTest.java
deleted file mode 100644
index fcc85a2..0000000
--- a/tests/unit/src/android/net/netlink/InetDiagSocketTest.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.net.netlink;
-
-import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
-import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
-import static android.system.OsConstants.AF_INET;
-import static android.system.OsConstants.AF_INET6;
-import static android.system.OsConstants.IPPROTO_TCP;
-import static android.system.OsConstants.IPPROTO_UDP;
-import static android.system.OsConstants.NETLINK_INET_DIAG;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import libcore.util.HexEncoding;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class InetDiagSocketTest {
-    // Hexadecimal representation of InetDiagReqV2 request.
-    private static final String INET_DIAG_REQ_V2_UDP_INET4_HEX =
-            // struct nlmsghdr
-            "48000000" +     // length = 72
-            "1400" +         // type = SOCK_DIAG_BY_FAMILY
-            "0103" +         // flags = NLM_F_REQUEST | NLM_F_DUMP
-            "00000000" +     // seqno
-            "00000000" +     // pid (0 == kernel)
-            // struct inet_diag_req_v2
-            "02" +           // family = AF_INET
-            "11" +           // protcol = IPPROTO_UDP
-            "00" +           // idiag_ext
-            "00" +           // pad
-            "ffffffff" +     // idiag_states
-            // inet_diag_sockid
-            "a5de" +         // idiag_sport = 42462
-            "b971" +         // idiag_dport = 47473
-            "0a006402000000000000000000000000" + // idiag_src = 10.0.100.2
-            "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8
-            "00000000" +     // idiag_if
-            "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE
-    private static final byte[] INET_DIAG_REQ_V2_UDP_INET4_BYTES =
-            HexEncoding.decode(INET_DIAG_REQ_V2_UDP_INET4_HEX.toCharArray(), false);
-
-    @Test
-    public void testInetDiagReqV2UdpInet4() throws Exception {
-        InetSocketAddress local = new InetSocketAddress(InetAddress.getByName("10.0.100.2"),
-                42462);
-        InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"),
-                47473);
-        final byte[] msg = InetDiagMessage.InetDiagReqV2(IPPROTO_UDP, local, remote, AF_INET,
-                (short) (NLM_F_REQUEST | NLM_F_DUMP));
-        assertArrayEquals(INET_DIAG_REQ_V2_UDP_INET4_BYTES, msg);
-    }
-
-    // Hexadecimal representation of InetDiagReqV2 request.
-    private static final String INET_DIAG_REQ_V2_TCP_INET6_HEX =
-            // struct nlmsghdr
-            "48000000" +     // length = 72
-            "1400" +         // type = SOCK_DIAG_BY_FAMILY
-            "0100" +         // flags = NLM_F_REQUEST
-            "00000000" +     // seqno
-            "00000000" +     // pid (0 == kernel)
-            // struct inet_diag_req_v2
-            "0a" +           // family = AF_INET6
-            "06" +           // protcol = IPPROTO_TCP
-            "00" +           // idiag_ext
-            "00" +           // pad
-            "ffffffff" +     // idiag_states
-                // inet_diag_sockid
-                "a5de" +         // idiag_sport = 42462
-                "b971" +         // idiag_dport = 47473
-                "fe8000000000000086c9b2fffe6aed4b" + // idiag_src = fe80::86c9:b2ff:fe6a:ed4b
-                "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8
-                "00000000" +     // idiag_if
-                "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE
-    private static final byte[] INET_DIAG_REQ_V2_TCP_INET6_BYTES =
-            HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET6_HEX.toCharArray(), false);
-
-    @Test
-    public void testInetDiagReqV2TcpInet6() throws Exception {
-        InetSocketAddress local = new InetSocketAddress(
-                InetAddress.getByName("fe80::86c9:b2ff:fe6a:ed4b"), 42462);
-        InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"),
-                47473);
-        byte[] msg = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, local, remote, AF_INET6,
-                NLM_F_REQUEST);
-
-        assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_BYTES, msg);
-    }
-
-    // Hexadecimal representation of InetDiagReqV2 request with extension, INET_DIAG_INFO.
-    private static final String INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_HEX =
-            // struct nlmsghdr
-            "48000000" +     // length = 72
-            "1400" +         // type = SOCK_DIAG_BY_FAMILY
-            "0100" +         // flags = NLM_F_REQUEST
-            "00000000" +     // seqno
-            "00000000" +     // pid (0 == kernel)
-            // struct inet_diag_req_v2
-            "02" +           // family = AF_INET
-            "06" +           // protcol = IPPROTO_TCP
-            "02" +           // idiag_ext = INET_DIAG_INFO
-            "00" +           // pad
-            "ffffffff" +   // idiag_states
-            // inet_diag_sockid
-            "3039" +         // idiag_sport = 12345
-            "d431" +         // idiag_dport = 54321
-            "01020304000000000000000000000000" + // idiag_src = 1.2.3.4
-            "08080404000000000000000000000000" + // idiag_dst = 8.8.4.4
-            "00000000" +     // idiag_if
-            "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE
-
-    private static final byte[] INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_BYTES =
-            HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_HEX.toCharArray(), false);
-    private static final int TCP_ALL_STATES = 0xffffffff;
-    @Test
-    public void testInetDiagReqV2TcpInetWithExt() throws Exception {
-        InetSocketAddress local = new InetSocketAddress(
-                InetAddress.getByName("1.2.3.4"), 12345);
-        InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.4.4"),
-                54321);
-        byte[] msg = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, local, remote, AF_INET,
-                NLM_F_REQUEST, 0 /* pad */, 2 /* idiagExt */, TCP_ALL_STATES);
-
-        assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_BYTES, msg);
-
-        local = new InetSocketAddress(
-                InetAddress.getByName("fe80::86c9:b2ff:fe6a:ed4b"), 42462);
-        remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"),
-                47473);
-        msg = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, local, remote, AF_INET6,
-                NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES);
-
-        assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_BYTES, msg);
-    }
-
-    // Hexadecimal representation of InetDiagReqV2 request with no socket specified.
-    private static final String INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_HEX =
-            // struct nlmsghdr
-            "48000000" +     // length = 72
-            "1400" +         // type = SOCK_DIAG_BY_FAMILY
-            "0100" +         // flags = NLM_F_REQUEST
-            "00000000" +     // seqno
-            "00000000" +     // pid (0 == kernel)
-            // struct inet_diag_req_v2
-            "0a" +           // family = AF_INET6
-            "06" +           // protcol = IPPROTO_TCP
-            "00" +           // idiag_ext
-            "00" +           // pad
-            "ffffffff" +     // idiag_states
-            // inet_diag_sockid
-            "0000" +         // idiag_sport
-            "0000" +         // idiag_dport
-            "00000000000000000000000000000000" + // idiag_src
-            "00000000000000000000000000000000" + // idiag_dst
-            "00000000" +     // idiag_if
-            "0000000000000000"; // idiag_cookie
-
-    private static final byte[] INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFED_BYTES =
-            HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_HEX.toCharArray(), false);
-
-    @Test
-    public void testInetDiagReqV2TcpInet6NoIdSpecified() throws Exception {
-        InetSocketAddress local = new InetSocketAddress(
-                InetAddress.getByName("fe80::fe6a:ed4b"), 12345);
-        InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.4.4"),
-                54321);
-        // Verify no socket specified if either local or remote socket address is null.
-        byte[] msgExt = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, null, null, AF_INET6,
-                NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES);
-        byte[] msg;
-        try {
-            msg = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, null, remote, AF_INET6,
-                    NLM_F_REQUEST);
-            fail("Both remote and local should be null, expected UnknownHostException");
-        } catch (NullPointerException e) {
-        }
-
-        try {
-            msg = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, local, null, AF_INET6,
-                    NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES);
-            fail("Both remote and local should be null, expected UnknownHostException");
-        } catch (NullPointerException e) {
-        }
-
-        msg = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, null, null, AF_INET6,
-                NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES);
-        assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFED_BYTES, msg);
-        assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFED_BYTES, msgExt);
-    }
-
-    // Hexadecimal representation of InetDiagReqV2 request.
-    private static final String INET_DIAG_MSG_HEX =
-            // struct nlmsghdr
-            "58000000" +     // length = 88
-            "1400" +         // type = SOCK_DIAG_BY_FAMILY
-            "0200" +         // flags = NLM_F_MULTI
-            "00000000" +     // seqno
-            "f5220000" +     // pid (0 == kernel)
-            // struct inet_diag_msg
-            "0a" +           // family = AF_INET6
-            "01" +           // idiag_state
-            "00" +           // idiag_timer
-            "00" +           // idiag_retrans
-                // inet_diag_sockid
-                "a817" +     // idiag_sport = 43031
-                "960f" +     // idiag_dport = 38415
-                "fe8000000000000086c9b2fffe6aed4b" + // idiag_src = fe80::86c9:b2ff:fe6a:ed4b
-                "00000000000000000000ffff08080808" + // idiag_dst = 8.8.8.8
-                "00000000" + // idiag_if
-                "ffffffffffffffff" + // idiag_cookie = INET_DIAG_NOCOOKIE
-            "00000000" +     // idiag_expires
-            "00000000" +     // idiag_rqueue
-            "00000000" +     // idiag_wqueue
-            "a3270000" +     // idiag_uid
-            "A57E1900";      // idiag_inode
-    private static final byte[] INET_DIAG_MSG_BYTES =
-            HexEncoding.decode(INET_DIAG_MSG_HEX.toCharArray(), false);
-
-    @Test
-    public void testParseInetDiagResponse() throws Exception {
-        final ByteBuffer byteBuffer = ByteBuffer.wrap(INET_DIAG_MSG_BYTES);
-        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
-        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG);
-        assertNotNull(msg);
-
-        assertTrue(msg instanceof InetDiagMessage);
-        final InetDiagMessage inetDiagMsg = (InetDiagMessage) msg;
-        assertEquals(10147, inetDiagMsg.mStructInetDiagMsg.idiag_uid);
-
-        final StructNlMsgHdr hdr = inetDiagMsg.getHeader();
-        assertNotNull(hdr);
-        assertEquals(NetlinkConstants.SOCK_DIAG_BY_FAMILY, hdr.nlmsg_type);
-        assertEquals(StructNlMsgHdr.NLM_F_MULTI, hdr.nlmsg_flags);
-        assertEquals(0, hdr.nlmsg_seq);
-        assertEquals(8949, hdr.nlmsg_pid);
-    }
-}
diff --git a/tests/unit/src/android/net/netlink/NduseroptMessageTest.java b/tests/unit/src/android/net/netlink/NduseroptMessageTest.java
deleted file mode 100644
index b070d61..0000000
--- a/tests/unit/src/android/net/netlink/NduseroptMessageTest.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.net.netlink;
-
-import static android.net.InetAddresses.parseNumericAddress;
-import static android.system.OsConstants.AF_INET6;
-import static android.system.OsConstants.NETLINK_ROUTE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.net.IpPrefix;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import libcore.util.HexEncoding;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.net.InetAddress;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class NduseroptMessageTest {
-
-    private static final byte ICMP_TYPE_RA = (byte) 134;
-
-    private static final int IFINDEX1 = 15715755;
-    private static final int IFINDEX2 = 1431655765;
-
-    // IPv6, 0 bytes of options, interface index 15715755, type 134 (RA), code 0, padding.
-    private static final String HDR_EMPTY = "0a00" + "0000" + "abcdef00" + "8600000000000000";
-
-    // IPv6, 16 bytes of options, interface index 1431655765, type 134 (RA), code 0, padding.
-    private static final String HDR_16BYTE = "0a00" + "1000" + "55555555" + "8600000000000000";
-
-    // IPv6, 32 bytes of options, interface index 1431655765, type 134 (RA), code 0, padding.
-    private static final String HDR_32BYTE = "0a00" + "2000" + "55555555" + "8600000000000000";
-
-    // PREF64 option, 2001:db8:3:4:5:6::/96, lifetime=10064
-    private static final String OPT_PREF64 = "2602" + "2750" + "20010db80003000400050006";
-
-    // Length 20, NDUSEROPT_SRCADDR, fe80:2:3:4:5:6:7:8
-    private static final String NLA_SRCADDR = "1400" + "0100" + "fe800002000300040005000600070008";
-
-    private static final InetAddress SADDR1 = parseNumericAddress("fe80:2:3:4:5:6:7:8%" + IFINDEX1);
-    private static final InetAddress SADDR2 = parseNumericAddress("fe80:2:3:4:5:6:7:8%" + IFINDEX2);
-
-    private static final String MSG_EMPTY = HDR_EMPTY + NLA_SRCADDR;
-    private static final String MSG_PREF64 = HDR_16BYTE + OPT_PREF64 + NLA_SRCADDR;
-
-    @Test
-    public void testParsing() {
-        NduseroptMessage msg = parseNduseroptMessage(toBuffer(MSG_EMPTY));
-        assertMatches(AF_INET6, 0, IFINDEX1, ICMP_TYPE_RA, (byte) 0, SADDR1, msg);
-        assertNull(msg.option);
-
-        msg = parseNduseroptMessage(toBuffer(MSG_PREF64));
-        assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg);
-        assertPref64Option("2001:db8:3:4:5:6::/96", msg.option);
-    }
-
-    @Test
-    public void testParseWithinNetlinkMessage() throws Exception {
-        // A NduseroptMessage inside a netlink message. Ensure that it parses the same way both by
-        // parsing the netlink message via NetlinkMessage.parse() and by parsing the option itself
-        // with NduseroptMessage.parse().
-        final String hexBytes =
-                "44000000440000000000000000000000"             // len=68, RTM_NEWNDUSEROPT
-                + "0A0010001E0000008600000000000000"           // IPv6, opt_bytes=16, ifindex=30, RA
-                + "260202580064FF9B0000000000000000"           // pref64, prefix=64:ff9b::/96, 600
-                + "14000100FE800000000000000250B6FFFEB7C499";  // srcaddr=fe80::250:b6ff:feb7:c499
-
-        ByteBuffer buf = toBuffer(hexBytes);
-        assertEquals(68, buf.limit());
-        buf.order(ByteOrder.nativeOrder());
-
-        NetlinkMessage nlMsg = NetlinkMessage.parse(buf, NETLINK_ROUTE);
-        assertNotNull(nlMsg);
-        assertTrue(nlMsg instanceof NduseroptMessage);
-
-        NduseroptMessage msg = (NduseroptMessage) nlMsg;
-        InetAddress srcaddr = InetAddress.getByName("fe80::250:b6ff:feb7:c499%30");
-        assertMatches(AF_INET6, 16, 30, ICMP_TYPE_RA, (byte) 0, srcaddr, msg);
-        assertPref64Option("64:ff9b::/96", msg.option);
-
-        final String hexBytesWithoutHeader = hexBytes.substring(StructNlMsgHdr.STRUCT_SIZE * 2);
-        ByteBuffer bufWithoutHeader = toBuffer(hexBytesWithoutHeader);
-        assertEquals(52, bufWithoutHeader.limit());
-        msg = parseNduseroptMessage(bufWithoutHeader);
-        assertMatches(AF_INET6, 16, 30, ICMP_TYPE_RA, (byte) 0, srcaddr, msg);
-        assertPref64Option("64:ff9b::/96", msg.option);
-    }
-
-    @Test
-    public void testParseUnknownOptionWithinNetlinkMessage() throws Exception {
-        final String hexBytes =
-                "4C0000004400000000000000000000000"
-                + "A0018001E0000008600000000000000"
-                + "1903000000001770FD123456789000000000000000000001"  // RDNSS option
-                + "14000100FE800000000000000250B6FFFEB7C499";
-
-        ByteBuffer buf = toBuffer(hexBytes);
-        assertEquals(76, buf.limit());
-        buf.order(ByteOrder.nativeOrder());
-
-        NetlinkMessage nlMsg = NetlinkMessage.parse(buf, NETLINK_ROUTE);
-        assertNotNull(nlMsg);
-        assertTrue(nlMsg instanceof NduseroptMessage);
-
-        NduseroptMessage msg = (NduseroptMessage) nlMsg;
-        InetAddress srcaddr = InetAddress.getByName("fe80::250:b6ff:feb7:c499%30");
-        assertMatches(AF_INET6, 24, 30, ICMP_TYPE_RA, (byte) 0, srcaddr, msg);
-        assertEquals(NdOption.UNKNOWN, msg.option);
-    }
-
-    @Test
-    public void testUnknownOption() {
-        ByteBuffer buf = toBuffer(MSG_PREF64);
-        // Replace the PREF64 option type (38) with an unknown option number.
-        final int optionStart = NduseroptMessage.STRUCT_SIZE;
-        assertEquals(38, buf.get(optionStart));
-        buf.put(optionStart, (byte) 42);
-
-        NduseroptMessage msg = parseNduseroptMessage(buf);
-        assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg);
-        assertEquals(NdOption.UNKNOWN, msg.option);
-
-        buf.flip();
-        assertEquals(42, buf.get(optionStart));
-        buf.put(optionStart, (byte) 38);
-
-        msg = parseNduseroptMessage(buf);
-        assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg);
-        assertPref64Option("2001:db8:3:4:5:6::/96", msg.option);
-    }
-
-    @Test
-    public void testZeroLengthOption() {
-        // Make sure an unknown option with a 0-byte length is ignored and parsing continues with
-        // the address, which comes after it.
-        final String hexString = HDR_16BYTE + "00000000000000000000000000000000" + NLA_SRCADDR;
-        ByteBuffer buf = toBuffer(hexString);
-        assertEquals(52, buf.limit());
-        NduseroptMessage msg = parseNduseroptMessage(buf);
-        assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg);
-        assertNull(msg.option);
-    }
-
-    @Test
-    public void testTooLongOption() {
-        // Make sure that if an option's length is too long, it's ignored and parsing continues with
-        // the address, which comes after it.
-        final String hexString = HDR_16BYTE + "26030000000000000000000000000000" + NLA_SRCADDR;
-        ByteBuffer buf = toBuffer(hexString);
-        assertEquals(52, buf.limit());
-        NduseroptMessage msg = parseNduseroptMessage(buf);
-        assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg);
-        assertNull(msg.option);
-    }
-
-    @Test
-    public void testOptionsTooLong() {
-        // Header claims 32 bytes of options. Buffer ends before options end.
-        String hexString = HDR_32BYTE + OPT_PREF64;
-        ByteBuffer buf = toBuffer(hexString);
-        assertEquals(32, buf.limit());
-        assertNull(NduseroptMessage.parse(toBuffer(hexString), NETLINK_ROUTE));
-
-        // Header claims 32 bytes of options. Buffer ends at end of options with no source address.
-        hexString = HDR_32BYTE + OPT_PREF64 + OPT_PREF64;
-        buf = toBuffer(hexString);
-        assertEquals(48, buf.limit());
-        assertNull(NduseroptMessage.parse(toBuffer(hexString), NETLINK_ROUTE));
-    }
-
-    @Test
-    public void testTruncation() {
-        final int optLen = MSG_PREF64.length() / 2;  // 1 byte = 2 hex chars
-        for (int len = 0; len < optLen; len++) {
-            ByteBuffer buf = toBuffer(MSG_PREF64.substring(0, len * 2));
-            NduseroptMessage msg = parseNduseroptMessage(buf);
-            if (len < optLen) {
-                assertNull(msg);
-            } else {
-                assertNotNull(msg);
-                assertPref64Option("2001:db8:3:4:5:6::/96", msg.option);
-            }
-        }
-    }
-
-    @Test
-    public void testToString() {
-        NduseroptMessage msg = parseNduseroptMessage(toBuffer(MSG_PREF64));
-        assertNotNull(msg);
-        assertEquals("Nduseroptmsg(10, 16, 1431655765, 134, 0, fe80:2:3:4:5:6:7:8%1431655765)",
-                msg.toString());
-    }
-
-    // Convenience method to parse a NduseroptMessage that's not part of a netlink message.
-    private NduseroptMessage parseNduseroptMessage(ByteBuffer buf) {
-        return NduseroptMessage.parse(null, buf);
-    }
-
-    private ByteBuffer toBuffer(String hexString) {
-        return ByteBuffer.wrap(HexEncoding.decode(hexString));
-    }
-
-    private void assertMatches(int family, int optsLen, int ifindex, byte icmpType,
-            byte icmpCode, InetAddress srcaddr, NduseroptMessage msg) {
-        assertNotNull(msg);
-        assertEquals(family, msg.family);
-        assertEquals(ifindex, msg.ifindex);
-        assertEquals(optsLen, msg.opts_len);
-        assertEquals(icmpType, msg.icmp_type);
-        assertEquals(icmpCode, msg.icmp_code);
-        assertEquals(srcaddr, msg.srcaddr);
-    }
-
-    private void assertPref64Option(String prefix, NdOption opt) {
-        assertNotNull(opt);
-        assertTrue(opt instanceof StructNdOptPref64);
-        StructNdOptPref64 pref64Opt = (StructNdOptPref64) opt;
-        assertEquals(new IpPrefix(prefix), pref64Opt.prefix);
-    }
-}
diff --git a/tests/unit/src/android/net/netlink/NetlinkConstantsTest.java b/tests/unit/src/android/net/netlink/NetlinkConstantsTest.java
deleted file mode 100644
index 131feeb..0000000
--- a/tests/unit/src/android/net/netlink/NetlinkConstantsTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.net.netlink;
-
-import static android.net.netlink.NetlinkConstants.IPCTNL_MSG_CT_DELETE;
-import static android.net.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET;
-import static android.net.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET_CTRZERO;
-import static android.net.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET_DYING;
-import static android.net.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET_STATS;
-import static android.net.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET_STATS_CPU;
-import static android.net.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET_UNCONFIRMED;
-import static android.net.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW;
-import static android.net.netlink.NetlinkConstants.NFNL_SUBSYS_CTNETLINK;
-import static android.net.netlink.NetlinkConstants.NLMSG_DONE;
-import static android.net.netlink.NetlinkConstants.NLMSG_ERROR;
-import static android.net.netlink.NetlinkConstants.NLMSG_NOOP;
-import static android.net.netlink.NetlinkConstants.NLMSG_OVERRUN;
-import static android.net.netlink.NetlinkConstants.RTM_DELADDR;
-import static android.net.netlink.NetlinkConstants.RTM_DELLINK;
-import static android.net.netlink.NetlinkConstants.RTM_DELNEIGH;
-import static android.net.netlink.NetlinkConstants.RTM_DELROUTE;
-import static android.net.netlink.NetlinkConstants.RTM_DELRULE;
-import static android.net.netlink.NetlinkConstants.RTM_GETADDR;
-import static android.net.netlink.NetlinkConstants.RTM_GETLINK;
-import static android.net.netlink.NetlinkConstants.RTM_GETNEIGH;
-import static android.net.netlink.NetlinkConstants.RTM_GETROUTE;
-import static android.net.netlink.NetlinkConstants.RTM_GETRULE;
-import static android.net.netlink.NetlinkConstants.RTM_NEWADDR;
-import static android.net.netlink.NetlinkConstants.RTM_NEWLINK;
-import static android.net.netlink.NetlinkConstants.RTM_NEWNDUSEROPT;
-import static android.net.netlink.NetlinkConstants.RTM_NEWNEIGH;
-import static android.net.netlink.NetlinkConstants.RTM_NEWROUTE;
-import static android.net.netlink.NetlinkConstants.RTM_NEWRULE;
-import static android.net.netlink.NetlinkConstants.RTM_SETLINK;
-import static android.net.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY;
-import static android.net.netlink.NetlinkConstants.stringForNlMsgType;
-import static android.system.OsConstants.NETLINK_INET_DIAG;
-import static android.system.OsConstants.NETLINK_NETFILTER;
-import static android.system.OsConstants.NETLINK_ROUTE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class NetlinkConstantsTest {
-    private static final short UNKNOWN_FAMILY = 1234;
-
-    private short makeCtType(short msgType) {
-        return (short) (NFNL_SUBSYS_CTNETLINK << 8 | (byte) msgType);
-    }
-
-    @Test
-    public void testStringForNlMsgType() {
-        assertEquals("RTM_NEWLINK", stringForNlMsgType(RTM_NEWLINK, NETLINK_ROUTE));
-        assertEquals("RTM_DELLINK", stringForNlMsgType(RTM_DELLINK, NETLINK_ROUTE));
-        assertEquals("RTM_GETLINK", stringForNlMsgType(RTM_GETLINK, NETLINK_ROUTE));
-        assertEquals("RTM_SETLINK", stringForNlMsgType(RTM_SETLINK, NETLINK_ROUTE));
-        assertEquals("RTM_NEWADDR", stringForNlMsgType(RTM_NEWADDR, NETLINK_ROUTE));
-        assertEquals("RTM_DELADDR", stringForNlMsgType(RTM_DELADDR, NETLINK_ROUTE));
-        assertEquals("RTM_GETADDR", stringForNlMsgType(RTM_GETADDR, NETLINK_ROUTE));
-        assertEquals("RTM_NEWROUTE", stringForNlMsgType(RTM_NEWROUTE, NETLINK_ROUTE));
-        assertEquals("RTM_DELROUTE", stringForNlMsgType(RTM_DELROUTE, NETLINK_ROUTE));
-        assertEquals("RTM_GETROUTE", stringForNlMsgType(RTM_GETROUTE, NETLINK_ROUTE));
-        assertEquals("RTM_NEWNEIGH", stringForNlMsgType(RTM_NEWNEIGH, NETLINK_ROUTE));
-        assertEquals("RTM_DELNEIGH", stringForNlMsgType(RTM_DELNEIGH, NETLINK_ROUTE));
-        assertEquals("RTM_GETNEIGH", stringForNlMsgType(RTM_GETNEIGH, NETLINK_ROUTE));
-        assertEquals("RTM_NEWRULE", stringForNlMsgType(RTM_NEWRULE, NETLINK_ROUTE));
-        assertEquals("RTM_DELRULE", stringForNlMsgType(RTM_DELRULE, NETLINK_ROUTE));
-        assertEquals("RTM_GETRULE", stringForNlMsgType(RTM_GETRULE, NETLINK_ROUTE));
-        assertEquals("RTM_NEWNDUSEROPT", stringForNlMsgType(RTM_NEWNDUSEROPT, NETLINK_ROUTE));
-
-        assertEquals("SOCK_DIAG_BY_FAMILY",
-                stringForNlMsgType(SOCK_DIAG_BY_FAMILY, NETLINK_INET_DIAG));
-
-        assertEquals("IPCTNL_MSG_CT_NEW",
-                stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_NEW), NETLINK_NETFILTER));
-        assertEquals("IPCTNL_MSG_CT_GET",
-                stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET), NETLINK_NETFILTER));
-        assertEquals("IPCTNL_MSG_CT_DELETE",
-                stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_DELETE), NETLINK_NETFILTER));
-        assertEquals("IPCTNL_MSG_CT_GET_CTRZERO",
-                stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET_CTRZERO), NETLINK_NETFILTER));
-        assertEquals("IPCTNL_MSG_CT_GET_STATS_CPU",
-                stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET_STATS_CPU), NETLINK_NETFILTER));
-        assertEquals("IPCTNL_MSG_CT_GET_STATS",
-                stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET_STATS), NETLINK_NETFILTER));
-        assertEquals("IPCTNL_MSG_CT_GET_DYING",
-                stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET_DYING), NETLINK_NETFILTER));
-        assertEquals("IPCTNL_MSG_CT_GET_UNCONFIRMED",
-                stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET_UNCONFIRMED), NETLINK_NETFILTER));
-    }
-
-    @Test
-    public void testStringForNlMsgType_ControlMessage() {
-        for (int family : new int[]{NETLINK_ROUTE, NETLINK_INET_DIAG, NETLINK_NETFILTER}) {
-            assertEquals("NLMSG_NOOP", stringForNlMsgType(NLMSG_NOOP, family));
-            assertEquals("NLMSG_ERROR", stringForNlMsgType(NLMSG_ERROR, family));
-            assertEquals("NLMSG_DONE", stringForNlMsgType(NLMSG_DONE, family));
-            assertEquals("NLMSG_OVERRUN", stringForNlMsgType(NLMSG_OVERRUN, family));
-        }
-    }
-
-    @Test
-    public void testStringForNlMsgType_UnknownFamily() {
-        assertTrue(stringForNlMsgType(RTM_NEWLINK, UNKNOWN_FAMILY).startsWith("unknown"));
-        assertTrue(stringForNlMsgType(SOCK_DIAG_BY_FAMILY, UNKNOWN_FAMILY).startsWith("unknown"));
-        assertTrue(stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_NEW), UNKNOWN_FAMILY)
-                .startsWith("unknown"));
-    }
-}
diff --git a/tests/unit/src/android/net/netlink/NetlinkErrorMessageTest.java b/tests/unit/src/android/net/netlink/NetlinkErrorMessageTest.java
deleted file mode 100644
index 345622f..0000000
--- a/tests/unit/src/android/net/netlink/NetlinkErrorMessageTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2015 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 android.net.netlink;
-
-import static android.net.netlink.StructNlMsgHdr.NLM_F_ACK;
-import static android.net.netlink.StructNlMsgHdr.NLM_F_REPLACE;
-import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
-import static android.system.OsConstants.NETLINK_ROUTE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.net.netlink.NetlinkConstants;
-import android.net.netlink.NetlinkErrorMessage;
-import android.net.netlink.NetlinkMessage;
-import android.net.netlink.StructNlMsgErr;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import libcore.util.HexEncoding;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class NetlinkErrorMessageTest {
-    private final String TAG = "NetlinkErrorMessageTest";
-
-    // Hexadecimal representation of packet capture.
-    public static final String NLM_ERROR_OK_HEX =
-            // struct nlmsghdr
-            "24000000" +     // length = 36
-            "0200"     +     // type = 2 (NLMSG_ERROR)
-            "0000"     +     // flags
-            "26350000" +     // seqno
-            "64100000" +     // pid = userspace process
-            // error integer
-            "00000000" +     // "errno" (0 == OK)
-            // struct nlmsghdr
-            "30000000" +     // length (48) of original request
-            "1C00"     +     // type = 28 (RTM_NEWNEIGH)
-            "0501"     +     // flags (NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE)
-            "26350000" +     // seqno
-            "00000000";      // pid = kernel
-    public static final byte[] NLM_ERROR_OK =
-            HexEncoding.decode(NLM_ERROR_OK_HEX.toCharArray(), false);
-
-    @Test
-    public void testParseNlmErrorOk() {
-        final ByteBuffer byteBuffer = ByteBuffer.wrap(NLM_ERROR_OK);
-        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
-        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
-        assertNotNull(msg);
-        assertTrue(msg instanceof NetlinkErrorMessage);
-        final NetlinkErrorMessage errorMsg = (NetlinkErrorMessage) msg;
-
-        final StructNlMsgHdr hdr = errorMsg.getHeader();
-        assertNotNull(hdr);
-        assertEquals(36, hdr.nlmsg_len);
-        assertEquals(NetlinkConstants.NLMSG_ERROR, hdr.nlmsg_type);
-        assertEquals(0, hdr.nlmsg_flags);
-        assertEquals(13606, hdr.nlmsg_seq);
-        assertEquals(4196, hdr.nlmsg_pid);
-
-        final StructNlMsgErr err = errorMsg.getNlMsgError();
-        assertNotNull(err);
-        assertEquals(0, err.error);
-        assertNotNull(err.msg);
-        assertEquals(48, err.msg.nlmsg_len);
-        assertEquals(NetlinkConstants.RTM_NEWNEIGH, err.msg.nlmsg_type);
-        assertEquals((NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE), err.msg.nlmsg_flags);
-        assertEquals(13606, err.msg.nlmsg_seq);
-        assertEquals(0, err.msg.nlmsg_pid);
-    }
-}
diff --git a/tests/unit/src/android/net/netlink/NetlinkSocketTest.java b/tests/unit/src/android/net/netlink/NetlinkSocketTest.java
deleted file mode 100644
index 5716803..0000000
--- a/tests/unit/src/android/net/netlink/NetlinkSocketTest.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2015 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 android.net.netlink;
-
-import static android.net.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE;
-import static android.system.OsConstants.NETLINK_ROUTE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.net.netlink.NetlinkSocket;
-import android.net.netlink.RtNetlinkNeighborMessage;
-import android.net.netlink.StructNlMsgHdr;
-import android.system.NetlinkSocketAddress;
-import android.system.Os;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import libcore.io.IoUtils;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.FileDescriptor;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class NetlinkSocketTest {
-    private final String TAG = "NetlinkSocketTest";
-
-    @Test
-    public void testBasicWorkingGetNeighborsQuery() throws Exception {
-        final FileDescriptor fd = NetlinkSocket.forProto(NETLINK_ROUTE);
-        assertNotNull(fd);
-
-        NetlinkSocket.connectToKernel(fd);
-
-        final NetlinkSocketAddress localAddr = (NetlinkSocketAddress) Os.getsockname(fd);
-        assertNotNull(localAddr);
-        assertEquals(0, localAddr.getGroupsMask());
-        assertTrue(0 != localAddr.getPortId());
-
-        final int TEST_SEQNO = 5;
-        final byte[] req = RtNetlinkNeighborMessage.newGetNeighborsRequest(TEST_SEQNO);
-        assertNotNull(req);
-
-        final long TIMEOUT = 500;
-        assertEquals(req.length, NetlinkSocket.sendMessage(fd, req, 0, req.length, TIMEOUT));
-
-        int neighMessageCount = 0;
-        int doneMessageCount = 0;
-
-        while (doneMessageCount == 0) {
-            ByteBuffer response = NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT);
-            assertNotNull(response);
-            assertTrue(StructNlMsgHdr.STRUCT_SIZE <= response.limit());
-            assertEquals(0, response.position());
-            assertEquals(ByteOrder.nativeOrder(), response.order());
-
-            // Verify the messages at least appears minimally reasonable.
-            while (response.remaining() > 0) {
-                final NetlinkMessage msg = NetlinkMessage.parse(response, NETLINK_ROUTE);
-                assertNotNull(msg);
-                final StructNlMsgHdr hdr = msg.getHeader();
-                assertNotNull(hdr);
-
-                if (hdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) {
-                    doneMessageCount++;
-                    continue;
-                }
-
-                assertEquals(NetlinkConstants.RTM_NEWNEIGH, hdr.nlmsg_type);
-                assertTrue(msg instanceof RtNetlinkNeighborMessage);
-                assertTrue((hdr.nlmsg_flags & StructNlMsgHdr.NLM_F_MULTI) != 0);
-                assertEquals(TEST_SEQNO, hdr.nlmsg_seq);
-                assertEquals(localAddr.getPortId(), hdr.nlmsg_pid);
-
-                neighMessageCount++;
-            }
-        }
-
-        assertEquals(1, doneMessageCount);
-        // TODO: make sure this test passes sanely in airplane mode.
-        assertTrue(neighMessageCount > 0);
-
-        IoUtils.closeQuietly(fd);
-    }
-}
diff --git a/tests/unit/src/android/net/netlink/NetlinkTestUtils.kt b/tests/unit/src/android/net/netlink/NetlinkTestUtils.kt
deleted file mode 100644
index 3b485eb..0000000
--- a/tests/unit/src/android/net/netlink/NetlinkTestUtils.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2020 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.
- */
-
-@file:JvmName("NetlinkTestUtils")
-
-package android.net.netlink
-
-import android.net.netlink.NetlinkConstants.RTM_DELNEIGH
-import android.net.netlink.NetlinkConstants.RTM_NEWNEIGH
-import libcore.util.HexEncoding
-import libcore.util.HexEncoding.encodeToString
-import java.net.Inet6Address
-import java.net.InetAddress
-
-/**
- * Make a RTM_NEWNEIGH netlink message.
- */
-fun makeNewNeighMessage(
-    neighAddr: InetAddress,
-    nudState: Short
-) = makeNeighborMessage(
-        neighAddr = neighAddr,
-        type = RTM_NEWNEIGH,
-        nudState = nudState
-)
-
-/**
- * Make a RTM_DELNEIGH netlink message.
- */
-fun makeDelNeighMessage(
-    neighAddr: InetAddress,
-    nudState: Short
-) = makeNeighborMessage(
-        neighAddr = neighAddr,
-        type = RTM_DELNEIGH,
-        nudState = nudState
-)
-
-private fun makeNeighborMessage(
-    neighAddr: InetAddress,
-    type: Short,
-    nudState: Short
-) = HexEncoding.decode(
-    /* ktlint-disable indent */
-    // -- struct nlmsghdr --
-                         // length = 88 or 76:
-    (if (neighAddr is Inet6Address) "58000000" else "4c000000") +
-    type.toLEHex() +     // type
-    "0000" +             // flags
-    "00000000" +         // seqno
-    "00000000" +         // pid (0 == kernel)
-    // struct ndmsg
-                         // family (AF_INET6 or AF_INET)
-    (if (neighAddr is Inet6Address) "0a" else "02") +
-    "00" +               // pad1
-    "0000" +             // pad2
-    "15000000" +         // interface index (21 == wlan0, on test device)
-    nudState.toLEHex() + // NUD state
-    "00" +               // flags
-    "01" +               // type
-    // -- struct nlattr: NDA_DST --
-                         // length = 20 or 8:
-    (if (neighAddr is Inet6Address) "1400" else "0800") +
-    "0100" +             // type (1 == NDA_DST, for neighbor messages)
-                         // IP address:
-    encodeToString(neighAddr.address) +
-    // -- struct nlattr: NDA_LLADDR --
-    "0a00" +             // length = 10
-    "0200" +             // type (2 == NDA_LLADDR, for neighbor messages)
-    "00005e000164" +     // MAC Address (== 00:00:5e:00:01:64)
-    "0000" +             // padding, for 4 byte alignment
-    // -- struct nlattr: NDA_PROBES --
-    "0800" +             // length = 8
-    "0400" +             // type (4 == NDA_PROBES, for neighbor messages)
-    "01000000" +         // number of probes
-    // -- struct nlattr: NDA_CACHEINFO --
-    "1400" +             // length = 20
-    "0300" +             // type (3 == NDA_CACHEINFO, for neighbor messages)
-    "05190000" +         // ndm_used, as "clock ticks ago"
-    "05190000" +         // ndm_confirmed, as "clock ticks ago"
-    "190d0000" +         // ndm_updated, as "clock ticks ago"
-    "00000000",          // ndm_refcnt
-    false /* allowSingleChar */)
-    /* ktlint-enable indent */
-
-/**
- * Convert a [Short] to a little-endian hex string.
- */
-private fun Short.toLEHex() = String.format("%04x", java.lang.Short.reverseBytes(this))
diff --git a/tests/unit/src/android/net/netlink/RtNetlinkNeighborMessageTest.java b/tests/unit/src/android/net/netlink/RtNetlinkNeighborMessageTest.java
deleted file mode 100644
index a1f1d44..0000000
--- a/tests/unit/src/android/net/netlink/RtNetlinkNeighborMessageTest.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * 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 android.net.netlink;
-
-import static android.net.netlink.NetlinkTestUtils.makeDelNeighMessage;
-import static android.net.netlink.NetlinkTestUtils.makeNewNeighMessage;
-import static android.net.netlink.StructNdMsg.NUD_STALE;
-import static android.system.OsConstants.NETLINK_ROUTE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.net.InetAddresses;
-import android.net.netlink.NetlinkConstants;
-import android.net.netlink.NetlinkMessage;
-import android.net.netlink.RtNetlinkNeighborMessage;
-import android.net.netlink.StructNdMsg;
-import android.net.netlink.StructNlMsgHdr;
-import android.system.OsConstants;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import libcore.util.HexEncoding;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.net.Inet4Address;
-import java.net.InetAddress;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Arrays;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class RtNetlinkNeighborMessageTest {
-    private final String TAG = "RtNetlinkNeighborMessageTest";
-
-    public static final byte[] RTM_DELNEIGH = makeDelNeighMessage(
-            InetAddresses.parseNumericAddress("192.168.159.254"), NUD_STALE);
-
-    public static final byte[] RTM_NEWNEIGH = makeNewNeighMessage(
-            InetAddresses.parseNumericAddress("fe80::86c9:b2ff:fe6a:ed4b"), NUD_STALE);
-
-    // An example of the full response from an RTM_GETNEIGH query.
-    private static final String RTM_GETNEIGH_RESPONSE_HEX =
-            // <-- struct nlmsghr             -->|<-- struct ndmsg           -->|<-- struct nlattr: NDA_DST             -->|<-- NDA_LLADDR          -->|<-- NDA_PROBES -->|<-- NDA_CACHEINFO                         -->|
-            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff020000000000000000000000000001 0a00 0200 333300000001 0000 0800 0400 00000000 1400 0300 a2280000 32110000 32110000 01000000" +
-            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ff000001 0a00 0200 3333ff000001 0000 0800 0400 00000000 1400 0300 0d280000 9d100000 9d100000 00000000" +
-            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 0400 80 01 1400 0100 20010db800040ca00000000000000001 0a00 0200 84c9b26aed4b 0000 0800 0400 04000000 1400 0300 90100000 90100000 90080000 01000000" +
-            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ff47da19 0a00 0200 3333ff47da19 0000 0800 0400 00000000 1400 0300 a1280000 31110000 31110000 01000000" +
-            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 14000000 4000 00 05 1400 0100 ff020000000000000000000000000016 0a00 0200 333300000016 0000 0800 0400 00000000 1400 0300 912a0000 21130000 21130000 00000000" +
-            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 14000000 4000 00 05 1400 0100 ff0200000000000000000001ffeace3b 0a00 0200 3333ffeace3b 0000 0800 0400 00000000 1400 0300 922a0000 22130000 22130000 00000000" +
-            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ff5c2a83 0a00 0200 3333ff5c2a83 0000 0800 0400 00000000 1400 0300 391c0000 c9040000 c9040000 01000000" +
-            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 01000000 4000 00 02 1400 0100 00000000000000000000000000000000 0a00 0200 000000000000 0000 0800 0400 00000000 1400 0300 cd180200 5d010200 5d010200 08000000" +
-            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff020000000000000000000000000002 0a00 0200 333300000002 0000 0800 0400 00000000 1400 0300 352a0000 c5120000 c5120000 00000000" +
-            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff020000000000000000000000000016 0a00 0200 333300000016 0000 0800 0400 00000000 1400 0300 982a0000 28130000 28130000 00000000" +
-            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 0800 80 01 1400 0100 fe8000000000000086c9b2fffe6aed4b 0a00 0200 84c9b26aed4b 0000 0800 0400 00000000 1400 0300 23000000 24000000 57000000 13000000" +
-            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ffeace3b 0a00 0200 3333ffeace3b 0000 0800 0400 00000000 1400 0300 992a0000 29130000 29130000 01000000" +
-            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 14000000 4000 00 05 1400 0100 ff020000000000000000000000000002 0a00 0200 333300000002 0000 0800 0400 00000000 1400 0300 2e2a0000 be120000 be120000 00000000" +
-            "44000000 1c00 0200 00000000 3e2b0000 02 00 0000 18000000 4000 00 03 0800 0100 00000000                         0400 0200                   0800 0400 00000000 1400 0300 75280000 05110000 05110000 22000000";
-    public static final byte[] RTM_GETNEIGH_RESPONSE =
-            HexEncoding.decode(RTM_GETNEIGH_RESPONSE_HEX.replaceAll(" ", "").toCharArray(), false);
-
-    @Test
-    public void testParseRtmDelNeigh() {
-        final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_DELNEIGH);
-        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
-        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
-        assertNotNull(msg);
-        assertTrue(msg instanceof RtNetlinkNeighborMessage);
-        final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) msg;
-
-        final StructNlMsgHdr hdr = neighMsg.getHeader();
-        assertNotNull(hdr);
-        assertEquals(76, hdr.nlmsg_len);
-        assertEquals(NetlinkConstants.RTM_DELNEIGH, hdr.nlmsg_type);
-        assertEquals(0, hdr.nlmsg_flags);
-        assertEquals(0, hdr.nlmsg_seq);
-        assertEquals(0, hdr.nlmsg_pid);
-
-        final StructNdMsg ndmsgHdr = neighMsg.getNdHeader();
-        assertNotNull(ndmsgHdr);
-        assertEquals((byte) OsConstants.AF_INET, ndmsgHdr.ndm_family);
-        assertEquals(21, ndmsgHdr.ndm_ifindex);
-        assertEquals(NUD_STALE, ndmsgHdr.ndm_state);
-        final InetAddress destination = neighMsg.getDestination();
-        assertNotNull(destination);
-        assertEquals(InetAddress.parseNumericAddress("192.168.159.254"), destination);
-    }
-
-    @Test
-    public void testParseRtmNewNeigh() {
-        final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_NEWNEIGH);
-        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
-        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
-        assertNotNull(msg);
-        assertTrue(msg instanceof RtNetlinkNeighborMessage);
-        final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) msg;
-
-        final StructNlMsgHdr hdr = neighMsg.getHeader();
-        assertNotNull(hdr);
-        assertEquals(88, hdr.nlmsg_len);
-        assertEquals(NetlinkConstants.RTM_NEWNEIGH, hdr.nlmsg_type);
-        assertEquals(0, hdr.nlmsg_flags);
-        assertEquals(0, hdr.nlmsg_seq);
-        assertEquals(0, hdr.nlmsg_pid);
-
-        final StructNdMsg ndmsgHdr = neighMsg.getNdHeader();
-        assertNotNull(ndmsgHdr);
-        assertEquals((byte) OsConstants.AF_INET6, ndmsgHdr.ndm_family);
-        assertEquals(21, ndmsgHdr.ndm_ifindex);
-        assertEquals(NUD_STALE, ndmsgHdr.ndm_state);
-        final InetAddress destination = neighMsg.getDestination();
-        assertNotNull(destination);
-        assertEquals(InetAddress.parseNumericAddress("fe80::86c9:b2ff:fe6a:ed4b"), destination);
-    }
-
-    @Test
-    public void testParseRtmGetNeighResponse() {
-        final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_GETNEIGH_RESPONSE);
-        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
-
-        int messageCount = 0;
-        while (byteBuffer.remaining() > 0) {
-            final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
-            assertNotNull(msg);
-            assertTrue(msg instanceof RtNetlinkNeighborMessage);
-            final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) msg;
-
-            final StructNlMsgHdr hdr = neighMsg.getHeader();
-            assertNotNull(hdr);
-            assertEquals(NetlinkConstants.RTM_NEWNEIGH, hdr.nlmsg_type);
-            assertEquals(StructNlMsgHdr.NLM_F_MULTI, hdr.nlmsg_flags);
-            assertEquals(0, hdr.nlmsg_seq);
-            assertEquals(11070, hdr.nlmsg_pid);
-
-            final int probes = neighMsg.getProbes();
-            assertTrue("Unexpected number of probes. Got " +  probes + ", max=5",
-                    probes < 5);
-            final int ndm_refcnt = neighMsg.getCacheInfo().ndm_refcnt;
-            assertTrue("nda_cacheinfo has unexpectedly high ndm_refcnt: " + ndm_refcnt,
-                    ndm_refcnt < 0x100);
-
-            messageCount++;
-        }
-        // TODO: add more detailed spot checks.
-        assertEquals(14, messageCount);
-    }
-
-    @Test
-    public void testCreateRtmNewNeighMessage() {
-        final int seqNo = 2635;
-        final int ifIndex = 14;
-        final byte[] llAddr =
-                new byte[] { (byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5, (byte) 6 };
-
-        // Hexadecimal representation of our created packet.
-        final String expectedNewNeighHex =
-                // struct nlmsghdr
-                "30000000" +     // length = 48
-                "1c00" +         // type = 28 (RTM_NEWNEIGH)
-                "0501" +         // flags (NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE)
-                "4b0a0000" +     // seqno
-                "00000000" +     // pid (0 == kernel)
-                // struct ndmsg
-                "02" +           // family
-                "00" +           // pad1
-                "0000" +         // pad2
-                "0e000000" +     // interface index (14)
-                "0800" +         // NUD state (0x08 == NUD_DELAY)
-                "00" +           // flags
-                "00" +           // type
-                // struct nlattr: NDA_DST
-                "0800" +         // length = 8
-                "0100" +         // type (1 == NDA_DST, for neighbor messages)
-                "7f000001" +     // IPv4 address (== 127.0.0.1)
-                // struct nlattr: NDA_LLADDR
-                "0a00" +         // length = 10
-                "0200" +         // type (2 == NDA_LLADDR, for neighbor messages)
-                "010203040506" + // MAC Address (== 01:02:03:04:05:06)
-                "0000";          // padding, for 4 byte alignment
-        final byte[] expectedNewNeigh =
-                HexEncoding.decode(expectedNewNeighHex.toCharArray(), false);
-
-        final byte[] bytes = RtNetlinkNeighborMessage.newNewNeighborMessage(
-            seqNo, Inet4Address.LOOPBACK, StructNdMsg.NUD_DELAY, ifIndex, llAddr);
-        if (!Arrays.equals(expectedNewNeigh, bytes)) {
-            assertEquals(expectedNewNeigh.length, bytes.length);
-            for (int i = 0; i < Math.min(expectedNewNeigh.length, bytes.length); i++) {
-                assertEquals(expectedNewNeigh[i], bytes[i]);
-            }
-        }
-    }
-}
diff --git a/tests/unit/src/android/net/netlink/StructNdOptPref64Test.java b/tests/unit/src/android/net/netlink/StructNdOptPref64Test.java
deleted file mode 100644
index 0f3020d..0000000
--- a/tests/unit/src/android/net/netlink/StructNdOptPref64Test.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * 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 android.net.netlink;
-
-import static android.net.netlink.StructNdOptPref64.getScaledLifetimePlc;
-import static android.net.netlink.StructNdOptPref64.plcToPrefixLength;
-import static android.net.netlink.StructNdOptPref64.prefixLengthToPlc;
-
-import static com.android.testutils.MiscAsserts.assertThrows;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import android.net.IpPrefix;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import libcore.util.HexEncoding;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.net.InetAddress;
-import java.nio.ByteBuffer;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class StructNdOptPref64Test {
-
-    private static final String PREFIX1 = "64:ff9b::";
-    private static final String PREFIX2 = "2001:db8:1:2:3:64::";
-
-    private static byte[] prefixBytes(String addrString) throws Exception {
-        InetAddress addr = InetAddress.getByName(addrString);
-        byte[] prefixBytes = new byte[12];
-        System.arraycopy(addr.getAddress(), 0, prefixBytes, 0, 12);
-        return prefixBytes;
-    }
-
-    private static IpPrefix prefix(String addrString, int prefixLength) throws Exception {
-        return new IpPrefix(InetAddress.getByName(addrString), prefixLength);
-    }
-
-    private void assertPref64OptMatches(int lifetime, IpPrefix prefix, StructNdOptPref64 opt) {
-        assertEquals(StructNdOptPref64.TYPE, opt.type);
-        assertEquals(2, opt.length);
-        assertEquals(lifetime, opt.lifetime);
-        assertEquals(prefix, opt.prefix);
-    }
-
-    private void assertToByteBufferMatches(StructNdOptPref64 opt, String expected) {
-        String actual = HexEncoding.encodeToString(opt.toByteBuffer().array());
-        assertEquals(expected, actual);
-    }
-
-    private ByteBuffer makeNdOptPref64(int lifetime, byte[] prefix, int prefixLengthCode) {
-        if (prefix.length != 12) throw new IllegalArgumentException("Prefix must be 12 bytes");
-
-        ByteBuffer buf = ByteBuffer.allocate(16)
-                .put((byte) StructNdOptPref64.TYPE)
-                .put((byte) StructNdOptPref64.LENGTH)
-                .putShort(getScaledLifetimePlc(lifetime, prefixLengthCode))
-                .put(prefix, 0, 12);
-
-        buf.flip();
-        return buf;
-    }
-
-    @Test
-    public void testParseCannedOption() throws Exception {
-        String hexBytes = "2602"               // type=38, len=2 (16 bytes)
-                + "0088"                       // lifetime=136, PLC=0 (/96)
-                + "20010DB80003000400050006";  // 2001:db8:3:4:5:6/96
-        byte[] rawBytes = HexEncoding.decode(hexBytes);
-        StructNdOptPref64 opt = StructNdOptPref64.parse(ByteBuffer.wrap(rawBytes));
-        assertPref64OptMatches(136, prefix("2001:DB8:3:4:5:6::", 96), opt);
-        assertToByteBufferMatches(opt, hexBytes);
-
-        hexBytes = "2602"                      // type=38, len=2 (16 bytes)
-                + "2752"                       // lifetime=10064, PLC=2 (/56)
-                + "0064FF9B0000000000000000";  // 64:ff9b::/56
-        rawBytes = HexEncoding.decode(hexBytes);
-        opt = StructNdOptPref64.parse(ByteBuffer.wrap(rawBytes));
-        assertPref64OptMatches(10064, prefix("64:FF9B::", 56), opt);
-        assertToByteBufferMatches(opt, hexBytes);
-    }
-
-    @Test
-    public void testParsing() throws Exception {
-        // Valid.
-        ByteBuffer buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 0);
-        StructNdOptPref64 opt = StructNdOptPref64.parse(buf);
-        assertPref64OptMatches(600, prefix(PREFIX1, 96), opt);
-
-        // Valid, zero lifetime, /64.
-        buf = makeNdOptPref64(0, prefixBytes(PREFIX1), 1);
-        opt = StructNdOptPref64.parse(buf);
-        assertPref64OptMatches(0, prefix(PREFIX1, 64), opt);
-
-        // Valid, low lifetime, /56.
-        buf = makeNdOptPref64(8, prefixBytes(PREFIX2), 2);
-        opt = StructNdOptPref64.parse(buf);
-        assertPref64OptMatches(8, prefix(PREFIX2, 56), opt);
-        assertEquals(new IpPrefix("2001:db8:1::/56"), opt.prefix);  // Prefix is truncated.
-
-        // Valid, maximum lifetime, /32.
-        buf = makeNdOptPref64(65528, prefixBytes(PREFIX2), 5);
-        opt = StructNdOptPref64.parse(buf);
-        assertPref64OptMatches(65528, prefix(PREFIX2, 32), opt);
-        assertEquals(new IpPrefix("2001:db8::/32"), opt.prefix);  // Prefix is truncated.
-
-        // Lifetime not divisible by 8.
-        buf = makeNdOptPref64(300, prefixBytes(PREFIX2), 0);
-        opt = StructNdOptPref64.parse(buf);
-        assertPref64OptMatches(296, prefix(PREFIX2, 96), opt);
-
-        // Invalid prefix length codes.
-        buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 6);
-        assertNull(StructNdOptPref64.parse(buf));
-        buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 7);
-        assertNull(StructNdOptPref64.parse(buf));
-
-        // Truncated to varying lengths...
-        buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 3);
-        final int len = buf.limit();
-        for (int i = 0; i < buf.limit() - 1; i++) {
-            buf.flip();
-            buf.limit(i);
-            assertNull("Option truncated to " + i + " bytes, should have returned null",
-                    StructNdOptPref64.parse(buf));
-        }
-        buf.flip();
-        buf.limit(len);
-        // ... but otherwise OK.
-        opt = StructNdOptPref64.parse(buf);
-        assertPref64OptMatches(600, prefix(PREFIX1, 48), opt);
-    }
-
-    @Test
-    public void testToString() throws Exception {
-        ByteBuffer buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 4);
-        StructNdOptPref64 opt = StructNdOptPref64.parse(buf);
-        assertPref64OptMatches(600, prefix(PREFIX1, 40), opt);
-        assertEquals("NdOptPref64(64:ff9b::/40, 600)", opt.toString());
-    }
-
-    private void assertInvalidPlc(int plc) {
-        assertThrows(IllegalArgumentException.class, () -> plcToPrefixLength(plc));
-    }
-
-    @Test
-    public void testPrefixLengthToPlc() {
-        for (int i = 0; i < 6; i++) {
-            assertEquals(i, prefixLengthToPlc(plcToPrefixLength(i)));
-        }
-        assertInvalidPlc(-1);
-        assertInvalidPlc(6);
-        assertInvalidPlc(7);
-        assertEquals(0, prefixLengthToPlc(96));
-    }
-
-
-    private void assertInvalidParameters(IpPrefix prefix, int lifetime) {
-        assertThrows(IllegalArgumentException.class, () -> new StructNdOptPref64(prefix, lifetime));
-    }
-
-    @Test
-    public void testToByteBuffer() throws Exception {
-        final IpPrefix prefix1 = prefix(PREFIX1, 56);
-        final IpPrefix prefix2 = prefix(PREFIX2, 96);
-
-        StructNdOptPref64 opt = new StructNdOptPref64(prefix1, 600);
-        assertToByteBufferMatches(opt, "2602025A0064FF9B0000000000000000");
-        assertEquals(new IpPrefix("64:ff9b::/56"), opt.prefix);
-        assertEquals(600, opt.lifetime);
-
-        opt = new StructNdOptPref64(prefix2, 65519);
-        assertToByteBufferMatches(opt, "2602FFE820010DB80001000200030064");
-        assertEquals(new IpPrefix("2001:db8:1:2:3:64::/96"), opt.prefix);
-        assertEquals(65512, opt.lifetime);
-
-        assertInvalidParameters(prefix1, 65535);
-        assertInvalidParameters(prefix2, -1);
-        assertInvalidParameters(prefix("1.2.3.4", 32), 600);
-    }
-}
diff --git a/tests/unit/src/android/net/netlink/StructNlMsgHdrTest.java b/tests/unit/src/android/net/netlink/StructNlMsgHdrTest.java
deleted file mode 100644
index b44b31d..0000000
--- a/tests/unit/src/android/net/netlink/StructNlMsgHdrTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.net.netlink;
-
-import static org.junit.Assert.fail;
-
-import android.system.OsConstants;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class StructNlMsgHdrTest {
-
-    public static final short TEST_NLMSG_LEN = 16;
-    public static final short TEST_NLMSG_FLAGS = StructNlMsgHdr.NLM_F_REQUEST
-            | StructNlMsgHdr.NLM_F_MULTI | StructNlMsgHdr.NLM_F_ACK | StructNlMsgHdr.NLM_F_ECHO;
-    public static final short TEST_NLMSG_SEQ = 1234;
-    public static final short TEST_NLMSG_PID = 5678;
-
-    // Checking the header string nlmsg_{len, ..} of the number can make sure that the checking
-    // number comes from the expected element.
-    // TODO: Verify more flags once StructNlMsgHdr can distinguish the flags which have the same
-    // value. For example, NLM_F_MATCH (0x200) and NLM_F_EXCL (0x200) can't be distinguished.
-    // See StructNlMsgHdrTest#stringForNlMsgFlags.
-    public static final String TEST_NLMSG_LEN_STR = "nlmsg_len{16}";
-    public static final String TEST_NLMSG_FLAGS_STR =
-            "NLM_F_REQUEST|NLM_F_MULTI|NLM_F_ACK|NLM_F_ECHO";
-    public static final String TEST_NLMSG_SEQ_STR = "nlmsg_seq{1234}";
-    public static final String TEST_NLMSG_PID_STR = "nlmsg_pid{5678}";
-
-    private StructNlMsgHdr makeStructNlMsgHdr(short type) {
-        final StructNlMsgHdr struct = new StructNlMsgHdr();
-        struct.nlmsg_len = TEST_NLMSG_LEN;
-        struct.nlmsg_type = type;
-        struct.nlmsg_flags = TEST_NLMSG_FLAGS;
-        struct.nlmsg_seq = TEST_NLMSG_SEQ;
-        struct.nlmsg_pid = TEST_NLMSG_PID;
-        return struct;
-    }
-
-    private static void assertContains(String actualValue, String expectedSubstring) {
-        if (actualValue.contains(expectedSubstring)) return;
-        fail("\"" + actualValue + "\" does not contain \"" + expectedSubstring + "\"");
-    }
-
-    @Test
-    public void testToString() {
-        StructNlMsgHdr struct = makeStructNlMsgHdr(NetlinkConstants.RTM_NEWADDR);
-        String s = struct.toString();
-        assertContains(s, TEST_NLMSG_LEN_STR);
-        assertContains(s, TEST_NLMSG_FLAGS_STR);
-        assertContains(s, TEST_NLMSG_SEQ_STR);
-        assertContains(s, TEST_NLMSG_PID_STR);
-        assertContains(s, "nlmsg_type{20()}");
-
-        struct = makeStructNlMsgHdr(NetlinkConstants.SOCK_DIAG_BY_FAMILY);
-        s = struct.toString();
-        assertContains(s, TEST_NLMSG_LEN_STR);
-        assertContains(s, TEST_NLMSG_FLAGS_STR);
-        assertContains(s, TEST_NLMSG_SEQ_STR);
-        assertContains(s, TEST_NLMSG_PID_STR);
-        assertContains(s, "nlmsg_type{20()}");
-    }
-
-    @Test
-    public void testToStringWithNetlinkFamily() {
-        StructNlMsgHdr struct = makeStructNlMsgHdr(NetlinkConstants.RTM_NEWADDR);
-        String s = struct.toString(OsConstants.NETLINK_ROUTE);
-        assertContains(s, TEST_NLMSG_LEN_STR);
-        assertContains(s, TEST_NLMSG_FLAGS_STR);
-        assertContains(s, TEST_NLMSG_SEQ_STR);
-        assertContains(s, TEST_NLMSG_PID_STR);
-        assertContains(s, "nlmsg_type{20(RTM_NEWADDR)}");
-
-        struct = makeStructNlMsgHdr(NetlinkConstants.SOCK_DIAG_BY_FAMILY);
-        s = struct.toString(OsConstants.NETLINK_INET_DIAG);
-        assertContains(s, TEST_NLMSG_LEN_STR);
-        assertContains(s, TEST_NLMSG_FLAGS_STR);
-        assertContains(s, TEST_NLMSG_SEQ_STR);
-        assertContains(s, TEST_NLMSG_PID_STR);
-        assertContains(s, "nlmsg_type{20(SOCK_DIAG_BY_FAMILY)}");
-    }
-}
diff --git a/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java b/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java
index 436b81a..716abaa 100644
--- a/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java
+++ b/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java
@@ -17,12 +17,22 @@
 package android.net.shared;
 
 import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.ip.IIpClient.PROV_IPV4_DHCP;
+import static android.net.ip.IIpClient.PROV_IPV4_DISABLED;
+import static android.net.ip.IIpClient.PROV_IPV4_STATIC;
+import static android.net.ip.IIpClient.PROV_IPV6_DISABLED;
+import static android.net.ip.IIpClient.PROV_IPV6_LINKLOCAL;
+import static android.net.ip.IIpClient.PROV_IPV6_SLAAC;
 import static android.net.shared.ProvisioningConfiguration.fromStableParcelable;
+import static android.net.shared.ProvisioningConfiguration.ipv4ProvisioningModeToString;
+import static android.net.shared.ProvisioningConfiguration.ipv6ProvisioningModeToString;
 
 import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 
 import android.net.LinkAddress;
 import android.net.MacAddress;
@@ -76,32 +86,66 @@
         return options;
     }
 
-    @Before
-    public void setUp() {
-        mConfig = new ProvisioningConfiguration();
-        mConfig.mEnableIPv4 = true;
-        mConfig.mEnableIPv6 = true;
-        mConfig.mUsingMultinetworkPolicyTracker = true;
-        mConfig.mUsingIpReachabilityMonitor = true;
-        mConfig.mRequestedPreDhcpActionMs = 42;
-        mConfig.mInitialConfig = new InitialConfiguration();
-        mConfig.mInitialConfig.ipAddresses.add(
+    private ProvisioningConfiguration makeTestProvisioningConfiguration() {
+        final ProvisioningConfiguration config = new ProvisioningConfiguration();
+        config.mUsingMultinetworkPolicyTracker = true;
+        config.mUsingIpReachabilityMonitor = true;
+        config.mRequestedPreDhcpActionMs = 42;
+        config.mInitialConfig = new InitialConfiguration();
+        config.mInitialConfig.ipAddresses.add(
                 new LinkAddress(parseNumericAddress("192.168.42.42"), 24));
-        mConfig.mStaticIpConfig = new StaticIpConfiguration();
-        mConfig.mStaticIpConfig.ipAddress =
+        config.mStaticIpConfig = new StaticIpConfiguration();
+        config.mStaticIpConfig.ipAddress =
                 new LinkAddress(parseNumericAddress("2001:db8::42"), 90);
         // Not testing other InitialConfig or StaticIpConfig members: they have their own unit tests
-        mConfig.mApfCapabilities = new ApfCapabilities(1, 2, 3);
-        mConfig.mProvisioningTimeoutMs = 4200;
-        mConfig.mIPv6AddrGenMode = 123;
-        mConfig.mNetwork = new Network(321);
-        mConfig.mDisplayName = "test_config";
-        mConfig.mEnablePreconnection = false;
-        mConfig.mScanResultInfo = makeScanResultInfo("ssid");
-        mConfig.mLayer2Info = new Layer2Information("some l2key", "some cluster",
+        config.mApfCapabilities = new ApfCapabilities(1, 2, 3);
+        config.mProvisioningTimeoutMs = 4200;
+        config.mIPv6AddrGenMode = 123;
+        config.mNetwork = new Network(321);
+        config.mDisplayName = "test_config";
+        config.mEnablePreconnection = false;
+        config.mScanResultInfo = makeScanResultInfo("ssid");
+        config.mLayer2Info = new Layer2Information("some l2key", "some cluster",
                 MacAddress.fromString("00:01:02:03:04:05"));
-        mConfig.mDhcpOptions = makeCustomizedDhcpOptions((byte) 60,
+        config.mDhcpOptions = makeCustomizedDhcpOptions((byte) 60,
                 new String("android-dhcp-11").getBytes());
+        config.mIPv4ProvisioningMode = PROV_IPV4_DHCP;
+        config.mIPv6ProvisioningMode = PROV_IPV6_SLAAC;
+        return config;
+    }
+
+    private ProvisioningConfigurationParcelable makeTestProvisioningConfigurationParcelable() {
+        final ProvisioningConfigurationParcelable p = new ProvisioningConfigurationParcelable();
+        p.enableIPv4 = true;
+        p.enableIPv6 = true;
+        p.usingMultinetworkPolicyTracker = true;
+        p.usingIpReachabilityMonitor = true;
+        p.requestedPreDhcpActionMs = 42;
+        final InitialConfiguration initialConfig = new InitialConfiguration();
+        initialConfig.ipAddresses.add(
+                new LinkAddress(parseNumericAddress("192.168.42.42"), 24));
+        p.initialConfig = initialConfig.toStableParcelable();
+        p.staticIpConfig = new StaticIpConfiguration();
+        p.staticIpConfig.ipAddress =
+                new LinkAddress(parseNumericAddress("2001:db8::42"), 90);
+        p.apfCapabilities = new ApfCapabilities(1, 2, 3);
+        p.provisioningTimeoutMs = 4200;
+        p.ipv6AddrGenMode = 123;
+        p.network = new Network(321);
+        p.displayName = "test_config";
+        p.enablePreconnection = false;
+        final ScanResultInfo scanResultInfo = makeScanResultInfo("ssid");
+        p.scanResultInfo = scanResultInfo.toStableParcelable();
+        final Layer2Information layer2Info = new Layer2Information("some l2key", "some cluster",
+                MacAddress.fromString("00:01:02:03:04:05"));
+        p.layer2Info = layer2Info.toStableParcelable();
+        p.options = makeCustomizedDhcpOptions((byte) 60, new String("android-dhcp-11").getBytes());
+        return p;
+    }
+
+    @Before
+    public void setUp() {
+        mConfig = makeTestProvisioningConfiguration();
         // Any added field must be included in equals() to be tested properly
         assertFieldCountEquals(16, ProvisioningConfiguration.class);
     }
@@ -153,9 +197,43 @@
         doParcelUnparcelTest();
     }
 
+    @Test
+    public void testParcelUnparcel_DisabledIpProvisioningMode() {
+        mConfig.mIPv4ProvisioningMode = PROV_IPV4_DISABLED;
+        mConfig.mIPv6ProvisioningMode = PROV_IPV6_DISABLED;
+        doParcelUnparcelTest();
+
+        assertFalse(mConfig.toStableParcelable().enableIPv4);
+        assertFalse(mConfig.toStableParcelable().enableIPv6);
+    }
+
+    @Test
+    public void testParcelUnparcel_enabledIpProvisioningMode() {
+        mConfig.mIPv4ProvisioningMode = PROV_IPV4_DHCP;
+        mConfig.mIPv6ProvisioningMode = PROV_IPV6_SLAAC;
+        doParcelUnparcelTest();
+
+        assertTrue(mConfig.toStableParcelable().enableIPv4);
+        assertTrue(mConfig.toStableParcelable().enableIPv6);
+    }
+
+    @Test
+    public void testParcelUnparcel_IpProvisioningModefromOldStableParcelable() {
+        final ProvisioningConfigurationParcelable p = makeTestProvisioningConfigurationParcelable();
+        final ProvisioningConfiguration unparceled = fromStableParcelable(p,
+                11 /* interface version */);
+        assertEquals(mConfig, unparceled);
+    }
+
+    @Test
+    public void testParcelUnparcel_WithIpv6LinkLocalOnly() {
+        mConfig.mIPv6ProvisioningMode = PROV_IPV6_LINKLOCAL;
+        doParcelUnparcelTest();
+    }
+
     private void doParcelUnparcelTest() {
         final ProvisioningConfiguration unparceled =
-                fromStableParcelable(mConfig.toStableParcelable());
+                fromStableParcelable(mConfig.toStableParcelable(), 12 /* interface version */);
         assertEquals(mConfig, unparceled);
     }
 
@@ -163,8 +241,6 @@
     public void testEquals() {
         assertEquals(mConfig, new ProvisioningConfiguration(mConfig));
 
-        assertNotEqualsAfterChange(c -> c.mEnableIPv4 = false);
-        assertNotEqualsAfterChange(c -> c.mEnableIPv6 = false);
         assertNotEqualsAfterChange(c -> c.mUsingMultinetworkPolicyTracker = false);
         assertNotEqualsAfterChange(c -> c.mUsingIpReachabilityMonitor = false);
         assertNotEqualsAfterChange(c -> c.mRequestedPreDhcpActionMs++);
@@ -198,6 +274,10 @@
                   new String("vendor-class-identifier").getBytes()));
         assertNotEqualsAfterChange(c -> c.mDhcpOptions = makeCustomizedDhcpOptions((byte) 77,
                   new String("vendor-class-identifier").getBytes()));
+        assertNotEqualsAfterChange(c -> c.mIPv4ProvisioningMode = PROV_IPV4_DISABLED);
+        assertNotEqualsAfterChange(c -> c.mIPv4ProvisioningMode = PROV_IPV4_STATIC);
+        assertNotEqualsAfterChange(c -> c.mIPv6ProvisioningMode = PROV_IPV6_DISABLED);
+        assertNotEqualsAfterChange(c -> c.mIPv6ProvisioningMode = PROV_IPV6_LINKLOCAL);
         assertFieldCountEquals(16, ProvisioningConfiguration.class);
     }
 
@@ -223,7 +303,8 @@
             + " android.net.Layer2InformationParcelable{l2Key: some l2key,"
             + " cluster: some cluster, bssid: %s},"
             + " options: [android.net.networkstack.aidl.dhcp.DhcpOption{type: 60,"
-            + " value: [97, 110, 100, 114, 111, 105, 100, 45, 100, 104, 99, 112, 45, 49, 49]}]}";
+            + " value: [97, 110, 100, 114, 111, 105, 100, 45, 100, 104, 99, 112, 45, 49, 49]}],"
+            + " ipv4ProvisioningMode: 2, ipv6ProvisioningMode: 1}";
 
     @Test
     public void testParcelableToString() {
@@ -235,4 +316,17 @@
         expected = String.format(TEMPLATE, "null");
         assertEquals(expected, parcelWithNull.toString());
     }
+
+    @Test
+    public void testIpProvisioningModeToString() {
+        assertEquals("disabled", ipv4ProvisioningModeToString(PROV_IPV4_DISABLED));
+        assertEquals("static", ipv4ProvisioningModeToString(PROV_IPV4_STATIC));
+        assertEquals("dhcp", ipv4ProvisioningModeToString(PROV_IPV4_DHCP));
+        assertEquals("unknown", ipv4ProvisioningModeToString(0x03 /* unknown mode */));
+
+        assertEquals("disabled", ipv6ProvisioningModeToString(PROV_IPV6_DISABLED));
+        assertEquals("slaac", ipv6ProvisioningModeToString(PROV_IPV6_SLAAC));
+        assertEquals("link-local", ipv6ProvisioningModeToString(PROV_IPV6_LINKLOCAL));
+        assertEquals("unknown", ipv6ProvisioningModeToString(0x03 /* unknown mode */));
+    }
 }
diff --git a/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt b/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt
index 5b91985..5ca20d8 100644
--- a/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt
+++ b/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt
@@ -1,5 +1,6 @@
 package android.net.testutils
 
+import android.annotation.SuppressLint
 import android.net.LinkAddress
 import android.net.LinkProperties
 import android.net.Network
@@ -7,6 +8,15 @@
 import com.android.testutils.ConcurrentInterpreter
 import com.android.testutils.InterpretMatcher
 import com.android.testutils.RecorderCallback.CallbackEntry
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.AVAILABLE
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.BLOCKED_STATUS
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LINK_PROPERTIES_CHANGED
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LOSING
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.NETWORK_CAPS_UPDATED
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LOST
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.RESUMED
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.SUSPENDED
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.UNAVAILABLE
 import com.android.testutils.RecorderCallback.CallbackEntry.Available
 import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
 import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
@@ -33,6 +43,7 @@
 const val TEST_INTERFACE_NAME = "testInterfaceName"
 
 @RunWith(JUnit4::class)
+@SuppressLint("NewApi") // Uses hidden APIs, which the linter would identify as missing APIs.
 class TestableNetworkCallbackTest {
     private lateinit var mCallback: TestableNetworkCallback
 
@@ -102,6 +113,20 @@
     }
 
     @Test
+    fun testAssertNoCallbackThat() {
+        val net = Network(101)
+        mCallback.assertNoCallbackThat { it is Available }
+        mCallback.onAvailable(net)
+        // Expect no blocked status change. Receive other callback does not fail the test.
+        mCallback.assertNoCallbackThat { it is BlockedStatus }
+        mCallback.onBlockedStatusChanged(net, true)
+        assertFails { mCallback.assertNoCallbackThat { it is BlockedStatus } }
+        mCallback.onBlockedStatusChanged(net, false)
+        mCallback.onCapabilitiesChanged(net, NetworkCapabilities())
+        assertFails { mCallback.assertNoCallbackThat { it is CapabilitiesChanged } }
+    }
+
+    @Test
     fun testCapabilitiesWithAndWithout() {
         val net = Network(101)
         val matcher = makeHasNetwork(101)
@@ -245,6 +270,41 @@
             onBlockedStatus(199)       | poll(1) = BlockedStatus(199) time 0..3
         """)
     }
+
+    @Test
+    fun testEventuallyExpect() {
+        // TODO: Current test does not verify the inline one. Also verify the behavior after
+        // aligning two eventuallyExpect()
+        val net1 = Network(100)
+        val net2 = Network(101)
+        mCallback.onAvailable(net1)
+        mCallback.onCapabilitiesChanged(net1, NetworkCapabilities())
+        mCallback.onLinkPropertiesChanged(net1, LinkProperties())
+        mCallback.eventuallyExpect(LINK_PROPERTIES_CHANGED) {
+            net1.equals(it.network)
+        }
+        // No further new callback. Expect no callback.
+        assertFails { mCallback.eventuallyExpect(LINK_PROPERTIES_CHANGED) }
+
+        // Verify no predicate set.
+        mCallback.onAvailable(net2)
+        mCallback.onLinkPropertiesChanged(net2, LinkProperties())
+        mCallback.onBlockedStatusChanged(net1, false)
+        mCallback.eventuallyExpect(BLOCKED_STATUS) { net1.equals(it.network) }
+        // Verify no callback received if the callback does not happen.
+        assertFails { mCallback.eventuallyExpect(LOSING) }
+    }
+
+    @Test
+    fun testEventuallyExpectOnMultiThreads() {
+        TNCInterpreter.interpretTestSpec(initial = mCallback, lineShift = 1,
+                threadTransform = { cb -> cb.createLinkedCopy() }, spec = """
+                onAvailable(100)                   | eventually(CapabilitiesChanged(100), 1) fails
+                sleep ; onCapabilitiesChanged(100) | eventually(CapabilitiesChanged(100), 2)
+                onAvailable(101) ; onBlockedStatus(101) | eventually(BlockedStatus(100), 2) fails
+                onSuspended(100) ; sleep ; onLost(100)  | eventually(Lost(100), 2)
+        """)
+    }
 }
 
 private object TNCInterpreter : ConcurrentInterpreter<TestableNetworkCallback>(interpretTable)
@@ -254,6 +314,7 @@
     return CallbackEntry::class.sealedSubclasses.first { it.simpleName == name }
 }
 
+@SuppressLint("NewApi") // Uses hidden APIs, which the linter would identify as missing APIs.
 private val interpretTable = listOf<InterpretMatcher<TestableNetworkCallback>>(
     // Interpret "Available(xx)" as "call to onAvailable with netId xx", and likewise for
     // all callback types. This is implemented above by enumerating the subclasses of
@@ -289,5 +350,26 @@
     },
     Regex("""poll\((\d+)\)""") to { i, cb, t ->
         cb.pollForNextCallback(t.timeArg(1))
+    },
+    // Interpret "eventually(Available(xx), timeout)" as calling eventuallyExpect that expects
+    // CallbackEntry.AVAILABLE with netId of xx within timeout*INTERPRET_TIME_UNIT timeout, and
+    // likewise for all callback types.
+    Regex("""eventually\(($EntryList)\((\d+)\),\s+(\d+)\)""") to { i, cb, t ->
+        val net = Network(t.intArg(2))
+        val timeout = t.timeArg(3)
+        when (t.strArg(1)) {
+            "Available" -> cb.eventuallyExpect(AVAILABLE, timeout) { net == it.network }
+            "Suspended" -> cb.eventuallyExpect(SUSPENDED, timeout) { net == it.network }
+            "Resumed" -> cb.eventuallyExpect(RESUMED, timeout) { net == it.network }
+            "Losing" -> cb.eventuallyExpect(LOSING, timeout) { net == it.network }
+            "Lost" -> cb.eventuallyExpect(LOST, timeout) { net == it.network }
+            "Unavailable" -> cb.eventuallyExpect(UNAVAILABLE, timeout) { net == it.network }
+            "BlockedStatus" -> cb.eventuallyExpect(BLOCKED_STATUS, timeout) { net == it.network }
+            "CapabilitiesChanged" ->
+                cb.eventuallyExpect(NETWORK_CAPS_UPDATED, timeout) { net == it.network }
+            "LinkPropertiesChanged" ->
+                cb.eventuallyExpect(LINK_PROPERTIES_CHANGED, timeout) { net == it.network }
+            else -> fail("Unknown callback type")
+        }
     }
 )
diff --git a/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt b/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt
deleted file mode 100644
index 9fb4d8c..0000000
--- a/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt
+++ /dev/null
@@ -1,446 +0,0 @@
-/*
- * 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.net.module.util
-
-import com.android.testutils.ConcurrentInterpreter
-import com.android.testutils.InterpretException
-import com.android.testutils.InterpretMatcher
-import com.android.testutils.SyntaxException
-import com.android.testutils.__FILE__
-import com.android.testutils.__LINE__
-import com.android.testutils.intArg
-import com.android.testutils.strArg
-import com.android.testutils.timeArg
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import java.util.concurrent.CyclicBarrier
-import java.util.concurrent.TimeUnit
-import java.util.concurrent.atomic.AtomicInteger
-import kotlin.system.measureTimeMillis
-import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
-import kotlin.test.assertFalse
-import kotlin.test.assertNotEquals
-import kotlin.test.assertNull
-import kotlin.test.assertTrue
-import kotlin.test.fail
-
-val TEST_VALUES = listOf(4, 13, 52, 94, 41, 68, 11, 13, 51, 0, 91, 94, 33, 98, 14)
-const val ABSENT_VALUE = 2
-// Caution in changing these : some tests rely on the fact that TEST_TIMEOUT > 2 * SHORT_TIMEOUT
-// and LONG_TIMEOUT > 2 * TEST_TIMEOUT
-const val SHORT_TIMEOUT = 40L // ms
-const val TEST_TIMEOUT = 200L // ms
-const val LONG_TIMEOUT = 5000L // ms
-
-@RunWith(JUnit4::class)
-class TrackRecordTest {
-    @Test
-    fun testAddAndSizeAndGet() {
-        val repeats = 22 // arbitrary
-        val record = ArrayTrackRecord<Int>()
-        assertEquals(0, record.size)
-        repeat(repeats) { i -> record.add(i + 2) }
-        assertEquals(repeats, record.size)
-        record.add(2)
-        assertEquals(repeats + 1, record.size)
-
-        assertEquals(11, record[9])
-        assertEquals(11, record.getOrNull(9))
-        assertEquals(2, record[record.size - 1])
-        assertEquals(2, record.getOrNull(record.size - 1))
-
-        assertFailsWith<IndexOutOfBoundsException> { record[800] }
-        assertFailsWith<IndexOutOfBoundsException> { record[-1] }
-        assertFailsWith<IndexOutOfBoundsException> { record[repeats + 1] }
-        assertNull(record.getOrNull(800))
-        assertNull(record.getOrNull(-1))
-        assertNull(record.getOrNull(repeats + 1))
-        assertNull(record.getOrNull(800) { true })
-        assertNull(record.getOrNull(-1) { true })
-        assertNull(record.getOrNull(repeats + 1) { true })
-    }
-
-    @Test
-    fun testIndexOf() {
-        val record = ArrayTrackRecord<Int>()
-        TEST_VALUES.forEach { record.add(it) }
-        with(record) {
-            assertEquals(9, indexOf(0))
-            assertEquals(9, lastIndexOf(0))
-            assertEquals(1, indexOf(13))
-            assertEquals(7, lastIndexOf(13))
-            assertEquals(3, indexOf(94))
-            assertEquals(11, lastIndexOf(94))
-            assertEquals(-1, indexOf(ABSENT_VALUE))
-            assertEquals(-1, lastIndexOf(ABSENT_VALUE))
-        }
-    }
-
-    @Test
-    fun testContains() {
-        val record = ArrayTrackRecord<Int>()
-        TEST_VALUES.forEach { record.add(it) }
-        TEST_VALUES.forEach { assertTrue(record.contains(it)) }
-        assertFalse(record.contains(ABSENT_VALUE))
-        assertTrue(record.containsAll(TEST_VALUES))
-        assertTrue(record.containsAll(TEST_VALUES.sorted()))
-        assertTrue(record.containsAll(TEST_VALUES.sortedDescending()))
-        assertTrue(record.containsAll(TEST_VALUES.distinct()))
-        assertTrue(record.containsAll(TEST_VALUES.subList(0, TEST_VALUES.size / 2)))
-        assertTrue(record.containsAll(TEST_VALUES.subList(0, TEST_VALUES.size / 2).sorted()))
-        assertTrue(record.containsAll(listOf()))
-        assertFalse(record.containsAll(listOf(ABSENT_VALUE)))
-        assertFalse(record.containsAll(TEST_VALUES + listOf(ABSENT_VALUE)))
-    }
-
-    @Test
-    fun testEmpty() {
-        val record = ArrayTrackRecord<Int>()
-        assertTrue(record.isEmpty())
-        record.add(1)
-        assertFalse(record.isEmpty())
-    }
-
-    @Test
-    fun testIterate() {
-        val record = ArrayTrackRecord<Int>()
-        record.forEach { fail("Expected nothing to iterate") }
-        TEST_VALUES.forEach { record.add(it) }
-        // zip relies on the iterator (this calls extension function Iterable#zip(Iterable))
-        record.zip(TEST_VALUES).forEach { assertEquals(it.first, it.second) }
-        // Also test reverse iteration (to test hasPrevious() and friends)
-        record.reversed().zip(TEST_VALUES.reversed()).forEach { assertEquals(it.first, it.second) }
-    }
-
-    @Test
-    fun testIteratorIsSnapshot() {
-        val record = ArrayTrackRecord<Int>()
-        TEST_VALUES.forEach { record.add(it) }
-        val iterator = record.iterator()
-        val expectedSize = record.size
-        record.add(ABSENT_VALUE)
-        record.add(ABSENT_VALUE)
-        var measuredSize = 0
-        iterator.forEach {
-            ++measuredSize
-            assertNotEquals(ABSENT_VALUE, it)
-        }
-        assertEquals(expectedSize, measuredSize)
-    }
-
-    @Test
-    fun testSublist() {
-        val record = ArrayTrackRecord<Int>()
-        TEST_VALUES.forEach { record.add(it) }
-        assertEquals(record.subList(3, record.size - 3),
-                TEST_VALUES.subList(3, TEST_VALUES.size - 3))
-    }
-
-    fun testPollReturnsImmediately(record: TrackRecord<Int>) {
-        record.add(4)
-        val elapsed = measureTimeMillis { assertEquals(4, record.poll(LONG_TIMEOUT, 0)) }
-        // Should not have waited at all, in fact.
-        assertTrue(elapsed < LONG_TIMEOUT)
-        record.add(7)
-        record.add(9)
-        // Can poll multiple times for the same position, in whatever order
-        assertEquals(9, record.poll(0, 2))
-        assertEquals(7, record.poll(Long.MAX_VALUE, 1))
-        assertEquals(9, record.poll(0, 2))
-        assertEquals(4, record.poll(0, 0))
-        assertEquals(9, record.poll(0, 2) { it > 5 })
-        assertEquals(7, record.poll(0, 0) { it > 5 })
-    }
-
-    @Test
-    fun testPollReturnsImmediately() {
-        testPollReturnsImmediately(ArrayTrackRecord())
-        testPollReturnsImmediately(ArrayTrackRecord<Int>().newReadHead())
-    }
-
-    @Test
-    fun testPollTimesOut() {
-        val record = ArrayTrackRecord<Int>()
-        var delay = measureTimeMillis { assertNull(record.poll(SHORT_TIMEOUT, 0)) }
-        assertTrue(delay >= SHORT_TIMEOUT, "Delay $delay < $SHORT_TIMEOUT")
-        delay = measureTimeMillis { assertNull(record.poll(SHORT_TIMEOUT, 0) { it < 10 }) }
-        assertTrue(delay >= SHORT_TIMEOUT)
-    }
-
-    @Test
-    fun testConcurrentPollDisallowed() {
-        val failures = AtomicInteger(0)
-        val readHead = ArrayTrackRecord<Int>().newReadHead()
-        val barrier = CyclicBarrier(2)
-        Thread {
-            barrier.await(LONG_TIMEOUT, TimeUnit.MILLISECONDS) // barrier 1
-            try {
-                readHead.poll(LONG_TIMEOUT)
-            } catch (e: ConcurrentModificationException) {
-                failures.incrementAndGet()
-                // Unblock the other thread
-                readHead.add(0)
-            }
-        }.start()
-        barrier.await() // barrier 1
-        try {
-            readHead.poll(LONG_TIMEOUT)
-        } catch (e: ConcurrentModificationException) {
-            failures.incrementAndGet()
-            // Unblock the other thread
-            readHead.add(0)
-        }
-        // One of the threads must have gotten an exception.
-        assertEquals(failures.get(), 1)
-    }
-
-    @Test
-    fun testPollWakesUp() {
-        val record = ArrayTrackRecord<Int>()
-        val barrier = CyclicBarrier(2)
-        Thread {
-            barrier.await(LONG_TIMEOUT, TimeUnit.MILLISECONDS) // barrier 1
-            barrier.await() // barrier 2
-            Thread.sleep(SHORT_TIMEOUT * 2)
-            record.add(31)
-        }.start()
-        barrier.await() // barrier 1
-        // Should find the element in more than SHORT_TIMEOUT but less than TEST_TIMEOUT
-        var delay = measureTimeMillis {
-            barrier.await() // barrier 2
-            assertEquals(31, record.poll(TEST_TIMEOUT, 0))
-        }
-        assertTrue(delay in SHORT_TIMEOUT..TEST_TIMEOUT)
-        // Polling for an element already added in anothe thread (pos 0) : should return immediately
-        delay = measureTimeMillis { assertEquals(31, record.poll(TEST_TIMEOUT, 0)) }
-        assertTrue(delay < TEST_TIMEOUT, "Delay $delay > $TEST_TIMEOUT")
-        // Waiting for an element that never comes
-        delay = measureTimeMillis { assertNull(record.poll(SHORT_TIMEOUT, 1)) }
-        assertTrue(delay >= SHORT_TIMEOUT, "Delay $delay < $SHORT_TIMEOUT")
-        // Polling for an element that doesn't match what is already there
-        delay = measureTimeMillis { assertNull(record.poll(SHORT_TIMEOUT, 0) { it < 10 }) }
-        assertTrue(delay >= SHORT_TIMEOUT)
-    }
-
-    // Just make sure the interpreter actually throws an exception when the spec
-    // does not conform to the behavior. The interpreter is just a tool to test a
-    // tool used for a tool for test, let's not have hundreds of tests for it ;
-    // if it's broken one of the tests using it will break.
-    @Test
-    fun testInterpreter() {
-        val interpretLine = __LINE__ + 2
-        try {
-            TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """
-                add(4) | poll(1, 0) = 5
-            """)
-            fail("This spec should have thrown")
-        } catch (e: InterpretException) {
-            assertTrue(e.cause is AssertionError)
-            assertEquals(interpretLine + 1, e.stackTrace[0].lineNumber)
-            assertTrue(e.stackTrace[0].fileName.contains(__FILE__))
-            assertTrue(e.stackTrace[0].methodName.contains("testInterpreter"))
-            assertTrue(e.stackTrace[0].methodName.contains("thread1"))
-        }
-    }
-
-    @Test
-    fun testMultipleAdds() {
-        TRTInterpreter.interpretTestSpec(useReadHeads = false, spec = """
-            add(2)         |                |                |
-                           | add(4)         |                |
-                           |                | add(6)         |
-                           |                |                | add(8)
-            poll(0, 0) = 2 time 0..1 | poll(0, 0) = 2 | poll(0, 0) = 2 | poll(0, 0) = 2
-            poll(0, 1) = 4 time 0..1 | poll(0, 1) = 4 | poll(0, 1) = 4 | poll(0, 1) = 4
-            poll(0, 2) = 6 time 0..1 | poll(0, 2) = 6 | poll(0, 2) = 6 | poll(0, 2) = 6
-            poll(0, 3) = 8 time 0..1 | poll(0, 3) = 8 | poll(0, 3) = 8 | poll(0, 3) = 8
-        """)
-    }
-
-    @Test
-    fun testConcurrentAdds() {
-        TRTInterpreter.interpretTestSpec(useReadHeads = false, spec = """
-            add(2)             | add(4)             | add(6)             | add(8)
-            add(1)             | add(3)             | add(5)             | add(7)
-            poll(0, 1) is even | poll(0, 0) is even | poll(0, 3) is even | poll(0, 2) is even
-            poll(0, 5) is odd  | poll(0, 4) is odd  | poll(0, 7) is odd  | poll(0, 6) is odd
-        """)
-    }
-
-    @Test
-    fun testMultiplePoll() {
-        TRTInterpreter.interpretTestSpec(useReadHeads = false, spec = """
-            add(4)         | poll(1, 0) = 4
-                           | poll(0, 1) = null time 0..1
-                           | poll(1, 1) = null time 1..2
-            sleep; add(7)  | poll(2, 1) = 7 time 1..2
-            sleep; add(18) | poll(2, 2) = 18 time 1..2
-        """)
-    }
-
-    @Test
-    fun testMultiplePollWithPredicate() {
-        TRTInterpreter.interpretTestSpec(useReadHeads = false, spec = """
-                     | poll(1, 0) = null          | poll(1, 0) = null
-            add(6)   | poll(1, 0) = 6             |
-            add(11)  | poll(1, 0) { > 20 } = null | poll(1, 0) { = 11 } = 11
-                     | poll(1, 0) { > 8 } = 11    |
-        """)
-    }
-
-    @Test
-    fun testMultipleReadHeads() {
-        TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """
-                   | poll() = null | poll() = null | poll() = null
-            add(5) |               | poll() = 5    |
-                   | poll() = 5    |               |
-            add(8) | poll() = 8    | poll() = 8    |
-                   |               |               | poll() = 5
-                   |               |               | poll() = 8
-                   |               |               | poll() = null
-                   |               | poll() = null |
-        """)
-    }
-
-    @Test
-    fun testReadHeadPollWithPredicate() {
-        TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """
-            add(5)  | poll() { < 0 } = null
-                    | poll() { > 5 } = null
-            add(10) |
-                    | poll() { = 5 } = null   // The "5" was skipped in the previous line
-            add(15) | poll() { > 8 } = 15     // The "10" was skipped in the previous line
-                    | poll(1, 0) { > 8 } = 10 // 10 is the first element after pos 0 matching > 8
-        """)
-    }
-
-    @Test
-    fun testPollImmediatelyAdvancesReadhead() {
-        TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """
-            add(1)                  | add(2)              | add(3)   | add(4)
-            mark = 0                | poll(0) { > 3 } = 4 |          |
-            poll(0) { > 10 } = null |                     |          |
-            mark = 4                |                     |          |
-            poll() = null           |                     |          |
-        """)
-    }
-
-    @Test
-    fun testParallelReadHeads() {
-        TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """
-            mark = 0   | mark = 0   | mark = 0   | mark = 0
-            add(2)     |            |            |
-                       | add(4)     |            |
-                       |            | add(6)     |
-                       |            |            | add(8)
-            poll() = 2 | poll() = 2 | poll() = 2 | poll() = 2
-            poll() = 4 | poll() = 4 | poll() = 4 | poll() = 4
-            poll() = 6 | poll() = 6 | poll() = 6 | mark = 2
-            poll() = 8 | poll() = 8 | mark = 3   | poll() = 6
-            mark = 4   | mark = 4   | poll() = 8 | poll() = 8
-        """)
-    }
-
-    @Test
-    fun testPeek() {
-        TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """
-            add(2)     |            |               |
-                       | add(4)     |               |
-                       |            | add(6)        |
-                       |            |               | add(8)
-            peek() = 2 | poll() = 2 | poll() = 2    | peek() = 2
-            peek() = 2 | peek() = 4 | poll() = 4    | peek() = 2
-            peek() = 2 | peek() = 4 | peek() = 6    | poll() = 2
-            peek() = 2 | mark = 1   | mark = 2      | poll() = 4
-            mark = 0   | peek() = 4 | peek() = 6    | peek() = 6
-            poll() = 2 | poll() = 4 | poll() = 6    | poll() = 6
-            poll() = 4 | mark = 2   | poll() = 8    | peek() = 8
-            peek() = 6 | peek() = 6 | peek() = null | mark = 3
-        """)
-    }
-}
-
-private object TRTInterpreter : ConcurrentInterpreter<TrackRecord<Int>>(interpretTable) {
-    fun interpretTestSpec(spec: String, useReadHeads: Boolean) = if (useReadHeads) {
-        interpretTestSpec(spec, initial = ArrayTrackRecord(),
-                threadTransform = { (it as ArrayTrackRecord).newReadHead() })
-    } else {
-        interpretTestSpec(spec, ArrayTrackRecord())
-    }
-}
-
-/*
- * Quick ref of supported expressions :
- * sleep(x) : sleeps for x time units and returns Unit ; sleep alone means sleep(1)
- * add(x) : calls and returns TrackRecord#add.
- * poll(time, pos) [{ predicate }] : calls and returns TrackRecord#poll(x time units, pos).
- *   Optionally, a predicate may be specified.
- * poll() [{ predicate }] : calls and returns ReadHead#poll(1 time unit). Optionally, a predicate
- *   may be specified.
- * EXPR = VALUE : asserts that EXPR equals VALUE. EXPR is interpreted. VALUE can either be the
- *   string "null" or an int. Returns Unit.
- * EXPR time x..y : measures the time taken by EXPR and asserts it took at least x and at most
- *   y time units.
- * predicate must be one of "= x", "< x" or "> x".
- */
-private val interpretTable = listOf<InterpretMatcher<TrackRecord<Int>>>(
-    // Interpret "XXX is odd" : run XXX and assert its return value is odd ("even" works too)
-    Regex("(.*)\\s+is\\s+(even|odd)") to { i, t, r ->
-        i.interpret(r.strArg(1), t).also {
-            assertEquals((it as Int) % 2, if ("even" == r.strArg(2)) 0 else 1)
-        }
-    },
-    // Interpret "add(XXX)" as TrackRecord#add(int)
-    Regex("""add\((\d+)\)""") to { i, t, r ->
-        t.add(r.intArg(1))
-    },
-    // Interpret "poll(x, y)" as TrackRecord#poll(timeout = x * INTERPRET_TIME_UNIT, pos = y)
-    // Accepts an optional {} argument for the predicate (see makePredicate for syntax)
-    Regex("""poll\((\d+),\s*(\d+)\)\s*(\{.*\})?""") to { i, t, r ->
-        t.poll(r.timeArg(1), r.intArg(2), makePredicate(r.strArg(3)))
-    },
-    // ReadHead#poll. If this throws in the cast, the code is malformed and has passed "poll()"
-    // in a test that takes a TrackRecord that is not a ReadHead. It's technically possible to get
-    // the test code to not compile instead of throw, but it's vastly more complex and this will
-    // fail 100% at runtime any test that would not have compiled.
-    Regex("""poll\((\d+)?\)\s*(\{.*\})?""") to { i, t, r ->
-        (if (r.strArg(1).isEmpty()) i.interpretTimeUnit else r.timeArg(1)).let { time ->
-            (t as ArrayTrackRecord<Int>.ReadHead).poll(time, makePredicate(r.strArg(2)))
-        }
-    },
-    // ReadHead#mark. The same remarks apply as with ReadHead#poll.
-    Regex("mark") to { i, t, _ -> (t as ArrayTrackRecord<Int>.ReadHead).mark },
-    // ReadHead#peek. The same remarks apply as with ReadHead#poll.
-    Regex("peek\\(\\)") to { i, t, _ -> (t as ArrayTrackRecord<Int>.ReadHead).peek() }
-)
-
-// Parses a { = x } or { < x } or { > x } string and returns the corresponding predicate
-// Returns an always-true predicate for empty and null arguments
-private fun makePredicate(spec: String?): (Int) -> Boolean {
-    if (spec.isNullOrEmpty()) return { true }
-    val match = Regex("""\{\s*([<>=])\s*(\d+)\s*\}""").matchEntire(spec)
-            ?: throw SyntaxException("Predicate \"${spec}\"")
-    val arg = match.intArg(2)
-    return when (match.strArg(1)) {
-        ">" -> { i -> i > arg }
-        "<" -> { i -> i < arg }
-        "=" -> { i -> i == arg }
-        else -> throw RuntimeException("How did \"${spec}\" match this regexp ?")
-    }
-}
diff --git a/tests/unit/src/com/android/networkstack/metrics/NetworkIpReachabilityMonitorMetricsTest.java b/tests/unit/src/com/android/networkstack/metrics/NetworkIpReachabilityMonitorMetricsTest.java
new file mode 100644
index 0000000..45592aa
--- /dev/null
+++ b/tests/unit/src/com/android/networkstack/metrics/NetworkIpReachabilityMonitorMetricsTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.metrics;
+
+import android.stats.connectivity.IpType;
+import android.stats.connectivity.NudEventType;
+import android.stats.connectivity.NudNeighborType;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for IpReachabilityMonitorMetrics.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkIpReachabilityMonitorMetricsTest {
+    @Test
+    public void testIpReachabilityMonitorMetrics_setIpType() throws Exception {
+        NetworkIpReachabilityMonitorReported mStats;
+        final IpReachabilityMonitorMetrics mMetrics = new IpReachabilityMonitorMetrics();
+
+        for (IpType ip : IpType.values()) {
+            mMetrics.setNudIpType(ip);
+            mStats = mMetrics.statsWrite();
+            assertEquals(ip, mStats.getIpType());
+        }
+    }
+
+    @Test
+    public void testIpReachabilityMonitorMetrics_setNeighborType() throws Exception {
+        NetworkIpReachabilityMonitorReported mStats;
+        final IpReachabilityMonitorMetrics mMetrics = new IpReachabilityMonitorMetrics();
+
+        for (NudNeighborType neighborType : NudNeighborType.values()) {
+            mMetrics.setNudNeighborType(neighborType);
+            mStats = mMetrics.statsWrite();
+            assertEquals(neighborType, mStats.getNeighborType());
+        }
+    }
+
+    @Test
+    public void testIpReachabilityMonitorMetrics_setEventType() {
+        NetworkIpReachabilityMonitorReported mStats;
+        final IpReachabilityMonitorMetrics mMetrics = new IpReachabilityMonitorMetrics();
+
+        for (NudEventType type : NudEventType.values()) {
+            mMetrics.setNudEventType(type);
+            mStats = mMetrics.statsWrite();
+            assertEquals(type, mStats.getEventType());
+        }
+    }
+}
diff --git a/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java b/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java
index 8fbe0c4..198ac99 100644
--- a/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java
+++ b/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java
@@ -16,7 +16,6 @@
 
 package com.android.networkstack.netlink;
 
-import static android.net.netlink.NetlinkConstants.SOCKDIAG_MSG_HEADER_SIZE;
 import static android.net.util.DataStallUtils.CONFIG_TCP_PACKETS_FAIL_PERCENTAGE;
 import static android.net.util.DataStallUtils.DEFAULT_TCP_PACKETS_FAIL_PERCENTAGE;
 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
@@ -26,6 +25,7 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.net.module.util.netlink.NetlinkConstants.SOCKDIAG_MSG_HEADER_SIZE;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -43,7 +43,6 @@
 import android.net.INetd;
 import android.net.MarkMaskParcel;
 import android.net.Network;
-import android.net.netlink.StructNlMsgHdr;
 import android.os.Build;
 import android.util.Log;
 import android.util.Log.TerribleFailureHandler;
@@ -51,6 +50,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.net.module.util.netlink.StructNlMsgHdr;
 import com.android.networkstack.apishim.ConstantsShim;
 import com.android.networkstack.apishim.NetworkShimImpl;
 import com.android.testutils.DevSdkIgnoreRule;
diff --git a/tests/unit/src/com/android/networkstack/packets/NeighborAdvertisementTest.java b/tests/unit/src/com/android/networkstack/packets/NeighborAdvertisementTest.java
new file mode 100644
index 0000000..c689d7b
--- /dev/null
+++ b/tests/unit/src/com/android/networkstack/packets/NeighborAdvertisementTest.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.packets;
+
+import static android.system.OsConstants.ETH_P_IPV6;
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.net.InetAddresses;
+import android.net.MacAddress;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class NeighborAdvertisementTest {
+    private static final Inet6Address TEST_SRC_ADDR =
+            (Inet6Address) InetAddresses.parseNumericAddress("fe80::dfd9:50a0:cc7b:7d6d");
+    private static final Inet6Address TEST_TARGET_ADDR =
+            (Inet6Address) InetAddresses.parseNumericAddress("2001:db8:1:0:c928:250d:b90c:3178");
+    private static final byte[] TEST_SOURCE_MAC_ADDR = new byte[] {
+            (byte) 0xea, (byte) 0xbe, (byte) 0x11, (byte) 0x25, (byte) 0xc1, (byte) 0x25
+    };
+    private static final byte[] TEST_DST_MAC_ADDR = new byte[] {
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+    };
+    private static final byte[] TEST_GRATUITOUS_NA = new byte[] {
+        // dst mac address
+        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+        // src mac address
+        (byte) 0xea, (byte) 0xbe, (byte) 0x11, (byte) 0x25, (byte) 0xc1, (byte) 0x25,
+        // ether type
+        (byte) 0x86, (byte) 0xdd,
+        // version, priority and flow label
+        (byte) 0x60, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // length
+        (byte) 0x00, (byte) 0x20,
+        // next header
+        (byte) 0x3a,
+        // hop limit
+        (byte) 0xff,
+        // source address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0xdf, (byte) 0xd9, (byte) 0x50, (byte) 0xa0,
+        (byte) 0xcc, (byte) 0x7b, (byte) 0x7d, (byte) 0x6d,
+        // destination address
+        (byte) 0xff, (byte) 0x02, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+        // ICMP type, code, checksum
+        (byte) 0x88, (byte) 0x00, (byte) 0x3a, (byte) 0x3c,
+        // flags
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // target address
+        (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+        (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00,
+        (byte) 0xc9, (byte) 0x28, (byte) 0x25, (byte) 0x0d,
+        (byte) 0xb9, (byte) 0x0c, (byte) 0x31, (byte) 0x78,
+        // TLLA option
+        (byte) 0x02, (byte) 0x01,
+        // Link-Layer address
+        (byte) 0xea, (byte) 0xbe, (byte) 0x11, (byte) 0x25, (byte) 0xc1, (byte) 0x25,
+    };
+    private static final byte[] TEST_GRATUITOUS_NA_WITHOUT_TLLA = new byte[] {
+        // dst mac address
+        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+        // src mac address
+        (byte) 0xea, (byte) 0xbe, (byte) 0x11, (byte) 0x25, (byte) 0xc1, (byte) 0x25,
+        // ether type
+        (byte) 0x86, (byte) 0xdd,
+        // version, priority and flow label
+        (byte) 0x60, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // length
+        (byte) 0x00, (byte) 0x20,
+        // next header
+        (byte) 0x3a,
+        // hop limit
+        (byte) 0xff,
+        // source address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0xdf, (byte) 0xd9, (byte) 0x50, (byte) 0xa0,
+        (byte) 0xcc, (byte) 0x7b, (byte) 0x7d, (byte) 0x6d,
+        // destination address
+        (byte) 0xff, (byte) 0x02, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+        // ICMP type, code, checksum
+        (byte) 0x88, (byte) 0x00, (byte) 0x3a, (byte) 0x3c,
+        // flags
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // target address
+        (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+        (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00,
+        (byte) 0xc9, (byte) 0x28, (byte) 0x25, (byte) 0x0d,
+        (byte) 0xb9, (byte) 0x0c, (byte) 0x31, (byte) 0x78,
+    };
+    private static final byte[] TEST_GRATUITOUS_NA_LESS_LENGTH = new byte[] {
+        // dst mac address
+        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+        // src mac address
+        (byte) 0xea, (byte) 0xbe, (byte) 0x11, (byte) 0x25, (byte) 0xc1, (byte) 0x25,
+        // ether type
+        (byte) 0x86, (byte) 0xdd,
+        // version, priority and flow label
+        (byte) 0x60, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // length
+        (byte) 0x00, (byte) 0x20,
+        // next header
+        (byte) 0x3a,
+        // hop limit
+        (byte) 0xff,
+        // source address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0xdf, (byte) 0xd9, (byte) 0x50, (byte) 0xa0,
+        (byte) 0xcc, (byte) 0x7b, (byte) 0x7d, (byte) 0x6d,
+        // destination address
+        (byte) 0xff, (byte) 0x02, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+        // ICMP type, code, checksum
+        (byte) 0x88, (byte) 0x00, (byte) 0x3a, (byte) 0x3c,
+        // flags
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+    };
+    private static final byte[] TEST_GRATUITOUS_NA_TRUNCATED = new byte[] {
+        // dst mac address
+        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+        // src mac address
+        (byte) 0xea, (byte) 0xbe, (byte) 0x11, (byte) 0x25, (byte) 0xc1, (byte) 0x25,
+        // ether type
+        (byte) 0x86, (byte) 0xdd,
+        // version, priority and flow label
+        (byte) 0x60, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // length
+        (byte) 0x00, (byte) 0x20,
+        // next header
+        (byte) 0x3a,
+        // hop limit
+        (byte) 0xff,
+        // source address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0xdf, (byte) 0xd9, (byte) 0x50, (byte) 0xa0,
+        (byte) 0xcc, (byte) 0x7b, (byte) 0x7d, (byte) 0x6d,
+        // destination address
+        (byte) 0xff, (byte) 0x02, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+        // ICMP type, code, checksum
+        (byte) 0x88, (byte) 0x00, (byte) 0x3a, (byte) 0x3c,
+        // flags
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // target address
+        (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+        (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00,
+        (byte) 0xc9, (byte) 0x28, (byte) 0x25, (byte) 0x0d,
+        (byte) 0xb9, (byte) 0x0c, (byte) 0x31, (byte) 0x78,
+        // TLLA option
+        (byte) 0x02, (byte) 0x01,
+        // truncatd Link-Layer address: 4bytes
+        (byte) 0xea, (byte) 0xbe, (byte) 0x11, (byte) 0x25,
+    };
+
+    @Test
+    public void testGratuitousNa_build() throws Exception {
+        final ByteBuffer na = NeighborAdvertisement.build(
+                MacAddress.fromBytes(TEST_SOURCE_MAC_ADDR),
+                MacAddress.fromBytes(TEST_DST_MAC_ADDR),
+                TEST_SRC_ADDR, IPV6_ADDR_ALL_ROUTERS_MULTICAST, 0 /* flags */, TEST_TARGET_ADDR);
+        assertArrayEquals(na.array(), TEST_GRATUITOUS_NA);
+    }
+
+    private void assertNeighborAdvertisement(final NeighborAdvertisement na,
+            boolean hasTllaOption) {
+        assertArrayEquals(TEST_SOURCE_MAC_ADDR, na.ethHdr.srcMac.toByteArray());
+        assertArrayEquals(TEST_DST_MAC_ADDR, na.ethHdr.dstMac.toByteArray());
+        assertEquals(ETH_P_IPV6, na.ethHdr.etherType);
+        assertEquals(IPPROTO_ICMPV6, na.ipv6Hdr.nextHeader);
+        assertEquals(0xff, na.ipv6Hdr.hopLimit);
+        assertEquals(IPV6_ADDR_ALL_ROUTERS_MULTICAST, na.ipv6Hdr.dstIp);
+        assertEquals(TEST_SRC_ADDR, na.ipv6Hdr.srcIp);
+        assertEquals(ICMPV6_NEIGHBOR_ADVERTISEMENT, na.icmpv6Hdr.type);
+        assertEquals(0, na.icmpv6Hdr.code);
+        assertEquals(0, na.naHdr.flags);
+        assertEquals(TEST_TARGET_ADDR, na.naHdr.target);
+        if (hasTllaOption) {
+            assertEquals(ICMPV6_ND_OPTION_TLLA, na.tlla.type);
+            assertEquals(1, na.tlla.length);
+            assertArrayEquals(TEST_SOURCE_MAC_ADDR, na.tlla.linkLayerAddress.toByteArray());
+        }
+    }
+
+    @Test
+    public void testGratuitousNa_parse() throws Exception {
+        final NeighborAdvertisement na = NeighborAdvertisement.parse(TEST_GRATUITOUS_NA,
+                TEST_GRATUITOUS_NA.length);
+
+        assertNeighborAdvertisement(na, true /* hasTllaOption */);
+        assertArrayEquals(TEST_GRATUITOUS_NA, na.toByteBuffer().array());
+    }
+
+    @Test
+    public void testGratuitousNa_parseWithoutTllaOption() throws Exception {
+        final NeighborAdvertisement na =
+                NeighborAdvertisement.parse(TEST_GRATUITOUS_NA_WITHOUT_TLLA,
+                        TEST_GRATUITOUS_NA_WITHOUT_TLLA.length);
+
+        assertNeighborAdvertisement(na, false /* hasTllaOption */);
+        assertArrayEquals(TEST_GRATUITOUS_NA_WITHOUT_TLLA, na.toByteBuffer().array());
+    }
+
+    @Test
+    public void testGratuitousNa_zeroPacketLength() throws Exception {
+        assertThrows(NeighborAdvertisement.ParseException.class,
+                () -> NeighborAdvertisement.parse(TEST_GRATUITOUS_NA, 0));
+    }
+
+    @Test
+    public void testGratuitousNa_invalidByteBufferLength() throws Exception {
+        assertThrows(NeighborAdvertisement.ParseException.class,
+                () -> NeighborAdvertisement.parse(TEST_GRATUITOUS_NA_TRUNCATED,
+                                                  TEST_GRATUITOUS_NA.length));
+    }
+
+    @Test
+    public void testGratuitousNa_lessPacketLength() throws Exception {
+        assertThrows(NeighborAdvertisement.ParseException.class,
+                () -> NeighborAdvertisement.parse(TEST_GRATUITOUS_NA_LESS_LENGTH,
+                                                  TEST_GRATUITOUS_NA_LESS_LENGTH.length));
+    }
+
+    @Test
+    public void testGratuitousNa_truncatedPacket() throws Exception {
+        assertThrows(IllegalArgumentException.class,
+                () -> NeighborAdvertisement.parse(TEST_GRATUITOUS_NA_TRUNCATED,
+                                                  TEST_GRATUITOUS_NA_TRUNCATED.length));
+    }
+}
diff --git a/tests/unit/src/com/android/networkstack/packets/NeighborSolicitationTest.java b/tests/unit/src/com/android/networkstack/packets/NeighborSolicitationTest.java
new file mode 100644
index 0000000..18a3ef3
--- /dev/null
+++ b/tests/unit/src/com/android/networkstack/packets/NeighborSolicitationTest.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.packets;
+
+import static android.system.OsConstants.ETH_P_IPV6;
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.net.InetAddresses;
+import android.net.MacAddress;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class NeighborSolicitationTest {
+    private static final Inet6Address TEST_SRC_ADDR =
+            (Inet6Address) InetAddresses.parseNumericAddress("fe80::d419:d664:df38:2f65");
+    private static final Inet6Address TEST_DST_ADDR =
+            (Inet6Address) InetAddresses.parseNumericAddress("fe80::200:1a:1122:3344");
+    private static final Inet6Address TEST_TARGET_ADDR =
+            (Inet6Address) InetAddresses.parseNumericAddress("fe80::200:1a:1122:3344");
+    private static final byte[] TEST_SOURCE_MAC_ADDR = new byte[] {
+            (byte) 0x06, (byte) 0x5a, (byte) 0xac, (byte) 0x02, (byte) 0x61, (byte) 0x11,
+    };
+    private static final byte[] TEST_DST_MAC_ADDR = new byte[] {
+            (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+    };
+    private static final byte[] TEST_NEIGHBOR_SOLICITATION = new byte[] {
+        // dst mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+        // src mac address
+        (byte) 0x06, (byte) 0x5a, (byte) 0xac, (byte) 0x02, (byte) 0x61, (byte) 0x11,
+        // ether type
+        (byte) 0x86, (byte) 0xdd,
+        // version, priority and flow label
+        (byte) 0x60, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // length
+        (byte) 0x00, (byte) 0x20,
+        // next header
+        (byte) 0x3a,
+        // hop limit
+        (byte) 0xff,
+        // source address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0xd4, (byte) 0x19, (byte) 0xd6, (byte) 0x64,
+        (byte) 0xdf, (byte) 0x38, (byte) 0x2f, (byte) 0x65,
+        // destination address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x1a,
+        (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+        // ICMP type, code, checksum
+        (byte) 0x87, (byte) 0x00, (byte) 0x22, (byte) 0x96,
+        // reserved
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // target address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x1a,
+        (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+        // slla option
+        (byte) 0x01, (byte) 0x01,
+        // link-layer address
+        (byte) 0x06, (byte) 0x5a, (byte) 0xac, (byte) 0x02,
+        (byte) 0x61, (byte) 0x11,
+    };
+    private static final byte[] TEST_NEIGHBOR_SOLICITATION_WITHOUT_SLLA = new byte[] {
+        // dst mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+        // src mac address
+        (byte) 0x06, (byte) 0x5a, (byte) 0xac, (byte) 0x02, (byte) 0x61, (byte) 0x11,
+        // ether type
+        (byte) 0x86, (byte) 0xdd,
+        // version, priority and flow label
+        (byte) 0x60, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // length
+        (byte) 0x00, (byte) 0x20,
+        // next header
+        (byte) 0x3a,
+        // hop limit
+        (byte) 0xff,
+        // source address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0xd4, (byte) 0x19, (byte) 0xd6, (byte) 0x64,
+        (byte) 0xdf, (byte) 0x38, (byte) 0x2f, (byte) 0x65,
+        // destination address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x1a,
+        (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+        // ICMP type, code, checksum
+        (byte) 0x87, (byte) 0x00, (byte) 0x22, (byte) 0x96,
+        // reserved
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // target address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x1a,
+        (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+    };
+    private static final byte[] TEST_NEIGHBOR_SOLICITATION_LESS_LENGTH = new byte[] {
+        // dst mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+        // src mac address
+        (byte) 0x06, (byte) 0x5a, (byte) 0xac, (byte) 0x02, (byte) 0x61, (byte) 0x11,
+        // ether type
+        (byte) 0x86, (byte) 0xdd,
+        // version, priority and flow label
+        (byte) 0x60, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // length
+        (byte) 0x00, (byte) 0x20,
+        // next header
+        (byte) 0x3a,
+        // hop limit
+        (byte) 0xff,
+        // source address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0xd4, (byte) 0x19, (byte) 0xd6, (byte) 0x64,
+        (byte) 0xdf, (byte) 0x38, (byte) 0x2f, (byte) 0x65,
+        // destination address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x1a,
+        (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+        // ICMP type, code, checksum
+        (byte) 0x87, (byte) 0x00, (byte) 0x22, (byte) 0x96,
+        // reserved
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+    };
+    private static final byte[] TEST_NEIGHBOR_SOLICITATION_TRUNCATED = new byte[] {
+        // dst mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+        // src mac address
+        (byte) 0x06, (byte) 0x5a, (byte) 0xac, (byte) 0x02, (byte) 0x61, (byte) 0x11,
+        // ether type
+        (byte) 0x86, (byte) 0xdd,
+        // version, priority and flow label
+        (byte) 0x60, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // length
+        (byte) 0x00, (byte) 0x20,
+        // next header
+        (byte) 0x3a,
+        // hop limit
+        (byte) 0xff,
+        // source address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0xd4, (byte) 0x19, (byte) 0xd6, (byte) 0x64,
+        (byte) 0xdf, (byte) 0x38, (byte) 0x2f, (byte) 0x65,
+        // destination address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x1a,
+        (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+        // ICMP type, code, checksum
+        (byte) 0x87, (byte) 0x00, (byte) 0x22, (byte) 0x96,
+        // reserved
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // target address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x1a,
+        (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+        // slla option
+        (byte) 0x01, (byte) 0x01,
+        // truncatd link-layer address: 4bytes
+        (byte) 0x06, (byte) 0x5a, (byte) 0xac, (byte) 0x02,
+    };
+
+    @Test
+    public void testNeighborSolicitation_build() throws Exception {
+        final ByteBuffer ns = NeighborSolicitation.build(
+                MacAddress.fromBytes(TEST_SOURCE_MAC_ADDR),
+                MacAddress.fromBytes(TEST_DST_MAC_ADDR),
+                TEST_SRC_ADDR, TEST_DST_ADDR, TEST_TARGET_ADDR);
+        assertArrayEquals(ns.array(), TEST_NEIGHBOR_SOLICITATION);
+    }
+
+    private void assertNeighborSolicitation(final NeighborSolicitation ns, boolean hasSllaOption) {
+        assertArrayEquals(TEST_SOURCE_MAC_ADDR, ns.ethHdr.srcMac.toByteArray());
+        assertArrayEquals(TEST_DST_MAC_ADDR, ns.ethHdr.dstMac.toByteArray());
+        assertEquals(ETH_P_IPV6, ns.ethHdr.etherType);
+        assertEquals(IPPROTO_ICMPV6, ns.ipv6Hdr.nextHeader);
+        assertEquals(0xff, ns.ipv6Hdr.hopLimit);
+        assertEquals(TEST_DST_ADDR, ns.ipv6Hdr.dstIp);
+        assertEquals(TEST_SRC_ADDR, ns.ipv6Hdr.srcIp);
+        assertEquals(ICMPV6_NEIGHBOR_SOLICITATION, ns.icmpv6Hdr.type);
+        assertEquals(0, ns.icmpv6Hdr.code);
+        assertEquals(TEST_TARGET_ADDR, ns.nsHdr.target);
+        if (hasSllaOption) {
+            assertEquals(ICMPV6_ND_OPTION_SLLA, ns.slla.type);
+            assertEquals(1, ns.slla.length);
+            assertEquals(MacAddress.fromBytes(TEST_SOURCE_MAC_ADDR), ns.slla.linkLayerAddress);
+        }
+    }
+
+    @Test
+    public void testNeighborSolicitation_parse() throws Exception {
+        final NeighborSolicitation ns = NeighborSolicitation.parse(TEST_NEIGHBOR_SOLICITATION,
+                TEST_NEIGHBOR_SOLICITATION.length);
+
+        assertNeighborSolicitation(ns, true /* hasSllaOption */);
+        assertArrayEquals(TEST_NEIGHBOR_SOLICITATION, ns.toByteBuffer().array());
+    }
+
+    @Test
+    public void testNeighborSolicitation_parseWithoutSllaOption() throws Exception {
+        final NeighborSolicitation ns =
+                NeighborSolicitation.parse(TEST_NEIGHBOR_SOLICITATION_WITHOUT_SLLA,
+                        TEST_NEIGHBOR_SOLICITATION_WITHOUT_SLLA.length);
+
+        assertNeighborSolicitation(ns, false /* hasSllaOption */);
+        assertArrayEquals(TEST_NEIGHBOR_SOLICITATION_WITHOUT_SLLA, ns.toByteBuffer().array());
+    }
+
+    @Test
+    public void testNeighborSolicitation_invalidPacketLength() throws Exception {
+        assertThrows(NeighborSolicitation.ParseException.class,
+                () -> NeighborSolicitation.parse(TEST_NEIGHBOR_SOLICITATION, 0));
+    }
+
+    @Test
+    public void testNeighborSolicitation_invalidByteBufferLength() throws Exception {
+        assertThrows(NeighborSolicitation.ParseException.class,
+                () -> NeighborSolicitation.parse(TEST_NEIGHBOR_SOLICITATION_TRUNCATED,
+                                                  TEST_NEIGHBOR_SOLICITATION.length));
+    }
+
+    @Test
+    public void testNeighborSolicitation_lessPacketLength() throws Exception {
+        assertThrows(NeighborSolicitation.ParseException.class,
+                () -> NeighborSolicitation.parse(TEST_NEIGHBOR_SOLICITATION_LESS_LENGTH,
+                                                  TEST_NEIGHBOR_SOLICITATION_LESS_LENGTH.length));
+    }
+
+    @Test
+    public void testNeighborSolicitation_truncatedPacket() throws Exception {
+        assertThrows(IllegalArgumentException.class,
+                () -> NeighborSolicitation.parse(TEST_NEIGHBOR_SOLICITATION_TRUNCATED,
+                                                  TEST_NEIGHBOR_SOLICITATION_TRUNCATED.length));
+    }
+}
diff --git a/tests/unit/src/com/android/server/NetworkStackServiceTest.kt b/tests/unit/src/com/android/server/NetworkStackServiceTest.kt
index b32a419..cf65cd7 100644
--- a/tests/unit/src/com/android/server/NetworkStackServiceTest.kt
+++ b/tests/unit/src/com/android/server/NetworkStackServiceTest.kt
@@ -43,7 +43,6 @@
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
-import com.android.testutils.ExceptionUtils
 import com.android.testutils.assertThrows
 import org.junit.Rule
 import org.junit.Test
@@ -207,10 +206,10 @@
         verify(mockDhcpCb, times(2)).onDhcpServerCreated(eq(IDhcpServer.STATUS_SUCCESS), any())
 
         // allowTestUid does not need to record the caller's version
-        assertThrows(SecurityException::class.java, ExceptionUtils.ThrowingRunnable {
+        assertThrows(SecurityException::class.java) {
             // Should throw because the test does not run as root
             connector.allowTestUid(Process.myUid(), null)
-        })
+        }
 
         // Verify all methods were covered by the test (5 methods + getVersion + getHash)
         assertEquals(7, INetworkStackConnector::class.declaredMemberFunctions.count {
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
index 8f42a61..ff43325 100644
--- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -16,7 +16,9 @@
 
 package com.android.server.connectivity;
 
+import static android.content.Intent.ACTION_CONFIGURATION_CHANGED;
 import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
+import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS;
 import static android.net.DnsResolver.TYPE_A;
 import static android.net.DnsResolver.TYPE_AAAA;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
@@ -25,9 +27,14 @@
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_SKIPPED;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
@@ -91,6 +98,7 @@
 import static java.util.stream.Collectors.toList;
 
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -142,7 +150,11 @@
 import com.android.networkstack.R;
 import com.android.networkstack.apishim.CaptivePortalDataShimImpl;
 import com.android.networkstack.apishim.ConstantsShim;
+import com.android.networkstack.apishim.NetworkInformationShimImpl;
+import com.android.networkstack.apishim.common.CaptivePortalDataShim;
+import com.android.networkstack.apishim.common.NetworkInformationShim;
 import com.android.networkstack.apishim.common.ShimUtils;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
 import com.android.networkstack.metrics.DataStallDetectionStats;
 import com.android.networkstack.metrics.DataStallStatsUtils;
 import com.android.networkstack.netlink.TcpSocketTracker;
@@ -151,6 +163,7 @@
 import com.android.server.connectivity.nano.DnsEvent;
 import com.android.server.connectivity.nano.WifiData;
 import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.HandlerUtils;
 
@@ -197,6 +210,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@SuppressLint("NewApi")  // Uses hidden APIs, which the linter would identify as missing APIs.
 public class NetworkMonitorTest {
     private static final String LOCATION_HEADER = "location";
     private static final String CONTENT_TYPE_HEADER = "Content-Type";
@@ -261,6 +275,7 @@
     private static final String TEST_SPEED_TEST_URL = "https://speedtest.example.com";
     private static final String TEST_RELATIVE_URL = "/test/relative/gen_204";
     private static final String TEST_MCCMNC = "123456";
+    private static final String TEST_FRIENDLY_NAME = "Friendly Name";
     private static final String[] TEST_HTTP_URLS = {TEST_HTTP_OTHER_URL1, TEST_HTTP_OTHER_URL2};
     private static final String[] TEST_HTTPS_URLS = {TEST_HTTPS_OTHER_URL1, TEST_HTTPS_OTHER_URL2};
     private static final int TEST_TCP_FAIL_RATE = 99;
@@ -278,7 +293,9 @@
     private static final int DEFAULT_DNS_TIMEOUT_THRESHOLD = 5;
 
     private static final int HANDLER_TIMEOUT_MS = 1000;
-
+    private static final int TEST_MIN_STALL_EVALUATE_INTERVAL_MS = 500;
+    private static final int STALL_EXPECTED_LAST_PROBE_TIME_MS =
+            TEST_MIN_STALL_EVALUATE_INTERVAL_MS + HANDLER_TIMEOUT_MS;
     private static final LinkProperties TEST_LINK_PROPERTIES = new LinkProperties();
 
     // Cannot have a static member for the LinkProperties with captive portal API information, as
@@ -308,6 +325,14 @@
     private static final NetworkCapabilities CELL_NO_INTERNET_CAPABILITIES =
             new NetworkCapabilities().addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
 
+    private static final NetworkCapabilities WIFI_OEM_PAID_CAPABILITIES =
+            new NetworkCapabilities()
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .addCapability(NET_CAPABILITY_NOT_METERED)
+                .addCapability(NET_CAPABILITY_OEM_PAID)
+                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+
     /**
      * Fakes DNS responses.
      *
@@ -538,7 +563,7 @@
 
         resetCallbacks();
 
-        setMinDataStallEvaluateInterval(500);
+        setMinDataStallEvaluateInterval(TEST_MIN_STALL_EVALUATE_INTERVAL_MS);
         setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS);
         setValidDataStallDnsTimeThreshold(500);
         setConsecutiveDnsTimeoutThreshold(5);
@@ -565,7 +590,7 @@
     }
 
     private void resetCallbacks() {
-        resetCallbacks(6);
+        resetCallbacks(11);
     }
 
     private void resetCallbacks(int interfaceVersion) {
@@ -613,6 +638,7 @@
 
         @Override
         protected void onQuitting() {
+            super.onQuitting();
             assertTrue(mCreatedNetworkMonitors.remove(this));
             mQuitCv.open();
         }
@@ -839,7 +865,6 @@
 
     @Test
     public void testGetHttpProbeUrl() {
-        final WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
         // If config_captive_portal_http_url is set and the global setting is set, the config is
         // used.
         doReturn(TEST_HTTP_URL).when(mResources).getString(R.string.config_captive_portal_http_url);
@@ -847,16 +872,21 @@
                 R.string.default_captive_portal_http_url);
         when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTP_URL), any()))
                 .thenReturn(TEST_HTTP_OTHER_URL1);
-        assertEquals(TEST_HTTP_URL, wnm.getCaptivePortalServerHttpUrl());
+        final WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
+        assertEquals(TEST_HTTP_URL, wnm.getCaptivePortalServerHttpUrl(mContext));
         // If config_captive_portal_http_url is unset and the global setting is set, the global
         // setting is used.
         doReturn(null).when(mResources).getString(R.string.config_captive_portal_http_url);
-        assertEquals(TEST_HTTP_OTHER_URL1, wnm.getCaptivePortalServerHttpUrl());
+        assertEquals(TEST_HTTP_OTHER_URL1, wnm.getCaptivePortalServerHttpUrl(mContext));
         // If both config_captive_portal_http_url and global setting are unset,
-        // default_captive_portal_http_url is used.
+        // default_captive_portal_http_url is used. But the global setting will only be read in the
+        // constructor.
         when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTP_URL), any()))
                 .thenReturn(null);
-        assertEquals(TEST_HTTP_OTHER_URL2, wnm.getCaptivePortalServerHttpUrl());
+        assertEquals(TEST_HTTP_OTHER_URL1, wnm.getCaptivePortalServerHttpUrl(mContext));
+        // default_captive_portal_http_url is used when the configuration is applied in new NM.
+        final WrappedNetworkMonitor wnm2 = makeCellNotMeteredNetworkMonitor();
+        assertEquals(TEST_HTTP_OTHER_URL2, wnm2.getCaptivePortalServerHttpUrl(mContext));
     }
 
     @Test
@@ -913,6 +943,57 @@
         }
     }
 
+    @Test
+    public void testConfigurationChange_BeforeNMConnected() throws Exception {
+        final WrappedNetworkMonitor nm = new WrappedNetworkMonitor();
+        final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+        // Verify configuration change receiver is registered after start().
+        verify(mContext, never()).registerReceiver(receiverCaptor.capture(),
+                argThat(receiver -> ACTION_CONFIGURATION_CHANGED.equals(receiver.getAction(0))));
+        nm.start();
+        mCreatedNetworkMonitors.add(nm);
+        HandlerUtils.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
+        verify(mContext, times(1)).registerReceiver(receiverCaptor.capture(),
+                argThat(receiver -> ACTION_CONFIGURATION_CHANGED.equals(receiver.getAction(0))));
+        // Update a new URL and send a configuration change
+        doReturn(TEST_HTTPS_OTHER_URL1).when(mResources).getString(
+                R.string.config_captive_portal_https_url);
+        receiverCaptor.getValue().onReceive(mContext, new Intent(ACTION_CONFIGURATION_CHANGED));
+        HandlerUtils.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
+        // Should stay in default state before receiving CMD_NETWORK_CONNECTED
+        verify(mOtherHttpsConnection1, never()).getResponseCode();
+    }
+
+    @Test
+    public void testIsCaptivePortal_ConfigurationChange_RenewUrls() throws Exception {
+        setStatus(mHttpsConnection, 204);
+        final NetworkMonitor nm = runValidatedNetworkTest();
+        final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mContext, times(1)).registerReceiver(receiverCaptor.capture(),
+                argThat(receiver -> ACTION_CONFIGURATION_CHANGED.equals(receiver.getAction(0))));
+
+        resetCallbacks();
+        // New URLs with partial connectivity
+        doReturn(TEST_HTTPS_OTHER_URL1).when(mResources).getString(
+                R.string.config_captive_portal_https_url);
+        doReturn(TEST_HTTP_OTHER_URL1).when(mResources).getString(
+                R.string.config_captive_portal_http_url);
+        setStatus(mOtherHttpsConnection1, 500);
+        setStatus(mOtherHttpConnection1, 204);
+
+        // Receive configuration. Expect a reevaluation triggered.
+        receiverCaptor.getValue().onReceive(mContext, new Intent(ACTION_CONFIGURATION_CHANGED));
+
+        HandlerUtils.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
+        verifyNetworkTested(NETWORK_VALIDATION_RESULT_PARTIAL,
+                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP);
+        verify(mOtherHttpsConnection1, times(1)).getResponseCode();
+        verify(mOtherHttpConnection1, times(1)).getResponseCode();
+    }
+
     private CellInfoGsm makeTestCellInfoGsm(String mcc) throws Exception {
         final CellInfoGsm info = new CellInfoGsm();
         final CellIdentityGsm ci = makeCellIdentityGsm(0, 0, 0, 0, mcc, "01", "", "");
@@ -943,7 +1024,7 @@
     public void testMakeFallbackUrls() throws Exception {
         final WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
         // Value exist in setting provider.
-        URL[] urls = wnm.makeCaptivePortalFallbackUrls();
+        URL[] urls = wnm.makeCaptivePortalFallbackUrls(mContext);
         assertEquals(urls[0].toString(), TEST_FALLBACK_URL);
 
         // Clear setting provider value. Verify it to get configuration from resource instead.
@@ -951,13 +1032,13 @@
         // Verify that getting resource with exception.
         when(mResources.getStringArray(R.array.config_captive_portal_fallback_urls))
                 .thenThrow(Resources.NotFoundException.class);
-        urls = wnm.makeCaptivePortalFallbackUrls();
+        urls = wnm.makeCaptivePortalFallbackUrls(mContext);
         assertEquals(urls.length, 0);
 
         // Verify resource return 2 different URLs.
         doReturn(new String[] {"http://testUrl1.com", "http://testUrl2.com"}).when(mResources)
                 .getStringArray(R.array.config_captive_portal_fallback_urls);
-        urls = wnm.makeCaptivePortalFallbackUrls();
+        urls = wnm.makeCaptivePortalFallbackUrls(mContext);
         assertEquals(urls.length, 2);
         assertEquals("http://testUrl1.com", urls[0].toString());
         assertEquals("http://testUrl2.com", urls[1].toString());
@@ -968,7 +1049,7 @@
         setupNoSimCardNeighborMcc();
         doReturn(new String[] {"http://testUrl3.com"}).when(mMccResource)
                 .getStringArray(R.array.config_captive_portal_fallback_urls);
-        urls = wnm.makeCaptivePortalFallbackUrls();
+        urls = wnm.makeCaptivePortalFallbackUrls(mContext);
         assertEquals(urls.length, 2);
         assertEquals("http://testUrl1.com", urls[0].toString());
         assertEquals("http://testUrl2.com", urls[1].toString());
@@ -981,7 +1062,7 @@
         doReturn(new String[] {"http://testUrl.com"}).when(mMccResource)
                 .getStringArray(R.array.config_captive_portal_fallback_urls);
         final WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
-        final URL[] urls = wnm.makeCaptivePortalFallbackUrls();
+        final URL[] urls = wnm.makeCaptivePortalFallbackUrls(mMccContext);
         assertEquals(urls.length, 1);
         assertEquals("http://testUrl.com", urls[0].toString());
     }
@@ -1061,6 +1142,34 @@
         runPortalNetworkTest();
     }
 
+    @Test
+    public void testIsCaptivePortal_Http200EmptyResponse() throws Exception {
+        setSslException(mHttpsConnection);
+        setStatus(mHttpConnection, 200);
+        // Invalid if there is no content (can't login to an empty page)
+        runNetworkTest(VALIDATION_RESULT_INVALID, 0 /* probesSucceeded */, null);
+        verify(mCallbacks, never()).showProvisioningNotification(any(), any());
+    }
+
+    private void doCaptivePortal200ResponseTest(String expectedRedirectUrl) throws Exception {
+        setSslException(mHttpsConnection);
+        setStatus(mHttpConnection, 200);
+        doReturn(100L).when(mHttpConnection).getContentLengthLong();
+        // Redirect URL was null before S
+        runNetworkTest(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, expectedRedirectUrl);
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS)).showProvisioningNotification(any(), any());
+    }
+
+    @Test @IgnoreAfter(Build.VERSION_CODES.R)
+    public void testIsCaptivePortal_HttpProbeIs200Portal_R() throws Exception {
+        doCaptivePortal200ResponseTest(null);
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testIsCaptivePortal_HttpProbeIs200Portal() throws Exception {
+        doCaptivePortal200ResponseTest(TEST_HTTP_URL);
+    }
+
     private void setupPrivateIpResponse(String privateAddr) throws Exception {
         setSslException(mHttpsConnection);
         setPortal302(mHttpConnection);
@@ -1598,7 +1707,8 @@
         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
         assertFalse(wrappedMonitor.isDataStall());
 
-        wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
+        wrappedMonitor.setLastProbeTime(
+                SystemClock.elapsedRealtime() - STALL_EXPECTED_LAST_PROBE_TIME_MS);
         makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
         assertTrue(wrappedMonitor.isDataStall());
         verify(mCallbacks).notifyDataStallSuspected(
@@ -1608,7 +1718,8 @@
     @Test
     public void testIsDataStall_EvaluationDnsWithDnsTimeoutCount() throws Exception {
         WrappedNetworkMonitor wrappedMonitor = makeCellMeteredNetworkMonitor();
-        wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
+        wrappedMonitor.setLastProbeTime(
+                SystemClock.elapsedRealtime() - STALL_EXPECTED_LAST_PROBE_TIME_MS);
         makeDnsTimeoutEvent(wrappedMonitor, 3);
         assertFalse(wrappedMonitor.isDataStall());
         // Reset consecutive timeout counts.
@@ -1626,7 +1737,8 @@
         // Set the value to larger than the default dns log size.
         setConsecutiveDnsTimeoutThreshold(51);
         wrappedMonitor = makeCellMeteredNetworkMonitor();
-        wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
+        wrappedMonitor.setLastProbeTime(
+                SystemClock.elapsedRealtime() - STALL_EXPECTED_LAST_PROBE_TIME_MS);
         makeDnsTimeoutEvent(wrappedMonitor, 50);
         assertFalse(wrappedMonitor.isDataStall());
 
@@ -1658,7 +1770,8 @@
         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
         makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
         assertFalse(wrappedMonitor.isDataStall());
-        wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
+        wrappedMonitor.setLastProbeTime(
+                SystemClock.elapsedRealtime() - STALL_EXPECTED_LAST_PROBE_TIME_MS);
         assertTrue(wrappedMonitor.isDataStall());
         verify(mCallbacks).notifyDataStallSuspected(
                 matchDnsDataStallParcelable(DEFAULT_DNS_TIMEOUT_THRESHOLD));
@@ -1669,7 +1782,8 @@
         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
         makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
         assertFalse(wrappedMonitor.isDataStall());
-        wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
+        wrappedMonitor.setLastProbeTime(
+                SystemClock.elapsedRealtime() - STALL_EXPECTED_LAST_PROBE_TIME_MS);
         assertFalse(wrappedMonitor.isDataStall());
     }
 
@@ -1703,7 +1817,7 @@
         setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS | DATA_STALL_EVALUATION_TYPE_TCP);
         setupTcpDataStall();
         final WrappedNetworkMonitor nm = makeMonitor(CELL_METERED_CAPABILITIES);
-        nm.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
+        nm.setLastProbeTime(SystemClock.elapsedRealtime() - STALL_EXPECTED_LAST_PROBE_TIME_MS);
         makeDnsTimeoutEvent(nm, DEFAULT_DNS_TIMEOUT_THRESHOLD);
         assertTrue(nm.isDataStall());
         verify(mCallbacks).notifyDataStallSuspected(
@@ -1739,25 +1853,105 @@
         runFailedNetworkTest();
     }
 
-    @Test
-    public void testNoInternetCapabilityValidated() throws Exception {
-        runNetworkTest(TEST_LINK_PROPERTIES, CELL_NO_INTERNET_CAPABILITIES,
-                NETWORK_VALIDATION_RESULT_VALID, 0 /* probesSucceeded */, null /* redirectUrl */);
+    private void doValidationSkippedTest(NetworkCapabilities nc, int validationResult)
+            throws Exception {
+        runNetworkTest(TEST_LINK_PROPERTIES, nc, validationResult,
+                0 /* probesSucceeded */, null /* redirectUrl */);
         verify(mCleartextDnsNetwork, never()).openConnection(any());
     }
 
     @Test
-    public void testLaunchCaptivePortalApp() throws Exception {
+    public void testNoInternetCapabilityValidated() throws Exception {
+        doValidationSkippedTest(CELL_NO_INTERNET_CAPABILITIES,
+                NETWORK_VALIDATION_RESULT_VALID | NETWORK_VALIDATION_RESULT_SKIPPED);
+    }
+
+    @Test
+    public void testNoInternetCapabilityValidated_OlderPlatform() throws Exception {
+        // Before callbacks version 11, NETWORK_VALIDATION_RESULT_SKIPPED is not sent
+        resetCallbacks(10);
+        doValidationSkippedTest(CELL_NO_INTERNET_CAPABILITIES, NETWORK_VALIDATION_RESULT_VALID);
+    }
+
+    @Test
+    public void testNoTrustedCapabilityValidated() throws Exception {
+        // Cannot use the NetworkCapabilities builder on Q
+        final NetworkCapabilities nc = new NetworkCapabilities()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .removeCapability(NET_CAPABILITY_TRUSTED)
+                .addTransportType(TRANSPORT_CELLULAR);
+        if (ShimUtils.isAtLeastS()) {
+            nc.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+        }
+        doValidationSkippedTest(nc,
+                NETWORK_VALIDATION_RESULT_VALID | NETWORK_VALIDATION_RESULT_SKIPPED);
+    }
+
+    @Test
+    public void testRestrictedCapabilityValidated() throws Exception {
+        // Cannot use the NetworkCapabilities builder on Q
+        final NetworkCapabilities nc = new NetworkCapabilities()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                .addTransportType(TRANSPORT_CELLULAR);
+        if (ShimUtils.isAtLeastS()) {
+            nc.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+        }
+        doValidationSkippedTest(nc,
+                NETWORK_VALIDATION_RESULT_VALID | NETWORK_VALIDATION_RESULT_SKIPPED);
+    }
+
+    private NetworkCapabilities getVcnUnderlyingCarrierWifiCaps() {
+        // Must be called from within the test because NOT_VCN_MANAGED is an invalid capability
+        // value up to Android R. Thus, this must be guarded by an SDK check in tests that use this.
+        return new NetworkCapabilities.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .build();
+    }
+
+    @Test
+    public void testVcnUnderlyingNetwork() throws Exception {
+        assumeTrue(ShimUtils.isAtLeastS());
+        setStatus(mHttpsConnection, 204);
+        setStatus(mHttpConnection, 204);
+
+        final NetworkMonitor nm = runNetworkTest(
+                TEST_LINK_PROPERTIES, getVcnUnderlyingCarrierWifiCaps(),
+                NETWORK_VALIDATION_RESULT_VALID,
+                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS,
+                null /* redirectUrl */);
+        assertEquals(NETWORK_VALIDATION_RESULT_VALID,
+                nm.getEvaluationState().getEvaluationResult());
+    }
+
+    @Test
+    public void testVcnUnderlyingNetworkBadNetwork() throws Exception {
+        assumeTrue(ShimUtils.isAtLeastS());
+        setSslException(mHttpsConnection);
+        setStatus(mHttpConnection, 500);
+        setStatus(mFallbackConnection, 404);
+
+        final NetworkMonitor nm = runNetworkTest(
+                TEST_LINK_PROPERTIES, getVcnUnderlyingCarrierWifiCaps(),
+                VALIDATION_RESULT_INVALID, 0 /* probesSucceeded */, null /* redirectUrl */);
+        assertEquals(VALIDATION_RESULT_INVALID,
+                nm.getEvaluationState().getEvaluationResult());
+    }
+
+    public void setupAndLaunchCaptivePortalApp(final NetworkMonitor nm) throws Exception {
         setSslException(mHttpsConnection);
         setPortal302(mHttpConnection);
         when(mHttpConnection.getHeaderField(eq("location"))).thenReturn(TEST_LOGIN_URL);
-        final NetworkMonitor nm = makeMonitor(CELL_METERED_CAPABILITIES);
         notifyNetworkConnected(nm, CELL_METERED_CAPABILITIES);
 
         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
                 .showProvisioningNotification(any(), any());
 
-        assertEquals(1, mRegisteredReceivers.size());
+        assertCaptivePortalAppReceiverRegistered(true /* isPortal */);
 
         // Check that startCaptivePortalApp sends the expected intent.
         nm.launchCaptivePortalApp();
@@ -1778,17 +1972,49 @@
         final String redirectUrl = bundle.getString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL);
         assertEquals(TEST_HTTP_URL, redirectUrl);
 
+        resetCallbacks();
+    }
+
+    @Test
+    public void testCaptivePortalLogin() throws Exception {
+        final NetworkMonitor nm = makeMonitor(CELL_METERED_CAPABILITIES);
+        setupAndLaunchCaptivePortalApp(nm);
+
         // Have the app report that the captive portal is dismissed, and check that we revalidate.
         setStatus(mHttpsConnection, 204);
         setStatus(mHttpConnection, 204);
 
-        resetCallbacks();
         nm.notifyCaptivePortalAppFinished(APP_RETURN_DISMISSED);
         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
                 .notifyNetworkTestedWithExtras(matchNetworkTestResultParcelable(
                         NETWORK_VALIDATION_RESULT_VALID,
                         NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP));
-        assertEquals(0, mRegisteredReceivers.size());
+        assertCaptivePortalAppReceiverRegistered(false /* isPortal */);
+    }
+
+    @Test
+    public void testCaptivePortalUseAsIs() throws Exception {
+        final NetworkMonitor nm = makeMonitor(CELL_METERED_CAPABILITIES);
+        setupAndLaunchCaptivePortalApp(nm);
+
+        // The user decides this network is wanted as is, either by encountering an SSL error or
+        // encountering an unknown scheme and then deciding to continue through the browser, or by
+        // selecting this option through the options menu.
+        nm.notifyCaptivePortalAppFinished(APP_RETURN_WANTED_AS_IS);
+        // The captive portal is still closed, but the network validates since the user said so.
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
+                .notifyNetworkTestedWithExtras(matchNetworkTestResultParcelable(
+                        NETWORK_VALIDATION_RESULT_VALID, 0 /* probesSucceeded */));
+        resetCallbacks();
+
+        // Revalidate.
+        nm.forceReevaluation(0 /* responsibleUid */);
+
+        // The network should still be valid.
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
+                .notifyNetworkTestedWithExtras(matchNetworkTestResultParcelable(
+                        NETWORK_VALIDATION_RESULT_VALID, 0 /* probesSucceeded */,
+                        TEST_LOGIN_URL));
     }
 
     @Test
@@ -1996,7 +2222,7 @@
         nm.notifyNetworkConnected(TEST_LINK_PROPERTIES, nc);
         verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID,
                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS);
-        nm.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
+        nm.setLastProbeTime(SystemClock.elapsedRealtime() - STALL_EXPECTED_LAST_PROBE_TIME_MS);
         return nm;
     }
 
@@ -2018,6 +2244,8 @@
                 ArgumentCaptor.forClass(DataStallDetectionStats.class);
         verify(mDependencies, timeout(HANDLER_TIMEOUT_MS).times(1))
                 .writeDataStallDetectionStats(statsCaptor.capture(), probeResultCaptor.capture());
+        // Ensure probe will not stop due to rate-limiting mechanism.
+        nm.setLastProbeTime(SystemClock.elapsedRealtime() - STALL_EXPECTED_LAST_PROBE_TIME_MS);
         assertTrue(nm.isDataStall());
         assertTrue(probeResultCaptor.getValue().isSuccessful());
         verifyTestDataStallDetectionStats(evalType, transport, statsCaptor.getValue());
@@ -2347,7 +2575,7 @@
         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
             .showProvisioningNotification(any(), any());
 
-        assertEquals(1, mRegisteredReceivers.size());
+        assertCaptivePortalAppReceiverRegistered(true /* isPortal */);
         // Check that startCaptivePortalApp sends the expected intent.
         nm.launchCaptivePortalApp();
 
@@ -2537,6 +2765,117 @@
         verify(mCleartextDnsNetwork, times(4)).openConnection(any());
     }
 
+    @Test
+    public void testIsCaptivePortal_FromExternalSource() throws Exception {
+        assumeTrue(CaptivePortalDataShimImpl.isSupported());
+        assumeTrue(ShimUtils.isAtLeastS());
+        when(mDependencies.isFeatureEnabled(any(), eq(NAMESPACE_CONNECTIVITY),
+                eq(DISMISS_PORTAL_IN_VALIDATED_NETWORK), anyBoolean())).thenReturn(true);
+        final NetworkMonitor monitor = makeMonitor(WIFI_NOT_METERED_CAPABILITIES);
+
+        NetworkInformationShim networkShim = NetworkInformationShimImpl.newInstance();
+        CaptivePortalDataShim captivePortalData = new CaptivePortalDataShimImpl(
+                new CaptivePortalData.Builder().setCaptive(true).build());
+        final LinkProperties linkProperties = new LinkProperties(TEST_LINK_PROPERTIES);
+        networkShim.setCaptivePortalData(linkProperties, captivePortalData);
+        CaptivePortalDataShim captivePortalDataShim =
+                networkShim.getCaptivePortalData(linkProperties);
+
+        try {
+            // Set up T&C captive portal info from Passpoint
+            captivePortalData = captivePortalDataShim.withPasspointInfo(TEST_FRIENDLY_NAME,
+                    Uri.parse(TEST_VENUE_INFO_URL), Uri.parse(TEST_LOGIN_URL));
+        } catch (UnsupportedApiLevelException e) {
+            // Minimum API level for this test is 31
+            return;
+        }
+
+        networkShim.setCaptivePortalData(linkProperties, captivePortalData);
+        monitor.notifyLinkPropertiesChanged(linkProperties);
+        final NetworkCapabilities networkCapabilities =
+                new NetworkCapabilities(WIFI_NOT_METERED_CAPABILITIES);
+        monitor.notifyNetworkConnected(linkProperties, networkCapabilities);
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+                .showProvisioningNotification(any(), any());
+        assertCaptivePortalAppReceiverRegistered(true /* isPortal */);
+        verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, TEST_LOGIN_URL);
+
+        // Force reevaluation and confirm that the network is still captive
+        HandlerUtils.waitForIdle(monitor.getHandler(), HANDLER_TIMEOUT_MS);
+        resetCallbacks();
+        monitor.forceReevaluation(Process.myUid());
+        assertEquals(monitor.getEvaluationState().getProbeCompletedResult(), 0);
+        verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, TEST_LOGIN_URL);
+
+        // Check that startCaptivePortalApp sends the expected intent.
+        monitor.launchCaptivePortalApp();
+
+        verify(mCm, timeout(HANDLER_TIMEOUT_MS).times(1)).startCaptivePortalApp(
+                argThat(network -> TEST_NETID == network.netId),
+                argThat(bundle -> bundle.getString(
+                        ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL).equals(TEST_LOGIN_URL)
+                        && TEST_NETID == ((Network) bundle.getParcelable(
+                        ConnectivityManager.EXTRA_NETWORK)).netId));
+    }
+
+    @Test
+    public void testOemPaidNetworkValidated() throws Exception {
+        setValidProbes();
+
+        final NetworkMonitor nm = runNetworkTest(TEST_LINK_PROPERTIES,
+                WIFI_OEM_PAID_CAPABILITIES,
+                NETWORK_VALIDATION_RESULT_VALID,
+                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS,
+                null /* redirectUrl */);
+        assertEquals(NETWORK_VALIDATION_RESULT_VALID,
+                nm.getEvaluationState().getEvaluationResult());
+    }
+
+    @Test
+    public void testOemPaidNetwork_AllProbesFailed() throws Exception {
+        setSslException(mHttpsConnection);
+        setStatus(mHttpConnection, 500);
+        setStatus(mFallbackConnection, 404);
+
+        runNetworkTest(TEST_LINK_PROPERTIES,
+                WIFI_OEM_PAID_CAPABILITIES,
+                VALIDATION_RESULT_INVALID, 0 /* probesSucceeded */, null /* redirectUrl */);
+    }
+
+    @Test
+    public void testOemPaidNetworkNoInternetCapabilityValidated() throws Exception {
+        setSslException(mHttpsConnection);
+        setStatus(mHttpConnection, 500);
+        setStatus(mFallbackConnection, 404);
+
+        final NetworkCapabilities networkCapabilities =
+                new NetworkCapabilities(WIFI_OEM_PAID_CAPABILITIES);
+        networkCapabilities.removeCapability(NET_CAPABILITY_INTERNET);
+
+        final int validationResult =
+                NETWORK_VALIDATION_RESULT_VALID | NETWORK_VALIDATION_RESULT_SKIPPED;
+        runNetworkTest(TEST_LINK_PROPERTIES, networkCapabilities,
+                validationResult, 0 /* probesSucceeded */, null /* redirectUrl */);
+
+        verify(mCleartextDnsNetwork, never()).openConnection(any());
+        verify(mHttpsConnection, never()).getResponseCode();
+        verify(mHttpConnection, never()).getResponseCode();
+        verify(mFallbackConnection, never()).getResponseCode();
+    }
+
+    @Test
+    public void testOemPaidNetwork_CaptivePortalNotLaunched() throws Exception {
+        setSslException(mHttpsConnection);
+        setStatus(mFallbackConnection, 404);
+        setPortal302(mHttpConnection);
+
+        runNetworkTest(TEST_LINK_PROPERTIES, WIFI_OEM_PAID_CAPABILITIES,
+                VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */,
+                TEST_LOGIN_URL);
+
+        verify(mCallbacks, never()).showProvisioningNotification(any(), any());
+    }
+
     private void setupResourceForMultipleProbes() {
         // Configure the resource to send multiple probe.
         when(mResources.getStringArray(R.array.config_captive_portal_https_urls))
@@ -2621,21 +2960,21 @@
     private NetworkMonitor runPortalNetworkTest() throws RemoteException {
         final NetworkMonitor nm = runNetworkTest(VALIDATION_RESULT_PORTAL,
                 0 /* probesSucceeded */, TEST_LOGIN_URL);
-        assertEquals(1, mRegisteredReceivers.size());
+        assertCaptivePortalAppReceiverRegistered(true /* isPortal */);
         return nm;
     }
 
     private NetworkMonitor runNoValidationNetworkTest() throws RemoteException {
         final NetworkMonitor nm = runNetworkTest(NETWORK_VALIDATION_RESULT_VALID,
                 0 /* probesSucceeded */, null /* redirectUrl */);
-        assertEquals(0, mRegisteredReceivers.size());
+        assertCaptivePortalAppReceiverRegistered(false /* isPortal */);
         return nm;
     }
 
     private NetworkMonitor runFailedNetworkTest() throws RemoteException {
         final NetworkMonitor nm = runNetworkTest(
                 VALIDATION_RESULT_INVALID, 0 /* probesSucceeded */, null /* redirectUrl */);
-        assertEquals(0, mRegisteredReceivers.size());
+        assertCaptivePortalAppReceiverRegistered(false /* isPortal */);
         return nm;
     }
 
@@ -2643,7 +2982,7 @@
             throws RemoteException {
         final NetworkMonitor nm = runNetworkTest(NETWORK_VALIDATION_RESULT_PARTIAL,
                 probesSucceeded, null /* redirectUrl */);
-        assertEquals(0, mRegisteredReceivers.size());
+        assertCaptivePortalAppReceiverRegistered(false /* isPortal */);
         return nm;
     }
 
@@ -2775,5 +3114,13 @@
     private DataStallReportParcelable matchTcpDataStallParcelable() {
         return argThat(p -> (p.detectionMethod & ConstantsShim.DETECTION_METHOD_TCP_METRICS) != 0);
     }
+
+    private void assertCaptivePortalAppReceiverRegistered(boolean isPortal) {
+        // There will be configuration change receiver registered after NetworkMonitor being
+        // started. If captive portal app receiver is registered, then the size of the registered
+        // receivers will be 2. Otherwise, mRegisteredReceivers should only contain 1 configuration
+        // change receiver.
+        assertEquals(isPortal ? 2 : 1, mRegisteredReceivers.size());
+    }
 }