Snap for 5925869 from 3c9f687c97393a15d238a3272f62c595052e6b2e to sdk-release

Change-Id: I2aa3beab9ab9e716851c4b8e15d24f99af49774d
diff --git a/Android.bp b/Android.bp
index 9ba4f98..5c6ca9b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -14,25 +14,58 @@
 // limitations under the License.
 //
 
-java_library {
-    name: "captiveportal-lib",
-    srcs: ["common/**/*.java"],
-    libs: [
-        "androidx.annotation_annotation",
-    ],
-    sdk_version: "system_current",
-}
+// The network stack can be compiled using system_current (non-finalized) SDK, or finalized system_X
+// SDK. There is also a variant that uses system_current SDK and runs in the system process
+// (InProcessNetworkStack). The following structure is used to create the build rules:
+//
+//                          NetworkStackAndroidLibraryDefaults <-- common defaults for android libs
+//                                     /           \
+//    +NetworkStackApiStableShims --> /             \ <-- +NetworkStackApiCurrentShims
+//    +NetworkStackApiStableLevel    /               \    +NetworkStackApiCurrentLevel
+//                                  /                 \
+//           NetworkStackApiStableLib         NetworkStackApiCurrentLib <-- android libs w/ all code
+//                     |                                     |             (also used in unit tests)
+//                     | <--   +NetworkStackAppDefaults  --> |
+//                     |          (APK build params)         |
+//                     |                                     |
+//                     | <-- +NetworkStackApiStableLevel     | <-- +NetworkStackApiCurrentLevel
+//                     |                                     |
+//                     |                                     |
+//           NetworkStackApiStable          NetworkStack, InProcessNetworkStack, <-- output APKs
+//                                                    TestNetworkStack
 
+// Common defaults to define SDK level
 java_defaults {
-    name: "NetworkStackCommon",
+    name: "NetworkStackApiCurrentLevel",
     sdk_version: "system_current",
     min_sdk_version: "28",
 }
 
-// Library including the network stack, used to compile both variants of the network stack
-android_library {
-    name: "NetworkStackBase",
-    defaults: ["NetworkStackCommon"],
+java_defaults {
+    name: "NetworkStackApiStableLevel",
+    sdk_version: "system_current", // TODO: change to system_29
+    min_sdk_version: "28",
+}
+
+// Java libraries for the API shims
+filegroup {
+    name: "NetworkStackApiCurrentShims",
+    srcs: [
+        "apishim/current/**/*.java"
+    ],
+}
+
+filegroup {
+    name: "NetworkStackApiStableShims",
+    srcs: [
+        "apishim/29/**/*.java"
+    ],
+}
+
+// Common defaults for android libraries containing network stack code, used to compile variants of
+// the network stack in the system process and in the network_stack process
+java_defaults {
+    name: "NetworkStackAndroidLibraryDefaults",
     srcs: [
         "src/**/*.java",
         ":framework-networkstack-shared-srcs",
@@ -41,9 +74,8 @@
     ],
     static_libs: [
         "androidx.annotation_annotation",
-        "ipmemorystore-client",
         "netd_aidl_interface-V2-java",
-        "networkstack-aidl-interfaces-V3-java",
+        "networkstack-client",
         "datastallprotosnano",
         "networkstackprotosnano",
         "captiveportal-lib",
@@ -51,6 +83,85 @@
     manifest: "AndroidManifestBase.xml",
 }
 
+// The versions of the android library containing network stack code compiled for each SDK variant
+android_library {
+    name: "NetworkStackApiCurrentLib",
+    defaults: ["NetworkStackApiCurrentLevel", "NetworkStackAndroidLibraryDefaults"],
+    srcs: [
+        ":NetworkStackApiCurrentShims",
+    ],
+}
+
+android_library {
+    name: "NetworkStackApiStableLib",
+    defaults: ["NetworkStackApiStableLevel", "NetworkStackAndroidLibraryDefaults"],
+    srcs: [
+        ":NetworkStackApiStableShims",
+    ],
+}
+
+// Common defaults for compiling the actual APK, based on the NetworkStackApiXBase android libraries
+java_defaults {
+    name: "NetworkStackAppDefaults",
+    privileged: true,
+    jni_libs: [
+        "libnativehelper_compat_libc++",
+        "libnetworkstackutilsjni",
+    ],
+    // Resources already included in NetworkStackBase
+    resource_dirs: [],
+    jarjar_rules: "jarjar-rules-shared.txt",
+    use_embedded_native_libs: true,
+    optimize: {
+        proguard_flags_files: ["proguard.flags"],
+    },
+}
+
+// Non-updatable network stack running in the system server process for devices not using the module
+android_app {
+    name: "InProcessNetworkStack",
+    defaults: [ "NetworkStackAppDefaults", "NetworkStackApiCurrentLevel"],
+    static_libs: ["NetworkStackApiCurrentLib"],
+    certificate: "platform",
+    manifest: "AndroidManifest_InProcess.xml",
+    // InProcessNetworkStack is a replacement for NetworkStack
+    overrides: ["NetworkStack"],
+    // 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"],
+}
+
+// Updatable network stack packaged as an application
+android_app {
+    name: "NetworkStack",
+    defaults: ["NetworkStackAppDefaults", "NetworkStackApiCurrentLevel"],
+    static_libs: ["NetworkStackApiCurrentLib"],
+    certificate: "networkstack",
+    manifest: "AndroidManifest.xml",
+    // The permission configuration *must* be included to ensure security of the device
+    required: ["NetworkPermissionConfig"],
+}
+
+// Updatable network stack for finalized API
+android_app {
+    name: "NetworkStackApiStable",
+    defaults: ["NetworkStackAppDefaults", "NetworkStackApiStableLevel"],
+    static_libs: ["NetworkStackApiStableLib"],
+    certificate: "networkstack",
+    manifest: "AndroidManifest.xml",
+    // The permission configuration *must* be included to ensure security of the device
+    required: ["NetworkPermissionConfig"],
+}
+
+// Android library to derive test APKs for integration tests
+android_library {
+    name: "TestNetworkStackLib",
+    defaults: ["NetworkStackAppDefaults", "NetworkStackApiCurrentLevel"],
+    static_libs: ["NetworkStackApiCurrentLib"],
+    manifest: "AndroidManifest.xml",
+}
+
 cc_library_shared {
     name: "libnetworkstackutilsjni",
     srcs: [
@@ -81,50 +192,6 @@
     ],
 }
 
-java_defaults {
-    name: "NetworkStackAppCommon",
-    defaults: ["NetworkStackCommon"],
-    privileged: true,
-    static_libs: [
-        "NetworkStackBase",
-    ],
-    jni_libs: [
-        "libnativehelper_compat_libc++",
-        "libnetworkstackutilsjni",
-    ],
-    // Resources already included in NetworkStackBase
-    resource_dirs: [],
-    jarjar_rules: "jarjar-rules-shared.txt",
-    optimize: {
-        proguard_flags_files: ["proguard.flags"],
-    },
-}
-
-// Non-updatable network stack running in the system server process for devices not using the module
-android_app {
-    name: "InProcessNetworkStack",
-    defaults: ["NetworkStackAppCommon"],
-    certificate: "platform",
-    manifest: "AndroidManifest_InProcess.xml",
-    // InProcessNetworkStack is a replacement for NetworkStack
-    overrides: ["NetworkStack"],
-    // 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"],
-}
-
-// Updatable network stack packaged as an application
-android_app {
-    name: "NetworkStack",
-    defaults: ["NetworkStackAppCommon"],
-    certificate: "networkstack",
-    manifest: "AndroidManifest.xml",
-    use_embedded_native_libs: true,
-    // The permission configuration *must* be included to ensure security of the device
-    required: ["NetworkPermissionConfig"],
-}
-
 genrule {
     name: "statslog-networkstack-java-gen",
     tools: ["stats-log-api-gen"],
@@ -142,11 +209,10 @@
 
 android_app {
     name: "TestNetworkStack",
-    defaults: ["NetworkStackAppCommon"],
+    defaults: ["NetworkStackAppDefaults", "NetworkStackApiCurrentLevel"],
+    static_libs: ["NetworkStackApiCurrentLib"],
     certificate: "networkstack",
     manifest: ":NetworkStackTestAndroidManifest",
-    use_embedded_native_libs: true,
     // The permission configuration *must* be included to ensure security of the device
     required: ["NetworkPermissionConfig"],
 }
-
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index bb838a2..249e510 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -17,9 +17,13 @@
  */
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.networkstack"
-          android:sharedUserId="android.uid.networkstack">
-    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
+  package="com.android.networkstack"
+  android:sharedUserId="android.uid.networkstack"
+  android:versionCode="290000000"
+  android:versionName="2019-09"
+>
+
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
 
     <!-- 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
@@ -39,8 +43,10 @@
     <uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK" />
     <application
         android:extractNativeLibs="false"
-        android:persistent="true">
-        <service android:name="com.android.server.NetworkStackService">
+        android:persistent="true"
+        android:process="com.android.networkstack.process">
+        <service android:name="com.android.server.NetworkStackService"
+                 android:permission="android.permission.MAINLINE_NETWORK_STACK">
             <intent-filter>
                 <action android:name="android.net.INetworkStackConnector"/>
             </intent-filter>
diff --git a/AndroidManifest_InProcess.xml b/AndroidManifest_InProcess.xml
index 2778a2a..723df09 100644
--- a/AndroidManifest_InProcess.xml
+++ b/AndroidManifest_InProcess.xml
@@ -22,7 +22,9 @@
           android:process="system">
     <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
     <application>
-        <service android:name="com.android.server.NetworkStackService" android:process="system">
+        <service android:name="com.android.server.NetworkStackService"
+                 android:process="system"
+                 android:permission="android.permission.MAINLINE_NETWORK_STACK">
             <intent-filter>
                 <action android:name="android.net.INetworkStackConnector.InProcess"/>
             </intent-filter>
diff --git a/apishim/29/com/android/networkstack/apishim/SocketUtilsShimImpl.java b/apishim/29/com/android/networkstack/apishim/SocketUtilsShimImpl.java
new file mode 100644
index 0000000..0e41e19
--- /dev/null
+++ b/apishim/29/com/android/networkstack/apishim/SocketUtilsShimImpl.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim;
+
+import android.net.util.SocketUtils;
+
+import androidx.annotation.NonNull;
+
+import java.net.SocketAddress;
+
+/**
+ * Implementation of SocketUtilsShim for API 29.
+ */
+public class SocketUtilsShimImpl implements SocketUtilsShim {
+    @NonNull
+    @Override
+    public SocketAddress makePacketSocketAddress(
+            int protocol, int ifIndex, @NonNull byte[] hwAddr) {
+        // Not available for API <= 29: fallback to older behavior.
+        return SocketUtils.makePacketSocketAddress(ifIndex, hwAddr);
+    }
+}
diff --git a/apishim/current/com/android/networkstack/apishim/SocketUtilsShimImpl.java b/apishim/current/com/android/networkstack/apishim/SocketUtilsShimImpl.java
new file mode 100644
index 0000000..92f8438
--- /dev/null
+++ b/apishim/current/com/android/networkstack/apishim/SocketUtilsShimImpl.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim;
+
+import android.net.util.SocketUtils;
+
+import androidx.annotation.NonNull;
+
+import java.net.SocketAddress;
+
+/**
+ * Implementation of {@link SocketUtilsShim} for API 30.
+ */
+public class SocketUtilsShimImpl implements SocketUtilsShim {
+    @NonNull
+    @Override
+    public SocketAddress makePacketSocketAddress(
+            int protocol, int ifIndex, @NonNull byte[] hwAddr) {
+        // TODO: use new API (which takes protocol, ifIndex, hwAddr) once implemented
+        return SocketUtils.makePacketSocketAddress(ifIndex, hwAddr);
+    }
+}
diff --git a/common/captiveportal/Android.bp b/common/captiveportal/Android.bp
new file mode 100644
index 0000000..d34ab85
--- /dev/null
+++ b/common/captiveportal/Android.bp
@@ -0,0 +1,24 @@
+//
+// 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: "captiveportal-lib",
+    srcs: ["src/**/*.java"],
+    libs: [
+        "androidx.annotation_annotation",
+    ],
+    sdk_version: "system_current",
+}
\ No newline at end of file
diff --git a/common/CaptivePortalProbeResult.java b/common/captiveportal/src/android/net/captiveportal/CaptivePortalProbeResult.java
similarity index 100%
rename from common/CaptivePortalProbeResult.java
rename to common/captiveportal/src/android/net/captiveportal/CaptivePortalProbeResult.java
diff --git a/common/CaptivePortalProbeSpec.java b/common/captiveportal/src/android/net/captiveportal/CaptivePortalProbeSpec.java
similarity index 100%
rename from common/CaptivePortalProbeSpec.java
rename to common/captiveportal/src/android/net/captiveportal/CaptivePortalProbeSpec.java
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp
new file mode 100644
index 0000000..ccb3f45
--- /dev/null
+++ b/common/networkstackclient/Android.bp
@@ -0,0 +1,92 @@
+//
+// 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.
+//
+
+// AIDL interfaces between the core system and the networking mainline module.
+aidl_interface {
+    name: "ipmemorystore-aidl-interfaces",
+    local_include_dir: "src",
+    srcs: [
+        "src/android/net/IIpMemoryStore.aidl",
+        "src/android/net/IIpMemoryStoreCallbacks.aidl",
+        "src/android/net/ipmemorystore/**/*.aidl",
+    ],
+    backend: {
+        ndk: {
+            enabled: false,
+        },
+        cpp: {
+            enabled: false,
+        },
+    },
+    api_dir: "aidl/ipmemorystore",
+    versions: [
+        "1",
+        "2",
+        "3",
+    ],
+}
+
+aidl_interface {
+    name: "networkstack-aidl-interfaces",
+    local_include_dir: "src",
+    include_dirs: ["frameworks/base/core/java"], // For framework parcelables.
+    srcs: [
+        "src/android/net/DhcpResultsParcelable.aidl",
+        "src/android/net/INetworkMonitor.aidl",
+        "src/android/net/INetworkMonitorCallbacks.aidl",
+        "src/android/net/INetworkStackConnector.aidl",
+        "src/android/net/INetworkStackStatusCallback.aidl",
+        "src/android/net/InitialConfigurationParcelable.aidl",
+        "src/android/net/NattKeepalivePacketDataParcelable.aidl",
+        "src/android/net/PrivateDnsConfigParcel.aidl",
+        "src/android/net/ProvisioningConfigurationParcelable.aidl",
+        "src/android/net/TcpKeepalivePacketDataParcelable.aidl",
+        "src/android/net/dhcp/DhcpServingParamsParcel.aidl",
+        "src/android/net/dhcp/IDhcpServer.aidl",
+        "src/android/net/dhcp/IDhcpServerCallbacks.aidl",
+        "src/android/net/ip/IIpClient.aidl",
+        "src/android/net/ip/IIpClientCallbacks.aidl",
+    ],
+    backend: {
+        ndk: {
+            enabled: false,
+        },
+        cpp: {
+            enabled: false,
+        },
+    },
+    api_dir: "aidl/networkstack",
+    imports: ["ipmemorystore-aidl-interfaces"],
+    versions: [
+        "1",
+        "2",
+        "3",
+    ],
+}
+
+java_library {
+    name: "networkstack-client",
+    sdk_version: "system_current",
+    srcs: [
+        ":framework-annotations",
+        "src/android/net/IpMemoryStoreClient.java",
+        "src/android/net/ipmemorystore/**/*.java",
+    ],
+    static_libs: [
+        "ipmemorystore-aidl-interfaces-V3-java",
+        "networkstack-aidl-interfaces-V3-java",
+    ],
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/1/android/net/IIpMemoryStore.aidl b/common/networkstackclient/aidl/ipmemorystore/1/android/net/IIpMemoryStore.aidl
new file mode 100644
index 0000000..a8cbab2
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/1/android/net/IIpMemoryStore.aidl
@@ -0,0 +1,9 @@
+package android.net;
+interface IIpMemoryStore {
+  oneway void storeNetworkAttributes(String l2Key, in android.net.ipmemorystore.NetworkAttributesParcelable attributes, android.net.ipmemorystore.IOnStatusListener listener);
+  oneway void storeBlob(String l2Key, String clientId, String name, in android.net.ipmemorystore.Blob data, android.net.ipmemorystore.IOnStatusListener listener);
+  oneway void findL2Key(in android.net.ipmemorystore.NetworkAttributesParcelable attributes, android.net.ipmemorystore.IOnL2KeyResponseListener listener);
+  oneway void isSameNetwork(String l2Key1, String l2Key2, android.net.ipmemorystore.IOnSameL3NetworkResponseListener listener);
+  oneway void retrieveNetworkAttributes(String l2Key, android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener listener);
+  oneway void retrieveBlob(String l2Key, String clientId, String name, android.net.ipmemorystore.IOnBlobRetrievedListener listener);
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/1/android/net/IIpMemoryStoreCallbacks.aidl b/common/networkstackclient/aidl/ipmemorystore/1/android/net/IIpMemoryStoreCallbacks.aidl
new file mode 100644
index 0000000..cf02c26
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/1/android/net/IIpMemoryStoreCallbacks.aidl
@@ -0,0 +1,4 @@
+package android.net;
+interface IIpMemoryStoreCallbacks {
+  oneway void onIpMemoryStoreFetched(in android.net.IIpMemoryStore ipMemoryStore);
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/Blob.aidl b/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/Blob.aidl
new file mode 100644
index 0000000..291dbef
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/Blob.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+parcelable Blob {
+  byte[] data;
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl b/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
new file mode 100644
index 0000000..52f40d4
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+interface IOnBlobRetrievedListener {
+  oneway void onBlobRetrieved(in android.net.ipmemorystore.StatusParcelable status, in String l2Key, in String name, in android.net.ipmemorystore.Blob data);
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl b/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
new file mode 100644
index 0000000..7853514
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+interface IOnL2KeyResponseListener {
+  oneway void onL2KeyResponse(in android.net.ipmemorystore.StatusParcelable status, in String l2Key);
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl b/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
new file mode 100644
index 0000000..3dd2ae6
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+interface IOnNetworkAttributesRetrievedListener {
+  oneway void onNetworkAttributesRetrieved(in android.net.ipmemorystore.StatusParcelable status, in String l2Key, in android.net.ipmemorystore.NetworkAttributesParcelable attributes);
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl b/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
new file mode 100644
index 0000000..46d4ecb
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+interface IOnSameL3NetworkResponseListener {
+  oneway void onSameL3NetworkResponse(in android.net.ipmemorystore.StatusParcelable status, in android.net.ipmemorystore.SameL3NetworkResponseParcelable response);
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnStatusListener.aidl b/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnStatusListener.aidl
new file mode 100644
index 0000000..54e654b
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnStatusListener.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+interface IOnStatusListener {
+  oneway void onComplete(in android.net.ipmemorystore.StatusParcelable status);
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
new file mode 100644
index 0000000..9531ea3
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
@@ -0,0 +1,8 @@
+package android.net.ipmemorystore;
+parcelable NetworkAttributesParcelable {
+  byte[] assignedV4Address;
+  long assignedV4AddressExpiry;
+  String groupHint;
+  android.net.ipmemorystore.Blob[] dnsAddresses;
+  int mtu;
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl b/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
new file mode 100644
index 0000000..414272b
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
@@ -0,0 +1,6 @@
+package android.net.ipmemorystore;
+parcelable SameL3NetworkResponseParcelable {
+  String l2Key1;
+  String l2Key2;
+  float confidence;
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/StatusParcelable.aidl b/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/StatusParcelable.aidl
new file mode 100644
index 0000000..92c6779
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/1/android/net/ipmemorystore/StatusParcelable.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+parcelable StatusParcelable {
+  int resultCode;
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/2/android/net/IIpMemoryStore.aidl b/common/networkstackclient/aidl/ipmemorystore/2/android/net/IIpMemoryStore.aidl
new file mode 100644
index 0000000..a8cbab2
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/2/android/net/IIpMemoryStore.aidl
@@ -0,0 +1,9 @@
+package android.net;
+interface IIpMemoryStore {
+  oneway void storeNetworkAttributes(String l2Key, in android.net.ipmemorystore.NetworkAttributesParcelable attributes, android.net.ipmemorystore.IOnStatusListener listener);
+  oneway void storeBlob(String l2Key, String clientId, String name, in android.net.ipmemorystore.Blob data, android.net.ipmemorystore.IOnStatusListener listener);
+  oneway void findL2Key(in android.net.ipmemorystore.NetworkAttributesParcelable attributes, android.net.ipmemorystore.IOnL2KeyResponseListener listener);
+  oneway void isSameNetwork(String l2Key1, String l2Key2, android.net.ipmemorystore.IOnSameL3NetworkResponseListener listener);
+  oneway void retrieveNetworkAttributes(String l2Key, android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener listener);
+  oneway void retrieveBlob(String l2Key, String clientId, String name, android.net.ipmemorystore.IOnBlobRetrievedListener listener);
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/2/android/net/IIpMemoryStoreCallbacks.aidl b/common/networkstackclient/aidl/ipmemorystore/2/android/net/IIpMemoryStoreCallbacks.aidl
new file mode 100644
index 0000000..cf02c26
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/2/android/net/IIpMemoryStoreCallbacks.aidl
@@ -0,0 +1,4 @@
+package android.net;
+interface IIpMemoryStoreCallbacks {
+  oneway void onIpMemoryStoreFetched(in android.net.IIpMemoryStore ipMemoryStore);
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/Blob.aidl b/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/Blob.aidl
new file mode 100644
index 0000000..291dbef
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/Blob.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+parcelable Blob {
+  byte[] data;
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl b/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
new file mode 100644
index 0000000..52f40d4
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+interface IOnBlobRetrievedListener {
+  oneway void onBlobRetrieved(in android.net.ipmemorystore.StatusParcelable status, in String l2Key, in String name, in android.net.ipmemorystore.Blob data);
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl b/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
new file mode 100644
index 0000000..7853514
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+interface IOnL2KeyResponseListener {
+  oneway void onL2KeyResponse(in android.net.ipmemorystore.StatusParcelable status, in String l2Key);
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl b/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
new file mode 100644
index 0000000..3dd2ae6
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+interface IOnNetworkAttributesRetrievedListener {
+  oneway void onNetworkAttributesRetrieved(in android.net.ipmemorystore.StatusParcelable status, in String l2Key, in android.net.ipmemorystore.NetworkAttributesParcelable attributes);
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl b/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
new file mode 100644
index 0000000..46d4ecb
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+interface IOnSameL3NetworkResponseListener {
+  oneway void onSameL3NetworkResponse(in android.net.ipmemorystore.StatusParcelable status, in android.net.ipmemorystore.SameL3NetworkResponseParcelable response);
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnStatusListener.aidl b/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnStatusListener.aidl
new file mode 100644
index 0000000..54e654b
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnStatusListener.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+interface IOnStatusListener {
+  oneway void onComplete(in android.net.ipmemorystore.StatusParcelable status);
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
new file mode 100644
index 0000000..9531ea3
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
@@ -0,0 +1,8 @@
+package android.net.ipmemorystore;
+parcelable NetworkAttributesParcelable {
+  byte[] assignedV4Address;
+  long assignedV4AddressExpiry;
+  String groupHint;
+  android.net.ipmemorystore.Blob[] dnsAddresses;
+  int mtu;
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl b/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
new file mode 100644
index 0000000..414272b
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
@@ -0,0 +1,6 @@
+package android.net.ipmemorystore;
+parcelable SameL3NetworkResponseParcelable {
+  String l2Key1;
+  String l2Key2;
+  float confidence;
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/StatusParcelable.aidl b/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/StatusParcelable.aidl
new file mode 100644
index 0000000..92c6779
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/2/android/net/ipmemorystore/StatusParcelable.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+parcelable StatusParcelable {
+  int resultCode;
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/3/android/net/IIpMemoryStore.aidl b/common/networkstackclient/aidl/ipmemorystore/3/android/net/IIpMemoryStore.aidl
new file mode 100644
index 0000000..30893b2
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/3/android/net/IIpMemoryStore.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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;
+interface IIpMemoryStore {
+  oneway void storeNetworkAttributes(String l2Key, in android.net.ipmemorystore.NetworkAttributesParcelable attributes, android.net.ipmemorystore.IOnStatusListener listener);
+  oneway void storeBlob(String l2Key, String clientId, String name, in android.net.ipmemorystore.Blob data, android.net.ipmemorystore.IOnStatusListener listener);
+  oneway void findL2Key(in android.net.ipmemorystore.NetworkAttributesParcelable attributes, android.net.ipmemorystore.IOnL2KeyResponseListener listener);
+  oneway void isSameNetwork(String l2Key1, String l2Key2, android.net.ipmemorystore.IOnSameL3NetworkResponseListener listener);
+  oneway void retrieveNetworkAttributes(String l2Key, android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener listener);
+  oneway void retrieveBlob(String l2Key, String clientId, String name, android.net.ipmemorystore.IOnBlobRetrievedListener listener);
+  oneway void factoryReset();
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/3/android/net/IIpMemoryStoreCallbacks.aidl b/common/networkstackclient/aidl/ipmemorystore/3/android/net/IIpMemoryStoreCallbacks.aidl
new file mode 100644
index 0000000..535ae2c
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/3/android/net/IIpMemoryStoreCallbacks.aidl
@@ -0,0 +1,21 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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;
+interface IIpMemoryStoreCallbacks {
+  oneway void onIpMemoryStoreFetched(in android.net.IIpMemoryStore ipMemoryStore);
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/Blob.aidl b/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/Blob.aidl
new file mode 100644
index 0000000..6d2dc0c
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/Blob.aidl
@@ -0,0 +1,21 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.ipmemorystore;
+parcelable Blob {
+  byte[] data;
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl b/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
new file mode 100644
index 0000000..48c1fb8
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
@@ -0,0 +1,21 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.ipmemorystore;
+interface IOnBlobRetrievedListener {
+  oneway void onBlobRetrieved(in android.net.ipmemorystore.StatusParcelable status, in String l2Key, in String name, in android.net.ipmemorystore.Blob data);
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl b/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
new file mode 100644
index 0000000..aebc724
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
@@ -0,0 +1,21 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.ipmemorystore;
+interface IOnL2KeyResponseListener {
+  oneway void onL2KeyResponse(in android.net.ipmemorystore.StatusParcelable status, in String l2Key);
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl b/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
new file mode 100644
index 0000000..b66db5a
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
@@ -0,0 +1,21 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.ipmemorystore;
+interface IOnNetworkAttributesRetrievedListener {
+  oneway void onNetworkAttributesRetrieved(in android.net.ipmemorystore.StatusParcelable status, in String l2Key, in android.net.ipmemorystore.NetworkAttributesParcelable attributes);
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl b/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
new file mode 100644
index 0000000..e9f2db4
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
@@ -0,0 +1,21 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.ipmemorystore;
+interface IOnSameL3NetworkResponseListener {
+  oneway void onSameL3NetworkResponse(in android.net.ipmemorystore.StatusParcelable status, in android.net.ipmemorystore.SameL3NetworkResponseParcelable response);
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnStatusListener.aidl b/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnStatusListener.aidl
new file mode 100644
index 0000000..49172ce
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnStatusListener.aidl
@@ -0,0 +1,21 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.ipmemorystore;
+interface IOnStatusListener {
+  oneway void onComplete(in android.net.ipmemorystore.StatusParcelable status);
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
new file mode 100644
index 0000000..188db20
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.ipmemorystore;
+parcelable NetworkAttributesParcelable {
+  byte[] assignedV4Address;
+  long assignedV4AddressExpiry;
+  String groupHint;
+  android.net.ipmemorystore.Blob[] dnsAddresses;
+  int mtu;
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl b/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
new file mode 100644
index 0000000..7a2ed48
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.ipmemorystore;
+parcelable SameL3NetworkResponseParcelable {
+  String l2Key1;
+  String l2Key2;
+  float confidence;
+}
diff --git a/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/StatusParcelable.aidl b/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/StatusParcelable.aidl
new file mode 100644
index 0000000..d9b0678
--- /dev/null
+++ b/common/networkstackclient/aidl/ipmemorystore/3/android/net/ipmemorystore/StatusParcelable.aidl
@@ -0,0 +1,21 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.ipmemorystore;
+parcelable StatusParcelable {
+  int resultCode;
+}
diff --git a/common/networkstackclient/aidl/networkstack/1/android/net/DhcpResultsParcelable.aidl b/common/networkstackclient/aidl/networkstack/1/android/net/DhcpResultsParcelable.aidl
new file mode 100644
index 0000000..92b5345
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/1/android/net/DhcpResultsParcelable.aidl
@@ -0,0 +1,8 @@
+package android.net;
+parcelable DhcpResultsParcelable {
+  android.net.StaticIpConfiguration baseConfiguration;
+  int leaseDuration;
+  int mtu;
+  String serverAddress;
+  String vendorInfo;
+}
diff --git a/common/networkstackclient/aidl/networkstack/1/android/net/INetworkMonitor.aidl b/common/networkstackclient/aidl/networkstack/1/android/net/INetworkMonitor.aidl
new file mode 100644
index 0000000..b19f522
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/1/android/net/INetworkMonitor.aidl
@@ -0,0 +1,17 @@
+package android.net;
+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;
+}
diff --git a/common/networkstackclient/aidl/networkstack/1/android/net/INetworkMonitorCallbacks.aidl b/common/networkstackclient/aidl/networkstack/1/android/net/INetworkMonitorCallbacks.aidl
new file mode 100644
index 0000000..ee9871d
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/1/android/net/INetworkMonitorCallbacks.aidl
@@ -0,0 +1,8 @@
+package android.net;
+interface INetworkMonitorCallbacks {
+  oneway void onNetworkMonitorCreated(in android.net.INetworkMonitor networkMonitor);
+  oneway void notifyNetworkTested(int testResult, @nullable String redirectUrl);
+  oneway void notifyPrivateDnsConfigResolved(in android.net.PrivateDnsConfigParcel config);
+  oneway void showProvisioningNotification(String action, String packageName);
+  oneway void hideProvisioningNotification();
+}
diff --git a/common/networkstackclient/aidl/networkstack/1/android/net/INetworkStackConnector.aidl b/common/networkstackclient/aidl/networkstack/1/android/net/INetworkStackConnector.aidl
new file mode 100644
index 0000000..7da11e4
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/1/android/net/INetworkStackConnector.aidl
@@ -0,0 +1,7 @@
+package android.net;
+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);
+}
diff --git a/common/networkstackclient/aidl/networkstack/1/android/net/INetworkStackStatusCallback.aidl b/common/networkstackclient/aidl/networkstack/1/android/net/INetworkStackStatusCallback.aidl
new file mode 100644
index 0000000..f6ca6f7
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/1/android/net/INetworkStackStatusCallback.aidl
@@ -0,0 +1,4 @@
+package android.net;
+interface INetworkStackStatusCallback {
+  oneway void onStatusAvailable(int statusCode);
+}
diff --git a/common/networkstackclient/aidl/networkstack/1/android/net/InitialConfigurationParcelable.aidl b/common/networkstackclient/aidl/networkstack/1/android/net/InitialConfigurationParcelable.aidl
new file mode 100644
index 0000000..c80a787
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/1/android/net/InitialConfigurationParcelable.aidl
@@ -0,0 +1,7 @@
+package android.net;
+parcelable InitialConfigurationParcelable {
+  android.net.LinkAddress[] ipAddresses;
+  android.net.IpPrefix[] directlyConnectedRoutes;
+  String[] dnsServers;
+  String gateway;
+}
diff --git a/common/networkstackclient/aidl/networkstack/1/android/net/PrivateDnsConfigParcel.aidl b/common/networkstackclient/aidl/networkstack/1/android/net/PrivateDnsConfigParcel.aidl
new file mode 100644
index 0000000..2de790b
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/1/android/net/PrivateDnsConfigParcel.aidl
@@ -0,0 +1,5 @@
+package android.net;
+parcelable PrivateDnsConfigParcel {
+  String hostname;
+  String[] ips;
+}
diff --git a/common/networkstackclient/aidl/networkstack/1/android/net/ProvisioningConfigurationParcelable.aidl b/common/networkstackclient/aidl/networkstack/1/android/net/ProvisioningConfigurationParcelable.aidl
new file mode 100644
index 0000000..3a6c304
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/1/android/net/ProvisioningConfigurationParcelable.aidl
@@ -0,0 +1,15 @@
+package android.net;
+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;
+}
diff --git a/common/networkstackclient/aidl/networkstack/1/android/net/TcpKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl/networkstack/1/android/net/TcpKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..e121c06
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/1/android/net/TcpKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,13 @@
+package android.net;
+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/networkstack/1/android/net/dhcp/DhcpServingParamsParcel.aidl b/common/networkstackclient/aidl/networkstack/1/android/net/dhcp/DhcpServingParamsParcel.aidl
new file mode 100644
index 0000000..67193ae
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/1/android/net/dhcp/DhcpServingParamsParcel.aidl
@@ -0,0 +1,11 @@
+package android.net.dhcp;
+parcelable DhcpServingParamsParcel {
+  int serverAddr;
+  int serverAddrPrefixLength;
+  int[] defaultRouters;
+  int[] dnsServers;
+  int[] excludedAddrs;
+  long dhcpLeaseTimeSecs;
+  int linkMtu;
+  boolean metered;
+}
diff --git a/common/networkstackclient/aidl/networkstack/1/android/net/dhcp/IDhcpServer.aidl b/common/networkstackclient/aidl/networkstack/1/android/net/dhcp/IDhcpServer.aidl
new file mode 100644
index 0000000..9143158
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/1/android/net/dhcp/IDhcpServer.aidl
@@ -0,0 +1,10 @@
+package android.net.dhcp;
+interface IDhcpServer {
+  oneway void start(in android.net.INetworkStackStatusCallback cb);
+  oneway void updateParams(in android.net.dhcp.DhcpServingParamsParcel params, in android.net.INetworkStackStatusCallback cb);
+  oneway void stop(in android.net.INetworkStackStatusCallback cb);
+  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/networkstack/1/android/net/dhcp/IDhcpServerCallbacks.aidl b/common/networkstackclient/aidl/networkstack/1/android/net/dhcp/IDhcpServerCallbacks.aidl
new file mode 100644
index 0000000..dcc4489
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/1/android/net/dhcp/IDhcpServerCallbacks.aidl
@@ -0,0 +1,4 @@
+package android.net.dhcp;
+interface IDhcpServerCallbacks {
+  oneway void onDhcpServerCreated(int statusCode, in android.net.dhcp.IDhcpServer server);
+}
diff --git a/common/networkstackclient/aidl/networkstack/1/android/net/ip/IIpClient.aidl b/common/networkstackclient/aidl/networkstack/1/android/net/ip/IIpClient.aidl
new file mode 100644
index 0000000..95a1574
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/1/android/net/ip/IIpClient.aidl
@@ -0,0 +1,14 @@
+package android.net.ip;
+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);
+}
diff --git a/common/networkstackclient/aidl/networkstack/1/android/net/ip/IIpClientCallbacks.aidl b/common/networkstackclient/aidl/networkstack/1/android/net/ip/IIpClientCallbacks.aidl
new file mode 100644
index 0000000..d6bc808
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/1/android/net/ip/IIpClientCallbacks.aidl
@@ -0,0 +1,16 @@
+package android.net.ip;
+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);
+}
diff --git a/common/networkstackclient/aidl/networkstack/2/android/net/DhcpResultsParcelable.aidl b/common/networkstackclient/aidl/networkstack/2/android/net/DhcpResultsParcelable.aidl
new file mode 100644
index 0000000..31891de
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/2/android/net/DhcpResultsParcelable.aidl
@@ -0,0 +1,9 @@
+package android.net;
+parcelable DhcpResultsParcelable {
+  android.net.StaticIpConfiguration baseConfiguration;
+  int leaseDuration;
+  int mtu;
+  String serverAddress;
+  String vendorInfo;
+  String serverHostName;
+}
diff --git a/common/networkstackclient/aidl/networkstack/2/android/net/INetworkMonitor.aidl b/common/networkstackclient/aidl/networkstack/2/android/net/INetworkMonitor.aidl
new file mode 100644
index 0000000..029968b
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/2/android/net/INetworkMonitor.aidl
@@ -0,0 +1,24 @@
+package android.net;
+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_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/networkstack/2/android/net/INetworkMonitorCallbacks.aidl b/common/networkstackclient/aidl/networkstack/2/android/net/INetworkMonitorCallbacks.aidl
new file mode 100644
index 0000000..ee9871d
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/2/android/net/INetworkMonitorCallbacks.aidl
@@ -0,0 +1,8 @@
+package android.net;
+interface INetworkMonitorCallbacks {
+  oneway void onNetworkMonitorCreated(in android.net.INetworkMonitor networkMonitor);
+  oneway void notifyNetworkTested(int testResult, @nullable String redirectUrl);
+  oneway void notifyPrivateDnsConfigResolved(in android.net.PrivateDnsConfigParcel config);
+  oneway void showProvisioningNotification(String action, String packageName);
+  oneway void hideProvisioningNotification();
+}
diff --git a/common/networkstackclient/aidl/networkstack/2/android/net/INetworkStackConnector.aidl b/common/networkstackclient/aidl/networkstack/2/android/net/INetworkStackConnector.aidl
new file mode 100644
index 0000000..7da11e4
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/2/android/net/INetworkStackConnector.aidl
@@ -0,0 +1,7 @@
+package android.net;
+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);
+}
diff --git a/common/networkstackclient/aidl/networkstack/2/android/net/INetworkStackStatusCallback.aidl b/common/networkstackclient/aidl/networkstack/2/android/net/INetworkStackStatusCallback.aidl
new file mode 100644
index 0000000..f6ca6f7
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/2/android/net/INetworkStackStatusCallback.aidl
@@ -0,0 +1,4 @@
+package android.net;
+interface INetworkStackStatusCallback {
+  oneway void onStatusAvailable(int statusCode);
+}
diff --git a/common/networkstackclient/aidl/networkstack/2/android/net/InitialConfigurationParcelable.aidl b/common/networkstackclient/aidl/networkstack/2/android/net/InitialConfigurationParcelable.aidl
new file mode 100644
index 0000000..c80a787
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/2/android/net/InitialConfigurationParcelable.aidl
@@ -0,0 +1,7 @@
+package android.net;
+parcelable InitialConfigurationParcelable {
+  android.net.LinkAddress[] ipAddresses;
+  android.net.IpPrefix[] directlyConnectedRoutes;
+  String[] dnsServers;
+  String gateway;
+}
diff --git a/common/networkstackclient/aidl/networkstack/2/android/net/NattKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl/networkstack/2/android/net/NattKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..65de883
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/2/android/net/NattKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,7 @@
+package android.net;
+parcelable NattKeepalivePacketDataParcelable {
+  byte[] srcAddress;
+  int srcPort;
+  byte[] dstAddress;
+  int dstPort;
+}
diff --git a/common/networkstackclient/aidl/networkstack/2/android/net/PrivateDnsConfigParcel.aidl b/common/networkstackclient/aidl/networkstack/2/android/net/PrivateDnsConfigParcel.aidl
new file mode 100644
index 0000000..2de790b
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/2/android/net/PrivateDnsConfigParcel.aidl
@@ -0,0 +1,5 @@
+package android.net;
+parcelable PrivateDnsConfigParcel {
+  String hostname;
+  String[] ips;
+}
diff --git a/common/networkstackclient/aidl/networkstack/2/android/net/ProvisioningConfigurationParcelable.aidl b/common/networkstackclient/aidl/networkstack/2/android/net/ProvisioningConfigurationParcelable.aidl
new file mode 100644
index 0000000..3a6c304
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/2/android/net/ProvisioningConfigurationParcelable.aidl
@@ -0,0 +1,15 @@
+package android.net;
+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;
+}
diff --git a/common/networkstackclient/aidl/networkstack/2/android/net/TcpKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl/networkstack/2/android/net/TcpKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..e121c06
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/2/android/net/TcpKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,13 @@
+package android.net;
+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/networkstack/2/android/net/dhcp/DhcpServingParamsParcel.aidl b/common/networkstackclient/aidl/networkstack/2/android/net/dhcp/DhcpServingParamsParcel.aidl
new file mode 100644
index 0000000..67193ae
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/2/android/net/dhcp/DhcpServingParamsParcel.aidl
@@ -0,0 +1,11 @@
+package android.net.dhcp;
+parcelable DhcpServingParamsParcel {
+  int serverAddr;
+  int serverAddrPrefixLength;
+  int[] defaultRouters;
+  int[] dnsServers;
+  int[] excludedAddrs;
+  long dhcpLeaseTimeSecs;
+  int linkMtu;
+  boolean metered;
+}
diff --git a/common/networkstackclient/aidl/networkstack/2/android/net/dhcp/IDhcpServer.aidl b/common/networkstackclient/aidl/networkstack/2/android/net/dhcp/IDhcpServer.aidl
new file mode 100644
index 0000000..9143158
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/2/android/net/dhcp/IDhcpServer.aidl
@@ -0,0 +1,10 @@
+package android.net.dhcp;
+interface IDhcpServer {
+  oneway void start(in android.net.INetworkStackStatusCallback cb);
+  oneway void updateParams(in android.net.dhcp.DhcpServingParamsParcel params, in android.net.INetworkStackStatusCallback cb);
+  oneway void stop(in android.net.INetworkStackStatusCallback cb);
+  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/networkstack/2/android/net/dhcp/IDhcpServerCallbacks.aidl b/common/networkstackclient/aidl/networkstack/2/android/net/dhcp/IDhcpServerCallbacks.aidl
new file mode 100644
index 0000000..dcc4489
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/2/android/net/dhcp/IDhcpServerCallbacks.aidl
@@ -0,0 +1,4 @@
+package android.net.dhcp;
+interface IDhcpServerCallbacks {
+  oneway void onDhcpServerCreated(int statusCode, in android.net.dhcp.IDhcpServer server);
+}
diff --git a/common/networkstackclient/aidl/networkstack/2/android/net/ip/IIpClient.aidl b/common/networkstackclient/aidl/networkstack/2/android/net/ip/IIpClient.aidl
new file mode 100644
index 0000000..77d5917
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/2/android/net/ip/IIpClient.aidl
@@ -0,0 +1,15 @@
+package android.net.ip;
+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 groupHint);
+}
diff --git a/common/networkstackclient/aidl/networkstack/2/android/net/ip/IIpClientCallbacks.aidl b/common/networkstackclient/aidl/networkstack/2/android/net/ip/IIpClientCallbacks.aidl
new file mode 100644
index 0000000..d6bc808
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/2/android/net/ip/IIpClientCallbacks.aidl
@@ -0,0 +1,16 @@
+package android.net.ip;
+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);
+}
diff --git a/common/networkstackclient/aidl/networkstack/3/android/net/DhcpResultsParcelable.aidl b/common/networkstackclient/aidl/networkstack/3/android/net/DhcpResultsParcelable.aidl
new file mode 100644
index 0000000..07ff321
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/3/android/net/DhcpResultsParcelable.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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;
+parcelable DhcpResultsParcelable {
+  android.net.StaticIpConfiguration baseConfiguration;
+  int leaseDuration;
+  int mtu;
+  String serverAddress;
+  String vendorInfo;
+  String serverHostName;
+}
diff --git a/common/networkstackclient/aidl/networkstack/3/android/net/INetworkMonitor.aidl b/common/networkstackclient/aidl/networkstack/3/android/net/INetworkMonitor.aidl
new file mode 100644
index 0000000..8aa68bd
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/3/android/net/INetworkMonitor.aidl
@@ -0,0 +1,41 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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;
+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_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/networkstack/3/android/net/INetworkMonitorCallbacks.aidl b/common/networkstackclient/aidl/networkstack/3/android/net/INetworkMonitorCallbacks.aidl
new file mode 100644
index 0000000..ea93729
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/3/android/net/INetworkMonitorCallbacks.aidl
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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;
+interface INetworkMonitorCallbacks {
+  oneway void onNetworkMonitorCreated(in android.net.INetworkMonitor networkMonitor);
+  oneway void notifyNetworkTested(int testResult, @nullable String redirectUrl);
+  oneway void notifyPrivateDnsConfigResolved(in android.net.PrivateDnsConfigParcel config);
+  oneway void showProvisioningNotification(String action, String packageName);
+  oneway void hideProvisioningNotification();
+}
diff --git a/common/networkstackclient/aidl/networkstack/3/android/net/INetworkStackConnector.aidl b/common/networkstackclient/aidl/networkstack/3/android/net/INetworkStackConnector.aidl
new file mode 100644
index 0000000..e3a83d1
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/3/android/net/INetworkStackConnector.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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;
+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);
+}
diff --git a/common/networkstackclient/aidl/networkstack/3/android/net/INetworkStackStatusCallback.aidl b/common/networkstackclient/aidl/networkstack/3/android/net/INetworkStackStatusCallback.aidl
new file mode 100644
index 0000000..3112a08
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/3/android/net/INetworkStackStatusCallback.aidl
@@ -0,0 +1,21 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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;
+interface INetworkStackStatusCallback {
+  oneway void onStatusAvailable(int statusCode);
+}
diff --git a/common/networkstackclient/aidl/networkstack/3/android/net/InitialConfigurationParcelable.aidl b/common/networkstackclient/aidl/networkstack/3/android/net/InitialConfigurationParcelable.aidl
new file mode 100644
index 0000000..f846b26
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/3/android/net/InitialConfigurationParcelable.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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;
+parcelable InitialConfigurationParcelable {
+  android.net.LinkAddress[] ipAddresses;
+  android.net.IpPrefix[] directlyConnectedRoutes;
+  String[] dnsServers;
+  String gateway;
+}
diff --git a/common/networkstackclient/aidl/networkstack/3/android/net/NattKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl/networkstack/3/android/net/NattKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..de75940
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/3/android/net/NattKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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;
+parcelable NattKeepalivePacketDataParcelable {
+  byte[] srcAddress;
+  int srcPort;
+  byte[] dstAddress;
+  int dstPort;
+}
diff --git a/common/networkstackclient/aidl/networkstack/3/android/net/PrivateDnsConfigParcel.aidl b/common/networkstackclient/aidl/networkstack/3/android/net/PrivateDnsConfigParcel.aidl
new file mode 100644
index 0000000..cf0fbce
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/3/android/net/PrivateDnsConfigParcel.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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;
+parcelable PrivateDnsConfigParcel {
+  String hostname;
+  String[] ips;
+}
diff --git a/common/networkstackclient/aidl/networkstack/3/android/net/ProvisioningConfigurationParcelable.aidl b/common/networkstackclient/aidl/networkstack/3/android/net/ProvisioningConfigurationParcelable.aidl
new file mode 100644
index 0000000..c0f2d4d
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/3/android/net/ProvisioningConfigurationParcelable.aidl
@@ -0,0 +1,32 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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;
+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;
+}
diff --git a/common/networkstackclient/aidl/networkstack/3/android/net/TcpKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl/networkstack/3/android/net/TcpKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..5926794
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/3/android/net/TcpKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,30 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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;
+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/networkstack/3/android/net/dhcp/DhcpServingParamsParcel.aidl b/common/networkstackclient/aidl/networkstack/3/android/net/dhcp/DhcpServingParamsParcel.aidl
new file mode 100644
index 0000000..7ab156f
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/3/android/net/dhcp/DhcpServingParamsParcel.aidl
@@ -0,0 +1,28 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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;
+parcelable DhcpServingParamsParcel {
+  int serverAddr;
+  int serverAddrPrefixLength;
+  int[] defaultRouters;
+  int[] dnsServers;
+  int[] excludedAddrs;
+  long dhcpLeaseTimeSecs;
+  int linkMtu;
+  boolean metered;
+}
diff --git a/common/networkstackclient/aidl/networkstack/3/android/net/dhcp/IDhcpServer.aidl b/common/networkstackclient/aidl/networkstack/3/android/net/dhcp/IDhcpServer.aidl
new file mode 100644
index 0000000..d281ecf
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/3/android/net/dhcp/IDhcpServer.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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 IDhcpServer {
+  oneway void start(in android.net.INetworkStackStatusCallback cb);
+  oneway void updateParams(in android.net.dhcp.DhcpServingParamsParcel params, in android.net.INetworkStackStatusCallback cb);
+  oneway void stop(in android.net.INetworkStackStatusCallback cb);
+  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/networkstack/3/android/net/dhcp/IDhcpServerCallbacks.aidl b/common/networkstackclient/aidl/networkstack/3/android/net/dhcp/IDhcpServerCallbacks.aidl
new file mode 100644
index 0000000..98be0ab
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/3/android/net/dhcp/IDhcpServerCallbacks.aidl
@@ -0,0 +1,21 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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 IDhcpServerCallbacks {
+  oneway void onDhcpServerCreated(int statusCode, in android.net.dhcp.IDhcpServer server);
+}
diff --git a/common/networkstackclient/aidl/networkstack/3/android/net/ip/IIpClient.aidl b/common/networkstackclient/aidl/networkstack/3/android/net/ip/IIpClient.aidl
new file mode 100644
index 0000000..85c8676
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/3/android/net/ip/IIpClient.aidl
@@ -0,0 +1,33 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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;
+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 groupHint);
+  oneway void addNattKeepalivePacketFilter(int slot, in android.net.NattKeepalivePacketDataParcelable pkt);
+}
diff --git a/common/networkstackclient/aidl/networkstack/3/android/net/ip/IIpClientCallbacks.aidl b/common/networkstackclient/aidl/networkstack/3/android/net/ip/IIpClientCallbacks.aidl
new file mode 100644
index 0000000..7fe39ed
--- /dev/null
+++ b/common/networkstackclient/aidl/networkstack/3/android/net/ip/IIpClientCallbacks.aidl
@@ -0,0 +1,33 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen 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.
+//
+// You must not make a backward incompatible changes to the AIDL files 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;
+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);
+}
diff --git a/common/networkstackclient/src/android/net/DhcpResultsParcelable.aidl b/common/networkstackclient/src/android/net/DhcpResultsParcelable.aidl
new file mode 100644
index 0000000..c98d9c2
--- /dev/null
+++ b/common/networkstackclient/src/android/net/DhcpResultsParcelable.aidl
@@ -0,0 +1,28 @@
+/**
+ * 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.
+ */
+
+package android.net;
+
+import android.net.StaticIpConfiguration;
+
+parcelable DhcpResultsParcelable {
+    StaticIpConfiguration baseConfiguration;
+    int leaseDuration;
+    int mtu;
+    String serverAddress;
+    String vendorInfo;
+    String serverHostName;
+}
diff --git a/common/networkstackclient/src/android/net/IIpMemoryStore.aidl b/common/networkstackclient/src/android/net/IIpMemoryStore.aidl
new file mode 100644
index 0000000..add221a
--- /dev/null
+++ b/common/networkstackclient/src/android/net/IIpMemoryStore.aidl
@@ -0,0 +1,118 @@
+/*
+ * 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;
+
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.IOnBlobRetrievedListener;
+import android.net.ipmemorystore.IOnL2KeyResponseListener;
+import android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener;
+import android.net.ipmemorystore.IOnSameL3NetworkResponseListener;
+import android.net.ipmemorystore.IOnStatusListener;
+
+/** {@hide} */
+oneway interface IIpMemoryStore {
+    /**
+     * Store network attributes for a given L2 key.
+     * If L2Key is null, choose automatically from the attributes ; passing null is equivalent to
+     * calling findL2Key with the attributes and storing in the returned value.
+     *
+     * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2
+     *              key and only care about grouping can pass a unique ID here like the ones
+     *              generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low
+     *              relevance of such a network will lead to it being evicted soon if it's not
+     *              refreshed. Use findL2Key to try and find a similar L2Key to these attributes.
+     * @param attributes The attributes for this network.
+     * @param listener A listener that will be invoked to inform of the completion of this call,
+     *                 or null if the client is not interested in learning about success/failure.
+     * @return (through the listener) The L2 key. This is useful if the L2 key was not specified.
+     *         If the call failed, the L2 key will be null.
+     */
+    void storeNetworkAttributes(String l2Key, in NetworkAttributesParcelable attributes,
+            IOnStatusListener listener);
+
+    /**
+     * Store a binary blob associated with an L2 key and a name.
+     *
+     * @param l2Key The L2 key for this network.
+     * @param clientId The ID of the client.
+     * @param name The name of this data.
+     * @param data The data to store.
+     * @param listener A listener to inform of the completion of this call, or null if the client
+     *        is not interested in learning about success/failure.
+     * @return (through the listener) A status to indicate success or failure.
+     */
+    void storeBlob(String l2Key, String clientId, String name, in Blob data,
+            IOnStatusListener listener);
+
+    /**
+     * Returns the best L2 key associated with the attributes.
+     *
+     * This will find a record that would be in the same group as the passed attributes. This is
+     * useful to choose the key for storing a sample or private data when the L2 key is not known.
+     * If multiple records are group-close to these attributes, the closest match is returned.
+     * If multiple records have the same closeness, the one with the smaller (unicode codepoint
+     * order) L2 key is returned.
+     * If no record matches these attributes, null is returned.
+     *
+     * @param attributes The attributes of the network to find.
+     * @param listener The listener that will be invoked to return the answer.
+     * @return (through the listener) The L2 key if one matched, or null.
+     */
+    void findL2Key(in NetworkAttributesParcelable attributes, IOnL2KeyResponseListener listener);
+
+    /**
+     * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point
+     * to the same L3 network. Group-closeness is used to determine this.
+     *
+     * @param l2Key1 The key for the first network.
+     * @param l2Key2 The key for the second network.
+     * @param listener The listener that will be invoked to return the answer.
+     * @return (through the listener) A SameL3NetworkResponse containing the answer and confidence.
+     */
+    void isSameNetwork(String l2Key1, String l2Key2, IOnSameL3NetworkResponseListener listener);
+
+    /**
+     * Retrieve the network attributes for a key.
+     * If no record is present for this key, this will return null attributes.
+     *
+     * @param l2Key The key of the network to query.
+     * @param listener The listener that will be invoked to return the answer.
+     * @return (through the listener) The network attributes and the L2 key associated with
+     *         the query.
+     */
+    void retrieveNetworkAttributes(String l2Key, IOnNetworkAttributesRetrievedListener listener);
+
+    /**
+     * Retrieve previously stored private data.
+     * If no data was stored for this L2 key and name this will return null.
+     *
+     * @param l2Key The L2 key.
+     * @param clientId The id of the client that stored this data.
+     * @param name The name of the data.
+     * @param listener The listener that will be invoked to return the answer.
+     * @return (through the listener) The private data (or null), with the L2 key
+     *         and the name of the data associated with the query.
+     */
+    void retrieveBlob(String l2Key, String clientId, String name,
+            IOnBlobRetrievedListener listener);
+
+    /**
+     * Delete all data because a factory reset operation is in progress.
+     */
+    void factoryReset();
+}
diff --git a/common/networkstackclient/src/android/net/IIpMemoryStoreCallbacks.aidl b/common/networkstackclient/src/android/net/IIpMemoryStoreCallbacks.aidl
new file mode 100644
index 0000000..53108db
--- /dev/null
+++ b/common/networkstackclient/src/android/net/IIpMemoryStoreCallbacks.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.net.IIpMemoryStore;
+
+/** {@hide} */
+oneway interface IIpMemoryStoreCallbacks {
+    void onIpMemoryStoreFetched(in IIpMemoryStore ipMemoryStore);
+}
diff --git a/common/networkstackclient/src/android/net/INetworkMonitor.aidl b/common/networkstackclient/src/android/net/INetworkMonitor.aidl
new file mode 100644
index 0000000..3fc81a3
--- /dev/null
+++ b/common/networkstackclient/src/android/net/INetworkMonitor.aidl
@@ -0,0 +1,68 @@
+/**
+ * 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.
+ */
+package android.net;
+
+import android.net.LinkProperties;
+import android.net.NetworkCapabilities;
+import android.net.PrivateDnsConfigParcel;
+
+/** @hide */
+oneway interface INetworkMonitor {
+    // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
+    // The network should be used as a default internet connection.  It was found to be:
+    // 1. a functioning network providing internet access, or
+    // 2. a captive portal and the user decided to use it as is.
+    const int NETWORK_TEST_RESULT_VALID = 0;
+
+    // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
+    // The network should not be used as a default internet connection.  It was found to be:
+    // 1. a captive portal and the user is prompted to sign-in, or
+    // 2. a captive portal and the user did not want to use it, or
+    // 3. a broken network (e.g. DNS failed, connect failed, HTTP request failed).
+    const int NETWORK_TEST_RESULT_INVALID = 1;
+
+    // After a network has been tested, this result can be sent with EVENT_NETWORK_TESTED.
+    // The network may be used as a default internet connection, but it was found to be a partial
+    // connectivity network which can get the pass result for http probe but get the failed result
+    // for https probe.
+    const int NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY = 2;
+
+    // Network validation flags indicate probe result and types. If no NETWORK_VALIDATION_RESULT_*
+    // 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.
+    const int NETWORK_VALIDATION_RESULT_VALID = 0x01;
+    const int NETWORK_VALIDATION_RESULT_PARTIAL = 0x02;
+    const int NETWORK_VALIDATION_PROBE_DNS = 0x04;
+    const int NETWORK_VALIDATION_PROBE_HTTP = 0x08;
+    const int NETWORK_VALIDATION_PROBE_HTTPS = 0x10;
+    const int NETWORK_VALIDATION_PROBE_FALLBACK = 0x20;
+    const int NETWORK_VALIDATION_PROBE_PRIVDNS = 0x40;
+
+    void start();
+    void launchCaptivePortalApp();
+    void notifyCaptivePortalAppFinished(int response);
+    void setAcceptPartialConnectivity();
+    void forceReevaluation(int uid);
+    void notifyPrivateDnsChanged(in PrivateDnsConfigParcel config);
+    void notifyDnsResponse(int returnCode);
+    void notifyNetworkConnected(in LinkProperties lp, in NetworkCapabilities nc);
+    void notifyNetworkDisconnected();
+    void notifyLinkPropertiesChanged(in LinkProperties lp);
+    void notifyNetworkCapabilitiesChanged(in NetworkCapabilities nc);
+}
diff --git a/common/networkstackclient/src/android/net/INetworkMonitorCallbacks.aidl b/common/networkstackclient/src/android/net/INetworkMonitorCallbacks.aidl
new file mode 100644
index 0000000..2c61511
--- /dev/null
+++ b/common/networkstackclient/src/android/net/INetworkMonitorCallbacks.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+import android.net.INetworkMonitor;
+import android.net.PrivateDnsConfigParcel;
+
+/** @hide */
+oneway interface INetworkMonitorCallbacks {
+    void onNetworkMonitorCreated(in INetworkMonitor networkMonitor);
+    void notifyNetworkTested(int testResult, @nullable String redirectUrl);
+    void notifyPrivateDnsConfigResolved(in PrivateDnsConfigParcel config);
+    void showProvisioningNotification(String action, String packageName);
+    void hideProvisioningNotification();
+}
\ No newline at end of file
diff --git a/common/networkstackclient/src/android/net/INetworkStackConnector.aidl b/common/networkstackclient/src/android/net/INetworkStackConnector.aidl
new file mode 100644
index 0000000..3751c36
--- /dev/null
+++ b/common/networkstackclient/src/android/net/INetworkStackConnector.aidl
@@ -0,0 +1,32 @@
+/**
+ * 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.
+ */
+package android.net;
+
+import android.net.IIpMemoryStoreCallbacks;
+import android.net.INetworkMonitorCallbacks;
+import android.net.Network;
+import android.net.dhcp.DhcpServingParamsParcel;
+import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.ip.IIpClientCallbacks;
+
+/** @hide */
+oneway interface INetworkStackConnector {
+    void makeDhcpServer(in String ifName, in DhcpServingParamsParcel params,
+        in IDhcpServerCallbacks cb);
+    void makeNetworkMonitor(in Network network, String name, in INetworkMonitorCallbacks cb);
+    void makeIpClient(in String ifName, in IIpClientCallbacks callbacks);
+    void fetchIpMemoryStore(in IIpMemoryStoreCallbacks cb);
+}
diff --git a/common/networkstackclient/src/android/net/INetworkStackStatusCallback.aidl b/common/networkstackclient/src/android/net/INetworkStackStatusCallback.aidl
new file mode 100644
index 0000000..51032d8
--- /dev/null
+++ b/common/networkstackclient/src/android/net/INetworkStackStatusCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+/** @hide */
+oneway interface INetworkStackStatusCallback {
+    void onStatusAvailable(int statusCode);
+}
\ No newline at end of file
diff --git a/common/networkstackclient/src/android/net/InitialConfigurationParcelable.aidl b/common/networkstackclient/src/android/net/InitialConfigurationParcelable.aidl
new file mode 100644
index 0000000..3fa88c3
--- /dev/null
+++ b/common/networkstackclient/src/android/net/InitialConfigurationParcelable.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+
+parcelable InitialConfigurationParcelable {
+    LinkAddress[] ipAddresses;
+    IpPrefix[] directlyConnectedRoutes;
+    String[] dnsServers;
+    String gateway;
+}
\ No newline at end of file
diff --git a/common/networkstackclient/src/android/net/IpMemoryStoreClient.java b/common/networkstackclient/src/android/net/IpMemoryStoreClient.java
new file mode 100644
index 0000000..014b528
--- /dev/null
+++ b/common/networkstackclient/src/android/net/IpMemoryStoreClient.java
@@ -0,0 +1,227 @@
+/*
+ * 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.annotation.Nullable;
+import android.content.Context;
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.NetworkAttributes;
+import android.net.ipmemorystore.OnBlobRetrievedListener;
+import android.net.ipmemorystore.OnL2KeyResponseListener;
+import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener;
+import android.net.ipmemorystore.OnSameL3NetworkResponseListener;
+import android.net.ipmemorystore.OnStatusListener;
+import android.net.ipmemorystore.Status;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+
+/**
+ * service used to communicate with the ip memory store service in network stack,
+ * which is running in a separate module.
+ * @hide
+ */
+public abstract class IpMemoryStoreClient {
+    private static final String TAG = IpMemoryStoreClient.class.getSimpleName();
+    private final Context mContext;
+
+    public IpMemoryStoreClient(@NonNull final Context context) {
+        if (context == null) throw new IllegalArgumentException("missing context");
+        mContext = context;
+    }
+
+    protected abstract void runWhenServiceReady(Consumer<IIpMemoryStore> cb)
+            throws ExecutionException;
+
+    @FunctionalInterface
+    private interface ThrowingRunnable {
+        void run() throws RemoteException;
+    }
+
+    private void ignoringRemoteException(ThrowingRunnable r) {
+        ignoringRemoteException("Failed to execute remote procedure call", r);
+    }
+
+    private void ignoringRemoteException(String message, ThrowingRunnable r) {
+        try {
+            r.run();
+        } catch (RemoteException e) {
+            Log.e(TAG, message, e);
+        }
+    }
+
+    /**
+     * Store network attributes for a given L2 key.
+     * If L2Key is null, choose automatically from the attributes ; passing null is equivalent to
+     * calling findL2Key with the attributes and storing in the returned value.
+     *
+     * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2
+     *              key and only care about grouping can pass a unique ID here like the ones
+     *              generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low
+     *              relevance of such a network will lead to it being evicted soon if it's not
+     *              refreshed. Use findL2Key to try and find a similar L2Key to these attributes.
+     * @param attributes The attributes for this network.
+     * @param listener A listener that will be invoked to inform of the completion of this call,
+     *                 or null if the client is not interested in learning about success/failure.
+     * Through the listener, returns the L2 key. This is useful if the L2 key was not specified.
+     * If the call failed, the L2 key will be null.
+     */
+    public void storeNetworkAttributes(@NonNull final String l2Key,
+            @NonNull final NetworkAttributes attributes,
+            @Nullable final OnStatusListener listener) {
+        try {
+            runWhenServiceReady(service -> ignoringRemoteException(
+                    () -> service.storeNetworkAttributes(l2Key, attributes.toParcelable(),
+                            OnStatusListener.toAIDL(listener))));
+        } catch (ExecutionException m) {
+            ignoringRemoteException("Error storing network attributes",
+                    () -> listener.onComplete(new Status(Status.ERROR_UNKNOWN)));
+        }
+    }
+
+    /**
+     * Store a binary blob associated with an L2 key and a name.
+     *
+     * @param l2Key The L2 key for this network.
+     * @param clientId The ID of the client.
+     * @param name The name of this data.
+     * @param data The data to store.
+     * @param listener A listener to inform of the completion of this call, or null if the client
+     *        is not interested in learning about success/failure.
+     * Through the listener, returns a status to indicate success or failure.
+     */
+    public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId,
+            @NonNull final String name, @NonNull final Blob data,
+            @Nullable final OnStatusListener listener) {
+        try {
+            runWhenServiceReady(service -> ignoringRemoteException(
+                    () -> service.storeBlob(l2Key, clientId, name, data,
+                            OnStatusListener.toAIDL(listener))));
+        } catch (ExecutionException m) {
+            ignoringRemoteException("Error storing blob",
+                    () -> listener.onComplete(new Status(Status.ERROR_UNKNOWN)));
+        }
+    }
+
+    /**
+     * Returns the best L2 key associated with the attributes.
+     *
+     * This will find a record that would be in the same group as the passed attributes. This is
+     * useful to choose the key for storing a sample or private data when the L2 key is not known.
+     * If multiple records are group-close to these attributes, the closest match is returned.
+     * If multiple records have the same closeness, the one with the smaller (unicode codepoint
+     * order) L2 key is returned.
+     * If no record matches these attributes, null is returned.
+     *
+     * @param attributes The attributes of the network to find.
+     * @param listener The listener that will be invoked to return the answer.
+     * Through the listener, returns the L2 key if one matched, or null.
+     */
+    public void findL2Key(@NonNull final NetworkAttributes attributes,
+            @NonNull final OnL2KeyResponseListener listener) {
+        try {
+            runWhenServiceReady(service -> ignoringRemoteException(
+                    () -> service.findL2Key(attributes.toParcelable(),
+                            OnL2KeyResponseListener.toAIDL(listener))));
+        } catch (ExecutionException m) {
+            ignoringRemoteException("Error finding L2 Key",
+                    () -> listener.onL2KeyResponse(new Status(Status.ERROR_UNKNOWN), null));
+        }
+    }
+
+    /**
+     * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point
+     * to the same L3 network. Group-closeness is used to determine this.
+     *
+     * @param l2Key1 The key for the first network.
+     * @param l2Key2 The key for the second network.
+     * @param listener The listener that will be invoked to return the answer.
+     * Through the listener, a SameL3NetworkResponse containing the answer and confidence.
+     */
+    public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
+            @NonNull final OnSameL3NetworkResponseListener listener) {
+        try {
+            runWhenServiceReady(service -> ignoringRemoteException(
+                    () -> service.isSameNetwork(l2Key1, l2Key2,
+                            OnSameL3NetworkResponseListener.toAIDL(listener))));
+        } catch (ExecutionException m) {
+            ignoringRemoteException("Error checking for network sameness",
+                    () -> listener.onSameL3NetworkResponse(new Status(Status.ERROR_UNKNOWN), null));
+        }
+    }
+
+    /**
+     * Retrieve the network attributes for a key.
+     * If no record is present for this key, this will return null attributes.
+     *
+     * @param l2Key The key of the network to query.
+     * @param listener The listener that will be invoked to return the answer.
+     * Through the listener, returns the network attributes and the L2 key associated with
+     *         the query.
+     */
+    public void retrieveNetworkAttributes(@NonNull final String l2Key,
+            @NonNull final OnNetworkAttributesRetrievedListener listener) {
+        try {
+            runWhenServiceReady(service -> ignoringRemoteException(
+                    () -> service.retrieveNetworkAttributes(l2Key,
+                            OnNetworkAttributesRetrievedListener.toAIDL(listener))));
+        } catch (ExecutionException m) {
+            ignoringRemoteException("Error retrieving network attributes",
+                    () -> listener.onNetworkAttributesRetrieved(new Status(Status.ERROR_UNKNOWN),
+                            null, null));
+        }
+    }
+
+    /**
+     * Retrieve previously stored private data.
+     * If no data was stored for this L2 key and name this will return null.
+     *
+     * @param l2Key The L2 key.
+     * @param clientId The id of the client that stored this data.
+     * @param name The name of the data.
+     * @param listener The listener that will be invoked to return the answer.
+     * Through the listener, returns the private data (or null), with the L2 key
+     *         and the name of the data associated with the query.
+     */
+    public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId,
+            @NonNull final String name, @NonNull final OnBlobRetrievedListener listener) {
+        try {
+            runWhenServiceReady(service -> ignoringRemoteException(
+                    () -> service.retrieveBlob(l2Key, clientId, name,
+                            OnBlobRetrievedListener.toAIDL(listener))));
+        } catch (ExecutionException m) {
+            ignoringRemoteException("Error retrieving blob",
+                    () -> listener.onBlobRetrieved(new Status(Status.ERROR_UNKNOWN),
+                            null, null, null));
+        }
+    }
+
+    /**
+     * Wipe the data in the database upon network factory reset.
+     */
+    public void factoryReset() {
+        try {
+            runWhenServiceReady(service -> ignoringRemoteException(
+                    () -> service.factoryReset()));
+        } catch (ExecutionException m) {
+            Log.e(TAG, "Error executing factory reset", m);
+        }
+    }
+}
diff --git a/common/networkstackclient/src/android/net/NattKeepalivePacketDataParcelable.aidl b/common/networkstackclient/src/android/net/NattKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..6f006d4
--- /dev/null
+++ b/common/networkstackclient/src/android/net/NattKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+parcelable NattKeepalivePacketDataParcelable {
+    byte[] srcAddress;
+    int srcPort;
+    byte[] dstAddress;
+    int dstPort;
+}
+
diff --git a/common/networkstackclient/src/android/net/PrivateDnsConfigParcel.aidl b/common/networkstackclient/src/android/net/PrivateDnsConfigParcel.aidl
new file mode 100644
index 0000000..b52fce6
--- /dev/null
+++ b/common/networkstackclient/src/android/net/PrivateDnsConfigParcel.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+parcelable PrivateDnsConfigParcel {
+    String hostname;
+    String[] ips;
+}
diff --git a/common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl b/common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl
new file mode 100644
index 0000000..99606fb
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl
@@ -0,0 +1,38 @@
+/*
+**
+** 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.net.InitialConfigurationParcelable;
+import android.net.Network;
+import android.net.StaticIpConfiguration;
+import android.net.apf.ApfCapabilities;
+
+parcelable ProvisioningConfigurationParcelable {
+    boolean enableIPv4;
+    boolean enableIPv6;
+    boolean usingMultinetworkPolicyTracker;
+    boolean usingIpReachabilityMonitor;
+    int requestedPreDhcpActionMs;
+    InitialConfigurationParcelable initialConfig;
+    StaticIpConfiguration staticIpConfig;
+    ApfCapabilities apfCapabilities;
+    int provisioningTimeoutMs;
+    int ipv6AddrGenMode;
+    Network network;
+    String displayName;
+}
diff --git a/common/networkstackclient/src/android/net/TcpKeepalivePacketDataParcelable.aidl b/common/networkstackclient/src/android/net/TcpKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..e25168d
--- /dev/null
+++ b/common/networkstackclient/src/android/net/TcpKeepalivePacketDataParcelable.aidl
@@ -0,0 +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.
+ */
+
+package android.net;
+
+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/src/android/net/dhcp/DhcpServingParamsParcel.aidl b/common/networkstackclient/src/android/net/dhcp/DhcpServingParamsParcel.aidl
new file mode 100644
index 0000000..7b8b9ee
--- /dev/null
+++ b/common/networkstackclient/src/android/net/dhcp/DhcpServingParamsParcel.aidl
@@ -0,0 +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.
+ */
+
+package android.net.dhcp;
+
+parcelable DhcpServingParamsParcel {
+    int serverAddr;
+    int serverAddrPrefixLength;
+    int[] defaultRouters;
+    int[] dnsServers;
+    int[] excludedAddrs;
+    long dhcpLeaseTimeSecs;
+    int linkMtu;
+    boolean metered;
+}
+
diff --git a/common/networkstackclient/src/android/net/dhcp/IDhcpServer.aidl b/common/networkstackclient/src/android/net/dhcp/IDhcpServer.aidl
new file mode 100644
index 0000000..559433b
--- /dev/null
+++ b/common/networkstackclient/src/android/net/dhcp/IDhcpServer.aidl
@@ -0,0 +1,32 @@
+/**
+ * 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.
+ */
+
+package android.net.dhcp;
+
+import android.net.INetworkStackStatusCallback;
+import android.net.dhcp.DhcpServingParamsParcel;
+
+/** @hide */
+oneway interface IDhcpServer {
+    const int STATUS_UNKNOWN = 0;
+    const int STATUS_SUCCESS = 1;
+    const int STATUS_INVALID_ARGUMENT = 2;
+    const int STATUS_UNKNOWN_ERROR = 3;
+
+    void start(in INetworkStackStatusCallback cb);
+    void updateParams(in DhcpServingParamsParcel params, in INetworkStackStatusCallback cb);
+    void stop(in INetworkStackStatusCallback cb);
+}
diff --git a/common/networkstackclient/src/android/net/dhcp/IDhcpServerCallbacks.aidl b/common/networkstackclient/src/android/net/dhcp/IDhcpServerCallbacks.aidl
new file mode 100644
index 0000000..7ab4dcd
--- /dev/null
+++ b/common/networkstackclient/src/android/net/dhcp/IDhcpServerCallbacks.aidl
@@ -0,0 +1,24 @@
+/**
+ * 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.
+ */
+
+package android.net.dhcp;
+
+import android.net.dhcp.IDhcpServer;
+
+/** @hide */
+oneway interface IDhcpServerCallbacks {
+    void onDhcpServerCreated(int statusCode, in IDhcpServer server);
+}
diff --git a/common/networkstackclient/src/android/net/ip/IIpClient.aidl b/common/networkstackclient/src/android/net/ip/IIpClient.aidl
new file mode 100644
index 0000000..9989c52
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ip/IIpClient.aidl
@@ -0,0 +1,38 @@
+/**
+ * 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.
+ */
+package android.net.ip;
+
+import android.net.ProxyInfo;
+import android.net.ProvisioningConfigurationParcelable;
+import android.net.NattKeepalivePacketDataParcelable;
+import android.net.TcpKeepalivePacketDataParcelable;
+
+/** @hide */
+oneway interface IIpClient {
+    void completedPreDhcpAction();
+    void confirmConfiguration();
+    void readPacketFilterComplete(in byte[] data);
+    void shutdown();
+    void startProvisioning(in ProvisioningConfigurationParcelable req);
+    void stop();
+    void setTcpBufferSizes(in String tcpBufferSizes);
+    void setHttpProxy(in ProxyInfo proxyInfo);
+    void setMulticastFilter(boolean enabled);
+    void addKeepalivePacketFilter(int slot, in TcpKeepalivePacketDataParcelable pkt);
+    void removeKeepalivePacketFilter(int slot);
+    void setL2KeyAndGroupHint(in String l2Key, in String groupHint);
+    void addNattKeepalivePacketFilter(int slot, in NattKeepalivePacketDataParcelable pkt);
+}
diff --git a/common/networkstackclient/src/android/net/ip/IIpClientCallbacks.aidl b/common/networkstackclient/src/android/net/ip/IIpClientCallbacks.aidl
new file mode 100644
index 0000000..3681416
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ip/IIpClientCallbacks.aidl
@@ -0,0 +1,66 @@
+/**
+ * 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.
+ */
+package android.net.ip;
+
+import android.net.LinkProperties;
+import android.net.ip.IIpClient;
+import android.net.DhcpResultsParcelable;
+
+/** @hide */
+oneway interface IIpClientCallbacks {
+    void onIpClientCreated(in IIpClient ipClient);
+
+    void onPreDhcpAction();
+    void 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.
+    void onNewDhcpResults(in DhcpResultsParcelable dhcpResults);
+
+    void onProvisioningSuccess(in LinkProperties newLp);
+    void onProvisioningFailure(in LinkProperties newLp);
+
+    // Invoked on LinkProperties changes.
+    void onLinkPropertiesChange(in LinkProperties newLp);
+
+    // Called when the internal IpReachabilityMonitor (if enabled) has
+    // detected the loss of a critical number of required neighbors.
+    void onReachabilityLost(in String logMsg);
+
+    // Called when the IpClient state machine terminates.
+    void onQuit();
+
+    // Install an APF program to filter incoming packets.
+    void installPacketFilter(in byte[] 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.
+    void startReadPacketFilter();
+
+    // If multicast filtering cannot be accomplished with APF, this function will be called to
+    // actuate multicast filtering using another means.
+    void setFallbackMulticastFilter(boolean enabled);
+
+    // Enabled/disable Neighbor Discover offload functionality. This is
+    // called, for example, whenever 464xlat is being started or stopped.
+    void setNeighborDiscoveryOffload(boolean enable);
+}
\ No newline at end of file
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/Blob.aidl b/common/networkstackclient/src/android/net/ipmemorystore/Blob.aidl
new file mode 100644
index 0000000..9dbef11
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/Blob.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.ipmemorystore;
+
+/**
+ * A blob of data opaque to the memory store. The client mutates this at its own risk,
+ * and it is strongly suggested to never do it at all and treat this as immutable.
+ * {@hide}
+ */
+parcelable Blob {
+    byte[] data;
+}
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl b/common/networkstackclient/src/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
new file mode 100644
index 0000000..4926feb
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
@@ -0,0 +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.
+ */
+
+package android.net.ipmemorystore;
+
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnBlobRetrievedListener {
+    /**
+     * Private data was retrieved for the L2 key and name specified.
+     * Note this does not return the client ID, as clients are expected to only ever use one ID.
+     */
+     void onBlobRetrieved(in StatusParcelable status, in String l2Key, in String name,
+             in Blob data);
+}
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl b/common/networkstackclient/src/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
new file mode 100644
index 0000000..dea0cc4
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.ipmemorystore;
+
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnL2KeyResponseListener {
+    /**
+     * The operation completed with the specified L2 key.
+     */
+     void onL2KeyResponse(in StatusParcelable status, in String l2Key);
+}
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl b/common/networkstackclient/src/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
new file mode 100644
index 0000000..870e217
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
@@ -0,0 +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.
+ */
+
+package android.net.ipmemorystore;
+
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnNetworkAttributesRetrievedListener {
+    /**
+     * Network attributes were fetched for the specified L2 key. While the L2 key will never
+     * be null, the attributes may be if no data is stored about this L2 key.
+     */
+     void onNetworkAttributesRetrieved(in StatusParcelable status, in String l2Key,
+             in NetworkAttributesParcelable attributes);
+}
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl b/common/networkstackclient/src/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
new file mode 100644
index 0000000..b8ccfb9
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.ipmemorystore;
+
+import android.net.ipmemorystore.SameL3NetworkResponseParcelable;
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnSameL3NetworkResponseListener {
+    /**
+     * The memory store has come up with the answer to a query that was sent.
+     */
+     void onSameL3NetworkResponse(in StatusParcelable status,
+             in SameL3NetworkResponseParcelable response);
+}
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/IOnStatusListener.aidl b/common/networkstackclient/src/android/net/ipmemorystore/IOnStatusListener.aidl
new file mode 100644
index 0000000..5d07504
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/IOnStatusListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.ipmemorystore;
+
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnStatusListener {
+    /**
+     * The operation has completed with the specified status.
+     */
+     void onComplete(in StatusParcelable status);
+}
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/NetworkAttributes.java b/common/networkstackclient/src/android/net/ipmemorystore/NetworkAttributes.java
new file mode 100644
index 0000000..818515a
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/NetworkAttributes.java
@@ -0,0 +1,371 @@
+/*
+ * 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.ipmemorystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+/**
+ * A POD object to represent attributes of a single L2 network entry.
+ * @hide
+ */
+public class NetworkAttributes {
+    private static final boolean DBG = true;
+
+    // Weight cutoff for grouping. To group, a similarity score is computed with the following
+    // algorithm : if both fields are non-null and equals() then add their assigned weight, else if
+    // both are null then add a portion of their assigned weight (see NULL_MATCH_WEIGHT),
+    // otherwise add nothing.
+    // As a guideline, this should be something like 60~75% of the total weights in this class. The
+    // design states "in essence a reader should imagine that if two important columns don't match,
+    // or one important and several unimportant columns don't match then the two records are
+    // considered a different group".
+    private static final float TOTAL_WEIGHT_CUTOFF = 520.0f;
+    // The portion of the weight that is earned when scoring group-sameness by having both columns
+    // being null. This is because some networks rightfully don't have some attributes (e.g. a
+    // V6-only network won't have an assigned V4 address) and both being null should count for
+    // something, but attributes may also be null just because data is unavailable.
+    private static final float NULL_MATCH_WEIGHT = 0.25f;
+
+    // The v4 address that was assigned to this device the last time it joined this network.
+    // This typically comes from DHCP but could be something else like static configuration.
+    // This does not apply to IPv6.
+    // TODO : add a list of v6 prefixes for the v6 case.
+    @Nullable
+    public final Inet4Address assignedV4Address;
+    private static final float WEIGHT_ASSIGNEDV4ADDR = 300.0f;
+
+    // The lease expiry timestamp of v4 address allocated from DHCP server, in milliseconds.
+    @Nullable
+    public final Long assignedV4AddressExpiry;
+    // lease expiry doesn't imply any correlation between "the same lease expiry value" and "the
+    // same L3 network".
+    private static final float WEIGHT_ASSIGNEDV4ADDREXPIRY = 0.0f;
+
+    // Optionally supplied by the client if it has an opinion on L3 network. For example, this
+    // could be a hash of the SSID + security type on WiFi.
+    @Nullable
+    public final String groupHint;
+    private static final float WEIGHT_GROUPHINT = 300.0f;
+
+    // The list of DNS server addresses.
+    @Nullable
+    public final List<InetAddress> dnsAddresses;
+    private static final float WEIGHT_DNSADDRESSES = 200.0f;
+
+    // The mtu on this network.
+    @Nullable
+    public final Integer mtu;
+    private static final float WEIGHT_MTU = 50.0f;
+
+    // The sum of all weights in this class. Tests ensure that this stays equal to the total of
+    // all weights.
+    /** @hide */
+    @VisibleForTesting
+    public static final float TOTAL_WEIGHT = WEIGHT_ASSIGNEDV4ADDR
+            + WEIGHT_ASSIGNEDV4ADDREXPIRY
+            + WEIGHT_GROUPHINT
+            + WEIGHT_DNSADDRESSES
+            + WEIGHT_MTU;
+
+    /** @hide */
+    @VisibleForTesting
+    public NetworkAttributes(
+            @Nullable final Inet4Address assignedV4Address,
+            @Nullable final Long assignedV4AddressExpiry,
+            @Nullable final String groupHint,
+            @Nullable final List<InetAddress> dnsAddresses,
+            @Nullable final Integer mtu) {
+        if (mtu != null && mtu < 0) throw new IllegalArgumentException("MTU can't be negative");
+        if (assignedV4AddressExpiry != null && assignedV4AddressExpiry <= 0) {
+            throw new IllegalArgumentException("lease expiry can't be negative or zero");
+        }
+        this.assignedV4Address = assignedV4Address;
+        this.assignedV4AddressExpiry = assignedV4AddressExpiry;
+        this.groupHint = groupHint;
+        this.dnsAddresses = null == dnsAddresses ? null :
+                Collections.unmodifiableList(new ArrayList<>(dnsAddresses));
+        this.mtu = mtu;
+    }
+
+    @VisibleForTesting
+    public NetworkAttributes(@NonNull final NetworkAttributesParcelable parcelable) {
+        // The call to the other constructor must be the first statement of this constructor,
+        // so everything has to be inline
+        this((Inet4Address) getByAddressOrNull(parcelable.assignedV4Address),
+                parcelable.assignedV4AddressExpiry > 0
+                        ? parcelable.assignedV4AddressExpiry : null,
+                parcelable.groupHint,
+                blobArrayToInetAddressList(parcelable.dnsAddresses),
+                parcelable.mtu >= 0 ? parcelable.mtu : null);
+    }
+
+    @Nullable
+    private static InetAddress getByAddressOrNull(@Nullable final byte[] address) {
+        if (null == address) return null;
+        try {
+            return InetAddress.getByAddress(address);
+        } catch (UnknownHostException e) {
+            return null;
+        }
+    }
+
+    @Nullable
+    private static List<InetAddress> blobArrayToInetAddressList(@Nullable final Blob[] blobs) {
+        if (null == blobs) return null;
+        final ArrayList<InetAddress> list = new ArrayList<>(blobs.length);
+        for (final Blob b : blobs) {
+            final InetAddress addr = getByAddressOrNull(b.data);
+            if (null != addr) list.add(addr);
+        }
+        return list;
+    }
+
+    @Nullable
+    private static Blob[] inetAddressListToBlobArray(@Nullable final List<InetAddress> addresses) {
+        if (null == addresses) return null;
+        final ArrayList<Blob> blobs = new ArrayList<>();
+        for (int i = 0; i < addresses.size(); ++i) {
+            final InetAddress addr = addresses.get(i);
+            if (null == addr) continue;
+            final Blob b = new Blob();
+            b.data = addr.getAddress();
+            blobs.add(b);
+        }
+        return blobs.toArray(new Blob[0]);
+    }
+
+    /** Converts this NetworkAttributes to a parcelable object */
+    @NonNull
+    public NetworkAttributesParcelable toParcelable() {
+        final NetworkAttributesParcelable parcelable = new NetworkAttributesParcelable();
+        parcelable.assignedV4Address =
+                (null == assignedV4Address) ? null : assignedV4Address.getAddress();
+        parcelable.assignedV4AddressExpiry =
+                (null == assignedV4AddressExpiry) ? 0 : assignedV4AddressExpiry;
+        parcelable.groupHint = groupHint;
+        parcelable.dnsAddresses = inetAddressListToBlobArray(dnsAddresses);
+        parcelable.mtu = (null == mtu) ? -1 : mtu;
+        return parcelable;
+    }
+
+    private float samenessContribution(final float weight,
+            @Nullable final Object o1, @Nullable final Object o2) {
+        if (null == o1) {
+            return (null == o2) ? weight * NULL_MATCH_WEIGHT : 0f;
+        }
+        return Objects.equals(o1, o2) ? weight : 0f;
+    }
+
+    /** @hide */
+    public float getNetworkGroupSamenessConfidence(@NonNull final NetworkAttributes o) {
+        final float samenessScore =
+                samenessContribution(WEIGHT_ASSIGNEDV4ADDR, assignedV4Address, o.assignedV4Address)
+                + samenessContribution(WEIGHT_ASSIGNEDV4ADDREXPIRY, assignedV4AddressExpiry,
+                      o.assignedV4AddressExpiry)
+                + samenessContribution(WEIGHT_GROUPHINT, groupHint, o.groupHint)
+                + samenessContribution(WEIGHT_DNSADDRESSES, dnsAddresses, o.dnsAddresses)
+                + samenessContribution(WEIGHT_MTU, mtu, o.mtu);
+        // The minimum is 0, the max is TOTAL_WEIGHT and should be represented by 1.0, and
+        // TOTAL_WEIGHT_CUTOFF should represent 0.5, but there is no requirement that
+        // TOTAL_WEIGHT_CUTOFF would be half of TOTAL_WEIGHT (indeed, it should not be).
+        // So scale scores under the cutoff between 0 and 0.5, and the scores over the cutoff
+        // between 0.5 and 1.0.
+        if (samenessScore < TOTAL_WEIGHT_CUTOFF) {
+            return samenessScore / (TOTAL_WEIGHT_CUTOFF * 2);
+        } else {
+            return (samenessScore - TOTAL_WEIGHT_CUTOFF) / (TOTAL_WEIGHT - TOTAL_WEIGHT_CUTOFF) / 2
+                    + 0.5f;
+        }
+    }
+
+    /** @hide */
+    public static class Builder {
+        @Nullable
+        private Inet4Address mAssignedAddress;
+        @Nullable
+        private Long mAssignedAddressExpiry;
+        @Nullable
+        private String mGroupHint;
+        @Nullable
+        private List<InetAddress> mDnsAddresses;
+        @Nullable
+        private Integer mMtu;
+
+        /**
+         * Set the assigned address.
+         * @param assignedV4Address The assigned address.
+         * @return This builder.
+         */
+        public Builder setAssignedV4Address(@Nullable final Inet4Address assignedV4Address) {
+            mAssignedAddress = assignedV4Address;
+            return this;
+        }
+
+        /**
+         * Set the lease expiry timestamp of assigned v4 address. Long.MAX_VALUE is used
+         * to represent "infinite lease".
+         *
+         * @param assignedV4AddressExpiry The lease expiry timestamp of assigned v4 address.
+         * @return This builder.
+         */
+        public Builder setAssignedV4AddressExpiry(
+                @Nullable final Long assignedV4AddressExpiry) {
+            if (null != assignedV4AddressExpiry && assignedV4AddressExpiry <= 0) {
+                throw new IllegalArgumentException("lease expiry can't be negative or zero");
+            }
+            mAssignedAddressExpiry = assignedV4AddressExpiry;
+            return this;
+        }
+
+        /**
+         * Set the group hint.
+         * @param groupHint The group hint.
+         * @return This builder.
+         */
+        public Builder setGroupHint(@Nullable final String groupHint) {
+            mGroupHint = groupHint;
+            return this;
+        }
+
+        /**
+         * Set the DNS addresses.
+         * @param dnsAddresses The DNS addresses.
+         * @return This builder.
+         */
+        public Builder setDnsAddresses(@Nullable final List<InetAddress> dnsAddresses) {
+            if (DBG && null != dnsAddresses) {
+                // Parceling code crashes if one of the addresses is null, therefore validate
+                // them when running in debug.
+                for (final InetAddress address : dnsAddresses) {
+                    if (null == address) throw new IllegalArgumentException("Null DNS address");
+                }
+            }
+            this.mDnsAddresses = dnsAddresses;
+            return this;
+        }
+
+        /**
+         * Set the MTU.
+         * @param mtu The MTU.
+         * @return This builder.
+         */
+        public Builder setMtu(@Nullable final Integer mtu) {
+            if (null != mtu && mtu < 0) throw new IllegalArgumentException("MTU can't be negative");
+            mMtu = mtu;
+            return this;
+        }
+
+        /**
+         * Return the built NetworkAttributes object.
+         * @return The built NetworkAttributes object.
+         */
+        public NetworkAttributes build() {
+            return new NetworkAttributes(mAssignedAddress, mAssignedAddressExpiry,
+                  mGroupHint, mDnsAddresses, mMtu);
+        }
+    }
+
+    /** @hide */
+    public boolean isEmpty() {
+        return (null == assignedV4Address) && (null == assignedV4AddressExpiry)
+                && (null == groupHint) && (null == dnsAddresses) && (null == mtu);
+    }
+
+    @Override
+    public boolean equals(@Nullable final Object o) {
+        if (!(o instanceof NetworkAttributes)) return false;
+        final NetworkAttributes other = (NetworkAttributes) o;
+        return Objects.equals(assignedV4Address, other.assignedV4Address)
+                && Objects.equals(assignedV4AddressExpiry, other.assignedV4AddressExpiry)
+                && Objects.equals(groupHint, other.groupHint)
+                && Objects.equals(dnsAddresses, other.dnsAddresses)
+                && Objects.equals(mtu, other.mtu);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(assignedV4Address, assignedV4AddressExpiry,
+                groupHint, dnsAddresses, mtu);
+    }
+
+    /** Pretty print */
+    @Override
+    public String toString() {
+        final StringJoiner resultJoiner = new StringJoiner(" ", "{", "}");
+        final ArrayList<String> nullFields = new ArrayList<>();
+
+        if (null != assignedV4Address) {
+            resultJoiner.add("assignedV4Addr :");
+            resultJoiner.add(assignedV4Address.toString());
+        } else {
+            nullFields.add("assignedV4Addr");
+        }
+
+        if (null != assignedV4AddressExpiry) {
+            resultJoiner.add("assignedV4AddressExpiry :");
+            resultJoiner.add(assignedV4AddressExpiry.toString());
+        } else {
+            nullFields.add("assignedV4AddressExpiry");
+        }
+
+        if (null != groupHint) {
+            resultJoiner.add("groupHint :");
+            resultJoiner.add(groupHint);
+        } else {
+            nullFields.add("groupHint");
+        }
+
+        if (null != dnsAddresses) {
+            resultJoiner.add("dnsAddr : [");
+            for (final InetAddress addr : dnsAddresses) {
+                resultJoiner.add(addr.getHostAddress());
+            }
+            resultJoiner.add("]");
+        } else {
+            nullFields.add("dnsAddr");
+        }
+
+        if (null != mtu) {
+            resultJoiner.add("mtu :");
+            resultJoiner.add(mtu.toString());
+        } else {
+            nullFields.add("mtu");
+        }
+
+        if (!nullFields.isEmpty()) {
+            resultJoiner.add("; Null fields : [");
+            for (final String field : nullFields) {
+                resultJoiner.add(field);
+            }
+            resultJoiner.add("]");
+        }
+
+        return resultJoiner.toString();
+    }
+}
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/common/networkstackclient/src/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
new file mode 100644
index 0000000..997eb2b
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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.ipmemorystore;
+
+// Blob[] is used to represent an array of byte[], as structured AIDL does not support arrays
+// of arrays.
+import android.net.ipmemorystore.Blob;
+
+/**
+ * An object to represent attributes of a single L2 network entry.
+ * See NetworkAttributes.java for a description of each field. The types used in this class
+ * are structured parcelable types instead of the richer types of the NetworkAttributes object,
+ * but they have the same purpose. The NetworkAttributes.java file also contains the code
+ * to convert the richer types to the parcelable types and back.
+ * @hide
+ */
+parcelable NetworkAttributesParcelable {
+    byte[] assignedV4Address;
+    long assignedV4AddressExpiry;
+    String groupHint;
+    Blob[] dnsAddresses;
+    int mtu;
+}
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/OnBlobRetrievedListener.java b/common/networkstackclient/src/android/net/ipmemorystore/OnBlobRetrievedListener.java
new file mode 100644
index 0000000..a17483a
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/OnBlobRetrievedListener.java
@@ -0,0 +1,50 @@
+/*
+ * 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.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/**
+ * A listener for the IpMemoryStore to return a blob.
+ * @hide
+ */
+public interface OnBlobRetrievedListener {
+    /**
+     * The memory store has come up with the answer to a query that was sent.
+     */
+    void onBlobRetrieved(Status status, String l2Key, String name, Blob blob);
+
+    /** Converts this OnBlobRetrievedListener to a parcelable object */
+    @NonNull
+    static IOnBlobRetrievedListener toAIDL(@NonNull final OnBlobRetrievedListener listener) {
+        return new IOnBlobRetrievedListener.Stub() {
+            @Override
+            public void onBlobRetrieved(final StatusParcelable statusParcelable, final String l2Key,
+                    final String name, final Blob blob) {
+                // NonNull, but still don't crash the system server if null
+                if (null != listener) {
+                    listener.onBlobRetrieved(new Status(statusParcelable), l2Key, name, blob);
+                }
+            }
+
+            @Override
+            public int getInterfaceVersion() {
+                return this.VERSION;
+            }
+        };
+    }
+}
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/OnL2KeyResponseListener.java b/common/networkstackclient/src/android/net/ipmemorystore/OnL2KeyResponseListener.java
new file mode 100644
index 0000000..e608aec
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/OnL2KeyResponseListener.java
@@ -0,0 +1,50 @@
+/*
+ * 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.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/**
+ * A listener for the IpMemoryStore to return a L2 key.
+ * @hide
+ */
+public interface OnL2KeyResponseListener {
+    /**
+     * The operation has completed with the specified status.
+     */
+    void onL2KeyResponse(Status status, String l2Key);
+
+    /** Converts this OnL2KeyResponseListener to a parcelable object */
+    @NonNull
+    static IOnL2KeyResponseListener toAIDL(@NonNull final OnL2KeyResponseListener listener) {
+        return new IOnL2KeyResponseListener.Stub() {
+            @Override
+            public void onL2KeyResponse(final StatusParcelable statusParcelable,
+                    final String l2Key) {
+                // NonNull, but still don't crash the system server if null
+                if (null != listener) {
+                    listener.onL2KeyResponse(new Status(statusParcelable), l2Key);
+                }
+            }
+
+            @Override
+            public int getInterfaceVersion() {
+                return this.VERSION;
+            }
+        };
+    }
+}
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/OnNetworkAttributesRetrievedListener.java b/common/networkstackclient/src/android/net/ipmemorystore/OnNetworkAttributesRetrievedListener.java
new file mode 100644
index 0000000..395ad98
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/OnNetworkAttributesRetrievedListener.java
@@ -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.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/**
+ * A listener for the IpMemoryStore to return network attributes.
+ * @hide
+ */
+public interface OnNetworkAttributesRetrievedListener {
+    /**
+     * The memory store has come up with the answer to a query that was sent.
+     */
+    void onNetworkAttributesRetrieved(Status status, String l2Key, NetworkAttributes attributes);
+
+    /** Converts this OnNetworkAttributesRetrievedListener to a parcelable object */
+    @NonNull
+    static IOnNetworkAttributesRetrievedListener toAIDL(
+            @NonNull final OnNetworkAttributesRetrievedListener listener) {
+        return new IOnNetworkAttributesRetrievedListener.Stub() {
+            @Override
+            public void onNetworkAttributesRetrieved(final StatusParcelable statusParcelable,
+                    final String l2Key,
+                    final NetworkAttributesParcelable networkAttributesParcelable) {
+                // NonNull, but still don't crash the system server if null
+                if (null != listener) {
+                    listener.onNetworkAttributesRetrieved(
+                            new Status(statusParcelable), l2Key, null == networkAttributesParcelable
+                                ? null : new NetworkAttributes(networkAttributesParcelable));
+                }
+            }
+
+            @Override
+            public int getInterfaceVersion() {
+                return this.VERSION;
+            }
+        };
+    }
+}
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/OnSameL3NetworkResponseListener.java b/common/networkstackclient/src/android/net/ipmemorystore/OnSameL3NetworkResponseListener.java
new file mode 100644
index 0000000..67f8da8
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/OnSameL3NetworkResponseListener.java
@@ -0,0 +1,53 @@
+/*
+ * 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.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/**
+ * A listener for the IpMemoryStore to return a response about network sameness.
+ * @hide
+ */
+public interface OnSameL3NetworkResponseListener {
+    /**
+     * The memory store has come up with the answer to a query that was sent.
+     */
+    void onSameL3NetworkResponse(Status status, SameL3NetworkResponse response);
+
+    /** Converts this OnSameL3NetworkResponseListener to a parcelable object */
+    @NonNull
+    static IOnSameL3NetworkResponseListener toAIDL(
+            @NonNull final OnSameL3NetworkResponseListener listener) {
+        return new IOnSameL3NetworkResponseListener.Stub() {
+            @Override
+            public void onSameL3NetworkResponse(final StatusParcelable statusParcelable,
+                    final SameL3NetworkResponseParcelable sameL3NetworkResponseParcelable) {
+                // NonNull, but still don't crash the system server if null
+                if (null != listener) {
+                    listener.onSameL3NetworkResponse(
+                            new Status(statusParcelable),
+                            new SameL3NetworkResponse(sameL3NetworkResponseParcelable));
+                }
+            }
+
+            @Override
+            public int getInterfaceVersion() {
+                return this.VERSION;
+            }
+        };
+    }
+}
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/OnStatusListener.java b/common/networkstackclient/src/android/net/ipmemorystore/OnStatusListener.java
new file mode 100644
index 0000000..4262efd
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/OnStatusListener.java
@@ -0,0 +1,49 @@
+/*
+ * 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.ipmemorystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * A listener for the IpMemoryStore to return a status to a client.
+ * @hide
+ */
+public interface OnStatusListener {
+    /**
+     * The operation has completed with the specified status.
+     */
+    void onComplete(Status status);
+
+    /** Converts this OnStatusListener to a parcelable object */
+    @NonNull
+    static IOnStatusListener toAIDL(@Nullable final OnStatusListener listener) {
+        return new IOnStatusListener.Stub() {
+            @Override
+            public void onComplete(final StatusParcelable statusParcelable) {
+                if (null != listener) {
+                    listener.onComplete(new Status(statusParcelable));
+                }
+            }
+
+            @Override
+            public int getInterfaceVersion() {
+                return this.VERSION;
+            }
+        };
+    }
+}
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/SameL3NetworkResponse.java b/common/networkstackclient/src/android/net/ipmemorystore/SameL3NetworkResponse.java
new file mode 100644
index 0000000..291aca8
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/SameL3NetworkResponse.java
@@ -0,0 +1,147 @@
+/*
+ * 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.ipmemorystore;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * An object representing the answer to a query whether two given L2 networks represent the
+ * same L3 network. Parcels as a SameL3NetworkResponseParceled object.
+ * @hide
+ */
+public class SameL3NetworkResponse {
+    @IntDef(prefix = "NETWORK_",
+            value = {NETWORK_SAME, NETWORK_DIFFERENT, NETWORK_NEVER_CONNECTED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface NetworkSameness {}
+
+    /**
+     * Both L2 networks represent the same L3 network.
+     */
+    public static final int NETWORK_SAME = 1;
+
+    /**
+     * The two L2 networks represent a different L3 network.
+     */
+    public static final int NETWORK_DIFFERENT = 2;
+
+    /**
+     * The device has never connected to at least one of these two L2 networks, or data
+     * has been wiped. Therefore the device has never seen the L3 network behind at least
+     * one of these two L2 networks, and can't evaluate whether it's the same as the other.
+     */
+    public static final int NETWORK_NEVER_CONNECTED = 3;
+
+    /**
+     * The first L2 key specified in the query.
+     */
+    @NonNull
+    public final String l2Key1;
+
+    /**
+     * The second L2 key specified in the query.
+     */
+    @NonNull
+    public final String l2Key2;
+
+    /**
+     * A confidence value indicating whether the two L2 networks represent the same L3 network.
+     *
+     * If both L2 networks were known, this value will be between 0.0 and 1.0, with 0.0
+     * representing complete confidence that the given L2 networks represent a different
+     * L3 network, and 1.0 representing complete confidence that the given L2 networks
+     * represent the same L3 network.
+     * If at least one of the L2 networks was not known, this value will be outside of the
+     * 0.0~1.0 range.
+     *
+     * Most apps should not be interested in this, and are encouraged to use the collapsing
+     * {@link #getNetworkSameness()} function below.
+     */
+    public final float confidence;
+
+    /**
+     * @return whether the two L2 networks represent the same L3 network. Either
+     *     {@code NETWORK_SAME}, {@code NETWORK_DIFFERENT} or {@code NETWORK_NEVER_CONNECTED}.
+     */
+    @NetworkSameness
+    public final int getNetworkSameness() {
+        if (confidence > 1.0 || confidence < 0.0) return NETWORK_NEVER_CONNECTED;
+        return confidence > 0.5 ? NETWORK_SAME : NETWORK_DIFFERENT;
+    }
+
+    /** @hide */
+    public SameL3NetworkResponse(@NonNull final String l2Key1, @NonNull final String l2Key2,
+            final float confidence) {
+        this.l2Key1 = l2Key1;
+        this.l2Key2 = l2Key2;
+        this.confidence = confidence;
+    }
+
+    /** Builds a SameL3NetworkResponse from a parcelable object */
+    @VisibleForTesting
+    public SameL3NetworkResponse(@NonNull final SameL3NetworkResponseParcelable parceled) {
+        this(parceled.l2Key1, parceled.l2Key2, parceled.confidence);
+    }
+
+    /** Converts this SameL3NetworkResponse to a parcelable object */
+    @NonNull
+    public SameL3NetworkResponseParcelable toParcelable() {
+        final SameL3NetworkResponseParcelable parcelable = new SameL3NetworkResponseParcelable();
+        parcelable.l2Key1 = l2Key1;
+        parcelable.l2Key2 = l2Key2;
+        parcelable.confidence = confidence;
+        return parcelable;
+    }
+
+    // Note key1 and key2 have to match each other for this to return true. If
+    // key1 matches o.key2 and the other way around this returns false.
+    @Override
+    public boolean equals(@Nullable final Object o) {
+        if (!(o instanceof SameL3NetworkResponse)) return false;
+        final SameL3NetworkResponse other = (SameL3NetworkResponse) o;
+        return l2Key1.equals(other.l2Key1) && l2Key2.equals(other.l2Key2)
+                && confidence == other.confidence;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(l2Key1, l2Key2, confidence);
+    }
+
+    @Override
+    /** Pretty print */
+    public String toString() {
+        switch (getNetworkSameness()) {
+            case NETWORK_SAME:
+                return "\"" + l2Key1 + "\" same L3 network as \"" + l2Key2 + "\"";
+            case NETWORK_DIFFERENT:
+                return "\"" + l2Key1 + "\" different L3 network from \"" + l2Key2 + "\"";
+            case NETWORK_NEVER_CONNECTED:
+                return "\"" + l2Key1 + "\" can't be tested against \"" + l2Key2 + "\"";
+            default:
+                return "Buggy sameness value ? \"" + l2Key1 + "\", \"" + l2Key2 + "\"";
+        }
+    }
+}
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl b/common/networkstackclient/src/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
new file mode 100644
index 0000000..7196699
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.ipmemorystore;
+
+/** {@hide} */
+parcelable SameL3NetworkResponseParcelable {
+    String l2Key1;
+    String l2Key2;
+    float confidence;
+}
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/Status.java b/common/networkstackclient/src/android/net/ipmemorystore/Status.java
new file mode 100644
index 0000000..13242c0
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/Status.java
@@ -0,0 +1,74 @@
+/*
+ * 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.ipmemorystore;
+
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A parcelable status representing the result of an operation.
+ * Parcels as StatusParceled.
+ * @hide
+ */
+public class Status {
+    public static final int SUCCESS = 0;
+
+    public static final int ERROR_GENERIC = -1;
+    public static final int ERROR_ILLEGAL_ARGUMENT = -2;
+    public static final int ERROR_DATABASE_CANNOT_BE_OPENED = -3;
+    public static final int ERROR_STORAGE = -4;
+    public static final int ERROR_UNKNOWN = -5;
+
+    public final int resultCode;
+
+    public Status(final int resultCode) {
+        this.resultCode = resultCode;
+    }
+
+    @VisibleForTesting
+    public Status(@NonNull final StatusParcelable parcelable) {
+        this(parcelable.resultCode);
+    }
+
+    /** Converts this Status to a parcelable object */
+    @NonNull
+    public StatusParcelable toParcelable() {
+        final StatusParcelable parcelable = new StatusParcelable();
+        parcelable.resultCode = resultCode;
+        return parcelable;
+    }
+
+    public boolean isSuccess() {
+        return SUCCESS == resultCode;
+    }
+
+    /** Pretty print */
+    @Override
+    public String toString() {
+        switch (resultCode) {
+            case SUCCESS: return "SUCCESS";
+            case ERROR_GENERIC: return "GENERIC ERROR";
+            case ERROR_ILLEGAL_ARGUMENT: return "ILLEGAL ARGUMENT";
+            case ERROR_DATABASE_CANNOT_BE_OPENED: return "DATABASE CANNOT BE OPENED";
+            // "DB storage error" is not very helpful but SQLite does not provide specific error
+            // codes upon store failure. Thus this indicates SQLite returned some error upon store
+            case ERROR_STORAGE: return "DATABASE STORAGE ERROR";
+            default: return "Unknown value ?!";
+        }
+    }
+}
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/StatusParcelable.aidl b/common/networkstackclient/src/android/net/ipmemorystore/StatusParcelable.aidl
new file mode 100644
index 0000000..fb36ef4
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/StatusParcelable.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.ipmemorystore;
+
+/** {@hide} */
+parcelable StatusParcelable {
+  int resultCode;
+}
diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml
new file mode 100644
index 0000000..b9d5337
--- /dev/null
+++ b/res/values/overlayable.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <overlayable name="NetworkStackConfig">
+        <policy type="product|system|vendor">
+            <!-- Configuration values for NetworkMonitor -->
+            <item type="integer" name="config_captive_portal_dns_probe_timeout"/>
+            <item type="string" name="config_captive_portal_http_url"/>
+            <item type="string" name="config_captive_portal_https_url"/>
+            <item type="array" name="config_captive_portal_fallback_urls"/>
+            <!-- Configuration value for DhcpResults -->
+            <item type="array" name="config_default_dns_servers"/>
+        </policy>
+    </overlayable>
+</resources>
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index 6075f69..1d3421c 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -31,6 +31,7 @@
 import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
 import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
 import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
+import static com.android.server.util.NetworkStackConstants.IPV6_ADDR_LEN;
 
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
@@ -548,6 +549,9 @@
         // For debugging only. Offsets into the packet where RDNSS options are.
         private final ArrayList<Integer> mRdnssOptionOffsets = new ArrayList<>();
 
+        // For debugging only. Offsets into the packet where RIO options are.
+        private final ArrayList<Integer> mRioOptionOffsets = new ArrayList<>();
+
         // For debugging only. How many times this RA was seen.
         int seenCount = 0;
 
@@ -598,6 +602,28 @@
             for (int server = 0; server < numServers; server++) {
                 sb.append(" ").append(IPv6AddresstoString(offset + 8 + 16 * server));
             }
+            sb.append(" ");
+        }
+
+        private void rioOptionToString(StringBuffer sb, int offset) {
+            int optLen = getUint8(mPacket, offset + 1) * 8;
+            if (optLen < 8 || optLen > 24) return;  // Malformed or empty.
+            int prefixLen = getUint8(mPacket, offset + 2);
+            long lifetime = getUint32(mPacket, offset + 4);
+
+            // This read is variable length because the prefix can be 0, 8 or 16 bytes long.
+            // We can't use any of the ByteBuffer#get methods here because they all start reading
+            // from the buffer's current position.
+            byte[] prefix = new byte[IPV6_ADDR_LEN];
+            System.arraycopy(mPacket.array(), offset + 8, prefix, 0, optLen - 8);
+            sb.append("RIO ").append(lifetime).append("s ");
+            try {
+                InetAddress address = (Inet6Address) InetAddress.getByAddress(prefix);
+                sb.append(address.getHostAddress());
+            } catch (UnknownHostException impossible) {
+                sb.append("???");
+            }
+            sb.append("/").append(prefixLen).append(" ");
         }
 
         public String toString() {
@@ -613,6 +639,9 @@
                 for (int i: mRdnssOptionOffsets) {
                     rdnssOptionToString(sb, i);
                 }
+                for (int i: mRioOptionOffsets) {
+                    rioOptionToString(sb, i);
+                }
                 return sb.toString();
             } catch (BufferUnderflowException|IndexOutOfBoundsException e) {
                 return "<Malformed RA>";
@@ -649,7 +678,7 @@
         // specifications.
         Ra(byte[] packet, int length) throws InvalidRaException {
             if (length < ICMP6_RA_OPTION_OFFSET) {
-                throw new InvalidRaException("Not an ICMP6 router advertisement");
+                throw new InvalidRaException("Not an ICMP6 router advertisement: too short");
             }
 
             mPacket = ByteBuffer.wrap(Arrays.copyOf(packet, length));
@@ -716,6 +745,7 @@
                         builder.updateRdnssLifetime(lifetime);
                         break;
                     case ICMP6_ROUTE_INFO_OPTION_TYPE:
+                        mRioOptionOffsets.add(position);
                         lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart);
                         lifetime = getUint32(mPacket, position + ICMP6_4_BYTE_LIFETIME_OFFSET);
                         builder.updateRouteInfoLifetime(lifetime);
@@ -1559,7 +1589,11 @@
             for (Ra ra : mRas) {
                 ra.generateFilterLocked(gen);
                 // Stop if we get too big.
-                if (gen.programLengthOverEstimate() > maximumApfProgramSize) break;
+                if (gen.programLengthOverEstimate() > maximumApfProgramSize) {
+                    if (VDBG) Log.d(TAG, "Past maximum program size, skipping RAs");
+                    break;
+                }
+
                 rasToFilter.add(ra);
             }
 
diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java
index a140f4d..bf63c1c 100644
--- a/src/android/net/dhcp/DhcpClient.java
+++ b/src/android/net/dhcp/DhcpClient.java
@@ -31,6 +31,7 @@
 import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
 import static android.net.util.NetworkStackUtils.closeSocketQuietly;
 import static android.net.util.SocketUtils.makePacketSocketAddress;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 import static android.system.OsConstants.AF_INET;
 import static android.system.OsConstants.AF_PACKET;
 import static android.system.OsConstants.ETH_P_IP;
@@ -361,7 +362,7 @@
      *
      */
     public boolean isDhcpLeaseCacheEnabled() {
-        return mDependencies.getBooleanDeviceConfig(NetworkStackUtils.NAMESPACE_CONNECTIVITY,
+        return mDependencies.getBooleanDeviceConfig(NAMESPACE_CONNECTIVITY,
                 NetworkStackUtils.DHCP_INIT_REBOOT_ENABLED);
     }
 
@@ -369,7 +370,7 @@
      * check whether or not to support DHCP Rapid Commit option.
      */
     public boolean isDhcpRapidCommitEnabled() {
-        return mDependencies.getBooleanDeviceConfig(NetworkStackUtils.NAMESPACE_CONNECTIVITY,
+        return mDependencies.getBooleanDeviceConfig(NAMESPACE_CONNECTIVITY,
                 NetworkStackUtils.DHCP_RAPID_COMMIT_ENABLED);
     }
 
diff --git a/src/android/net/dhcp/DhcpServer.java b/src/android/net/dhcp/DhcpServer.java
index b8ab94c..f75fe18 100644
--- a/src/android/net/dhcp/DhcpServer.java
+++ b/src/android/net/dhcp/DhcpServer.java
@@ -20,8 +20,6 @@
 import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME;
 import static android.net.dhcp.DhcpPacket.DHCP_SERVER;
 import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
-import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT;
-import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
 import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
 import static android.system.OsConstants.AF_INET;
@@ -36,7 +34,7 @@
 import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE;
 import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ALL;
 import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
-import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission;
+import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission;
 
 import static java.lang.Integer.toUnsignedLong;
 
@@ -213,7 +211,7 @@
 
         @Override
         public void checkCaller() {
-            checkNetworkStackCallingPermission();
+            enforceNetworkStackCallingPermission();
         }
     }
 
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index 799184e..98e1e49 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -19,7 +19,7 @@
 import static android.net.RouteInfo.RTN_UNICAST;
 import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable;
 
-import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission;
+import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission;
 
 import android.annotation.NonNull;
 import android.content.Context;
@@ -48,6 +48,7 @@
 import android.os.IBinder;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.ServiceSpecificException;
 import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.LocalLog;
@@ -514,67 +515,67 @@
     class IpClientConnector extends IIpClient.Stub {
         @Override
         public void completedPreDhcpAction() {
-            checkNetworkStackCallingPermission();
+            enforceNetworkStackCallingPermission();
             IpClient.this.completedPreDhcpAction();
         }
         @Override
         public void confirmConfiguration() {
-            checkNetworkStackCallingPermission();
+            enforceNetworkStackCallingPermission();
             IpClient.this.confirmConfiguration();
         }
         @Override
         public void readPacketFilterComplete(byte[] data) {
-            checkNetworkStackCallingPermission();
+            enforceNetworkStackCallingPermission();
             IpClient.this.readPacketFilterComplete(data);
         }
         @Override
         public void shutdown() {
-            checkNetworkStackCallingPermission();
+            enforceNetworkStackCallingPermission();
             IpClient.this.shutdown();
         }
         @Override
         public void startProvisioning(ProvisioningConfigurationParcelable req) {
-            checkNetworkStackCallingPermission();
+            enforceNetworkStackCallingPermission();
             IpClient.this.startProvisioning(ProvisioningConfiguration.fromStableParcelable(req));
         }
         @Override
         public void stop() {
-            checkNetworkStackCallingPermission();
+            enforceNetworkStackCallingPermission();
             IpClient.this.stop();
         }
         @Override
         public void setL2KeyAndGroupHint(String l2Key, String groupHint) {
-            checkNetworkStackCallingPermission();
+            enforceNetworkStackCallingPermission();
             IpClient.this.setL2KeyAndGroupHint(l2Key, groupHint);
         }
         @Override
         public void setTcpBufferSizes(String tcpBufferSizes) {
-            checkNetworkStackCallingPermission();
+            enforceNetworkStackCallingPermission();
             IpClient.this.setTcpBufferSizes(tcpBufferSizes);
         }
         @Override
         public void setHttpProxy(ProxyInfo proxyInfo) {
-            checkNetworkStackCallingPermission();
+            enforceNetworkStackCallingPermission();
             IpClient.this.setHttpProxy(proxyInfo);
         }
         @Override
         public void setMulticastFilter(boolean enabled) {
-            checkNetworkStackCallingPermission();
+            enforceNetworkStackCallingPermission();
             IpClient.this.setMulticastFilter(enabled);
         }
         @Override
         public void addKeepalivePacketFilter(int slot, TcpKeepalivePacketDataParcelable pkt) {
-            checkNetworkStackCallingPermission();
+            enforceNetworkStackCallingPermission();
             IpClient.this.addKeepalivePacketFilter(slot, pkt);
         }
         @Override
         public void addNattKeepalivePacketFilter(int slot, NattKeepalivePacketDataParcelable pkt) {
-            checkNetworkStackCallingPermission();
+            enforceNetworkStackCallingPermission();
             IpClient.this.addNattKeepalivePacketFilter(slot, pkt);
         }
         @Override
         public void removeKeepalivePacketFilter(int slot) {
-            checkNetworkStackCallingPermission();
+            enforceNetworkStackCallingPermission();
             IpClient.this.removeKeepalivePacketFilter(slot);
         }
 
@@ -850,10 +851,12 @@
         return shouldLog;
     }
 
+    private void logError(String fmt, Throwable e, Object... args) {
+        mLog.e(String.format(fmt, args), e);
+    }
+
     private void logError(String fmt, Object... args) {
-        final String msg = "ERROR " + String.format(fmt, args);
-        Log.e(mTag, msg);
-        mLog.log(msg);
+        logError(fmt, null, args);
     }
 
     // This needs to be called with care to ensure that our LinkProperties
@@ -948,7 +951,7 @@
         // accompanying code in IpReachabilityMonitor) is unreachable.
         final boolean ignoreIPv6ProvisioningLoss =
                 mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker
-                && mCm.shouldAvoidBadWifi();
+                && !mCm.shouldAvoidBadWifi();
 
         // Additionally:
         //
@@ -1274,6 +1277,28 @@
         // TODO : implement this
     }
 
+    private void maybeRestoreInterfaceMtu() {
+        InterfaceParams params = mDependencies.getInterfaceParams(mInterfaceName);
+        if (params == null) {
+            Log.w(mTag, "interface: " + mInterfaceName + " is gone");
+            return;
+        }
+
+        if (params.index != mInterfaceParams.index) {
+            Log.w(mTag, "interface: " + mInterfaceName + " has a different index: " + params.index);
+            return;
+        }
+
+        if (params.defaultMtu == mInterfaceParams.defaultMtu) return;
+
+        try {
+            mNetd.interfaceSetMtu(mInterfaceName, mInterfaceParams.defaultMtu);
+        } catch (RemoteException | ServiceSpecificException e) {
+            logError("Couldn't reset MTU on " + mInterfaceName + " from "
+                    + params.defaultMtu + " to " + mInterfaceParams.defaultMtu, e);
+        }
+    }
+
     class StoppedState extends State {
         @Override
         public void enter() {
@@ -1351,6 +1376,9 @@
                 // There's no DHCPv4 for which to wait; proceed to stopped.
                 deferMessage(obtainMessage(CMD_JUMP_STOPPING_TO_STOPPED));
             }
+
+            // Restore the interface MTU to initial value if it has changed.
+            maybeRestoreInterfaceMtu();
         }
 
         @Override
diff --git a/src/android/net/util/NetworkStackUtils.java b/src/android/net/util/NetworkStackUtils.java
index 4afc34b..0a18c0e 100644
--- a/src/android/net/util/NetworkStackUtils.java
+++ b/src/android/net/util/NetworkStackUtils.java
@@ -18,6 +18,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.provider.DeviceConfig;
+import android.util.Log;
 import android.util.SparseArray;
 
 import java.io.FileDescriptor;
@@ -33,8 +37,7 @@
  * Collection of utilities for the network stack.
  */
 public class NetworkStackUtils {
-    // TODO: Refer to DeviceConfig definition.
-    public static final String NAMESPACE_CONNECTIVITY = "connectivity";
+    private static final String TAG = "NetworkStackUtils";
 
     /**
      * A list of captive portal detection specifications used in addition to the fallback URLs.
@@ -183,8 +186,8 @@
     @Nullable
     public static String getDeviceConfigProperty(@NonNull String namespace, @NonNull String name,
             @Nullable String defaultValue) {
-        // TODO: Link to DeviceConfig API once it is ready.
-        return defaultValue;
+        String value = DeviceConfig.getProperty(namespace, name);
+        return value != null ? value : defaultValue;
     }
 
     /**
@@ -218,6 +221,30 @@
     }
 
     /**
+     * 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, the corresponding
+     * experimental feature would be enabled, otherwise disabled.
+     * @param context The global context information about an app environment.
+     * @param namespace The namespace containing the property to look up.
+     * @param name The name of the property to look up.
+     * @return true if this feature is enabled, or false if disabled.
+     */
+    public static boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
+            @NonNull String name) {
+        try {
+            final int propertyVersion = getDeviceConfigPropertyInt(namespace, name,
+                    0 /* default value */);
+            final long packageVersion = context.getPackageManager().getPackageInfo(
+                    context.getPackageName(), 0).getLongVersionCode();
+            return (propertyVersion != 0 && packageVersion >= (long) propertyVersion);
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Could not find the package name", e);
+            return false;
+        }
+    }
+
+    /**
      * 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/apishim/SocketUtilsShim.java b/src/com/android/networkstack/apishim/SocketUtilsShim.java
new file mode 100644
index 0000000..34b5f40
--- /dev/null
+++ b/src/com/android/networkstack/apishim/SocketUtilsShim.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim;
+
+import androidx.annotation.NonNull;
+
+import java.net.SocketAddress;
+
+/**
+ * Interface used to access API methods in {@link android.net.util.SocketUtils}, with appropriate
+ * fallbacks if the methods are not yet part of the released API.
+ *
+ * <p>This interface makes it easier for callers to use SocketUtilsShimImpl, 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 SocketUtilsShim {
+    /**
+     * Create a new instance of SocketUtilsShim.
+     */
+    @NonNull
+    static SocketUtilsShim newInstance() {
+        // TODO: when the R API is finalized, rename the API 29 shim to SocketUtilsCompat, and
+        // return it here instead of SocketUtilsShimImpl for devices with Build.VERSION <= 29.
+        // For now, the switch between implementations is done at build time (swapping the java file
+        // with another), since production modules should not be built with a non-finalized API.
+        return new SocketUtilsShimImpl();
+    }
+
+    /**
+     * @see android.net.util.SocketUtils#makePacketSocketAddress(int, int, byte[])
+     */
+    @NonNull
+    SocketAddress makePacketSocketAddress(int protocol, int ifIndex, @NonNull byte[] hwAddr);
+}
diff --git a/src/com/android/networkstack/metrics/DataStallStatsUtils.java b/src/com/android/networkstack/metrics/DataStallStatsUtils.java
index 9308901..59e8fd3 100644
--- a/src/com/android/networkstack/metrics/DataStallStatsUtils.java
+++ b/src/com/android/networkstack/metrics/DataStallStatsUtils.java
@@ -21,6 +21,8 @@
 import android.net.captiveportal.CaptivePortalProbeResult;
 import android.util.Log;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.internal.util.HexDump;
 import com.android.server.connectivity.nano.DataStallEventProto;
 
@@ -38,7 +40,11 @@
     private static final String TAG = DataStallStatsUtils.class.getSimpleName();
     private static final boolean DBG = false;
 
-    private static int probeResultToEnum(@Nullable final CaptivePortalProbeResult result) {
+    /**
+     * Map {@link CaptivePortalProbeResult} to {@link DataStallEventProto}.
+     */
+    @VisibleForTesting
+    public static int probeResultToEnum(@Nullable final CaptivePortalProbeResult result) {
         if (result == null) return DataStallEventProto.INVALID;
 
         if (result.isSuccessful()) {
diff --git a/src/com/android/server/NetworkStackService.java b/src/com/android/server/NetworkStackService.java
index 91cc8c3..9e5ed74 100644
--- a/src/com/android/server/NetworkStackService.java
+++ b/src/com/android/server/NetworkStackService.java
@@ -21,14 +21,12 @@
 import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR;
 
 import static com.android.server.util.PermissionUtil.checkDumpPermission;
-import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
-import android.net.ConnectivityManager;
 import android.net.IIpMemoryStore;
 import android.net.IIpMemoryStoreCallbacks;
 import android.net.INetd;
@@ -49,11 +47,15 @@
 import android.net.util.SharedLog;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.ArraySet;
+
+import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.connectivity.NetworkMonitor;
 import com.android.server.connectivity.ipmemorystore.IpMemoryStoreService;
+import com.android.server.util.PermissionUtil;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -62,7 +64,6 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Android service used to start the network stack when bound to via an intent.
@@ -81,7 +82,8 @@
      */
     public static synchronized IBinder makeConnector(Context context) {
         if (sConnector == null) {
-            sConnector = new NetworkStackConnector(context);
+            sConnector = new NetworkStackConnector(
+                    context, new NetworkStackConnector.PermissionChecker());
         }
         return sConnector;
     }
@@ -103,13 +105,17 @@
         IIpMemoryStore getIpMemoryStoreService();
     }
 
-    private static class NetworkStackConnector extends INetworkStackConnector.Stub
+    /**
+     * Connector implementing INetworkStackConnector for clients.
+     */
+    @VisibleForTesting
+    public static class NetworkStackConnector extends INetworkStackConnector.Stub
             implements NetworkStackServiceManager {
         private static final int NUM_VALIDATION_LOG_LINES = 20;
         private final Context mContext;
+        private final PermissionChecker mPermChecker;
         private final INetd mNetd;
         private final NetworkObserverRegistry mObserverRegistry;
-        private final ConnectivityManager mCm;
         @GuardedBy("mIpClients")
         private final ArrayList<WeakReference<IpClient>> mIpClients = new ArrayList<>();
         private final IpMemoryStoreService mIpMemoryStoreService;
@@ -118,14 +124,25 @@
         @GuardedBy("mValidationLogs")
         private final ArrayDeque<SharedLog> mValidationLogs = new ArrayDeque<>(MAX_VALIDATION_LOGS);
 
-        private static final int VERSION_UNKNOWN = 0;
         private static final String DUMPSYS_ARG_VERSION = "version";
 
-        /** Version of the AIDL interfaces observed on the system */
-        private final AtomicInteger mSystemAidlVersion = new AtomicInteger(VERSION_UNKNOWN);
+        /** Version of the framework AIDL interfaces observed. Should hold only one value. */
+        @GuardedBy("mFrameworkAidlVersions")
+        private final ArraySet<Integer> mFrameworkAidlVersions = new ArraySet<>(1);
+        private final int mNetdAidlVersion;
 
-        /** Whether different versions have been observed on interfaces provided by the system */
-        private volatile boolean mConflictingSystemAidlVersions = false;
+        /**
+         * Permission checking dependency of the connector, useful for testing.
+         */
+        @VisibleForTesting
+        public static class PermissionChecker {
+            /**
+             * @see PermissionUtil#enforceNetworkStackCallingPermission()
+             */
+            public void enforceNetworkStackCallingPermission() {
+                PermissionUtil.enforceNetworkStackCallingPermission();
+            }
+        }
 
         private SharedLog addValidationLogs(Network network, String name) {
             final SharedLog log = new SharedLog(NUM_VALIDATION_LOG_LINES, network + " - " + name);
@@ -138,14 +155,25 @@
             return log;
         }
 
-        NetworkStackConnector(Context context) {
+        @VisibleForTesting
+        public NetworkStackConnector(
+                @NonNull Context context, @NonNull PermissionChecker permChecker) {
             mContext = context;
+            mPermChecker = permChecker;
             mNetd = INetd.Stub.asInterface(
                     (IBinder) context.getSystemService(Context.NETD_SERVICE));
             mObserverRegistry = new NetworkObserverRegistry();
-            mCm = context.getSystemService(ConnectivityManager.class);
             mIpMemoryStoreService = new IpMemoryStoreService(context);
 
+            int netdVersion;
+            try {
+                netdVersion = mNetd.getInterfaceVersion();
+            } catch (RemoteException e) {
+                mLog.e("Error obtaining INetd version", e);
+                netdVersion = -1;
+            }
+            mNetdAidlVersion = netdVersion;
+
             try {
                 mObserverRegistry.register(mNetd);
             } catch (RemoteException e) {
@@ -154,9 +182,8 @@
         }
 
         private void updateSystemAidlVersion(final int version) {
-            final int previousVersion = mSystemAidlVersion.getAndSet(version);
-            if (previousVersion != VERSION_UNKNOWN && previousVersion != version) {
-                mConflictingSystemAidlVersions = true;
+            synchronized (mFrameworkAidlVersions) {
+                mFrameworkAidlVersions.add(version);
             }
         }
 
@@ -166,7 +193,7 @@
         @Override
         public void makeDhcpServer(@NonNull String ifName, @NonNull DhcpServingParamsParcel params,
                 @NonNull IDhcpServerCallbacks cb) throws RemoteException {
-            checkNetworkStackCallingPermission();
+            mPermChecker.enforceNetworkStackCallingPermission();
             updateSystemAidlVersion(cb.getInterfaceVersion());
             final DhcpServer server;
             try {
@@ -189,16 +216,16 @@
         @Override
         public void makeNetworkMonitor(Network network, String name, INetworkMonitorCallbacks cb)
                 throws RemoteException {
-            checkNetworkStackCallingPermission();
+            mPermChecker.enforceNetworkStackCallingPermission();
             updateSystemAidlVersion(cb.getInterfaceVersion());
             final SharedLog log = addValidationLogs(network, name);
             final NetworkMonitor nm = new NetworkMonitor(mContext, cb, network, log);
-            cb.onNetworkMonitorCreated(new NetworkMonitorImpl(nm));
+            cb.onNetworkMonitorCreated(new NetworkMonitorConnector(nm, mPermChecker));
         }
 
         @Override
         public void makeIpClient(String ifName, IIpClientCallbacks cb) throws RemoteException {
-            checkNetworkStackCallingPermission();
+            mPermChecker.enforceNetworkStackCallingPermission();
             updateSystemAidlVersion(cb.getInterfaceVersion());
             final IpClient ipClient = new IpClient(mContext, ifName, cb, mObserverRegistry, this);
 
@@ -224,7 +251,7 @@
         @Override
         public void fetchIpMemoryStore(@NonNull final IIpMemoryStoreCallbacks cb)
                 throws RemoteException {
-            checkNetworkStackCallingPermission();
+            mPermChecker.enforceNetworkStackCallingPermission();
             updateSystemAidlVersion(cb.getInterfaceVersion());
             cb.onIpMemoryStoreFetched(mIpMemoryStoreService);
         }
@@ -233,12 +260,16 @@
         protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
                 @Nullable String[] args) {
             checkDumpPermission();
+
+            final IndentingPrintWriter pw = new IndentingPrintWriter(fout, "  ");
+            pw.println("NetworkStack version:");
+            dumpVersion(pw);
+            pw.println();
+
             if (args != null && args.length >= 1 && DUMPSYS_ARG_VERSION.equals(args[0])) {
-                dumpVersion(fout);
                 return;
             }
 
-            final IndentingPrintWriter pw = new IndentingPrintWriter(fout, "  ");
             pw.println("NetworkStack logs:");
             mLog.dump(fd, pw, args);
 
@@ -286,86 +317,100 @@
          */
         private void dumpVersion(@NonNull PrintWriter fout) {
             fout.println("NetworkStackConnector: " + this.VERSION);
-            fout.println("SystemServer: " + mSystemAidlVersion);
-            fout.println("SystemServerConflicts: " + mConflictingSystemAidlVersions);
+            synchronized (mFrameworkAidlVersions) {
+                fout.println("SystemServer: " + mFrameworkAidlVersions);
+            }
+            fout.println("Netd: " + mNetdAidlVersion);
         }
 
+        /**
+         * Get the version of the AIDL interface.
+         */
         @Override
         public int getInterfaceVersion() {
             return this.VERSION;
         }
     }
 
-    private static class NetworkMonitorImpl extends INetworkMonitor.Stub {
+    /**
+     * Proxy for {@link NetworkMonitor} that implements {@link INetworkMonitor}.
+     */
+    @VisibleForTesting
+    public static class NetworkMonitorConnector extends INetworkMonitor.Stub {
+        @NonNull
         private final NetworkMonitor mNm;
+        @NonNull
+        private final NetworkStackConnector.PermissionChecker mPermChecker;
 
-        NetworkMonitorImpl(NetworkMonitor nm) {
+        public NetworkMonitorConnector(@NonNull NetworkMonitor nm,
+                @NonNull NetworkStackConnector.PermissionChecker permChecker) {
             mNm = nm;
+            mPermChecker = permChecker;
         }
 
         @Override
         public void start() {
-            checkNetworkStackCallingPermission();
+            mPermChecker.enforceNetworkStackCallingPermission();
             mNm.start();
         }
 
         @Override
         public void launchCaptivePortalApp() {
-            checkNetworkStackCallingPermission();
+            mPermChecker.enforceNetworkStackCallingPermission();
             mNm.launchCaptivePortalApp();
         }
 
         @Override
         public void notifyCaptivePortalAppFinished(int response) {
-            checkNetworkStackCallingPermission();
+            mPermChecker.enforceNetworkStackCallingPermission();
             mNm.notifyCaptivePortalAppFinished(response);
         }
 
         @Override
         public void setAcceptPartialConnectivity() {
-            checkNetworkStackCallingPermission();
+            mPermChecker.enforceNetworkStackCallingPermission();
             mNm.setAcceptPartialConnectivity();
         }
 
         @Override
         public void forceReevaluation(int uid) {
-            checkNetworkStackCallingPermission();
+            mPermChecker.enforceNetworkStackCallingPermission();
             mNm.forceReevaluation(uid);
         }
 
         @Override
         public void notifyPrivateDnsChanged(PrivateDnsConfigParcel config) {
-            checkNetworkStackCallingPermission();
+            mPermChecker.enforceNetworkStackCallingPermission();
             mNm.notifyPrivateDnsSettingsChanged(PrivateDnsConfig.fromParcel(config));
         }
 
         @Override
         public void notifyDnsResponse(int returnCode) {
-            checkNetworkStackCallingPermission();
+            mPermChecker.enforceNetworkStackCallingPermission();
             mNm.notifyDnsResponse(returnCode);
         }
 
         @Override
         public void notifyNetworkConnected(LinkProperties lp, NetworkCapabilities nc) {
-            checkNetworkStackCallingPermission();
+            mPermChecker.enforceNetworkStackCallingPermission();
             mNm.notifyNetworkConnected(lp, nc);
         }
 
         @Override
         public void notifyNetworkDisconnected() {
-            checkNetworkStackCallingPermission();
+            mPermChecker.enforceNetworkStackCallingPermission();
             mNm.notifyNetworkDisconnected();
         }
 
         @Override
         public void notifyLinkPropertiesChanged(LinkProperties lp) {
-            checkNetworkStackCallingPermission();
+            mPermChecker.enforceNetworkStackCallingPermission();
             mNm.notifyLinkPropertiesChanged(lp);
         }
 
         @Override
         public void notifyNetworkCapabilitiesChanged(NetworkCapabilities nc) {
-            checkNetworkStackCallingPermission();
+            mPermChecker.enforceNetworkStackCallingPermission();
             mNm.notifyNetworkCapabilitiesChanged(nc);
         }
 
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java
index 6122d98..585e38e 100644
--- a/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -62,8 +62,8 @@
 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS;
 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USER_AGENT;
 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USE_HTTPS;
-import static android.net.util.NetworkStackUtils.NAMESPACE_CONNECTIVITY;
 import static android.net.util.NetworkStackUtils.isEmpty;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 
 import static com.android.networkstack.util.DnsUtils.PRIVATE_DNS_PROBE_HOST_SUFFIX;
 import static com.android.networkstack.util.DnsUtils.TYPE_ADDRCONFIG;
@@ -379,7 +379,7 @@
     }
 
     @VisibleForTesting
-    protected NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
+    public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
             IpConnectivityLog logger, SharedLog validationLogs,
             Dependencies deps, DataStallStatsUtils detectionStatsUtils) {
         // Add suffix indicating which NetworkMonitor we're talking about.
@@ -1843,8 +1843,7 @@
             latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_TIMESTAMP_MS,
                     responseTimestampMs);
         }
-        mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT,
-                NetworkMonitorUtils.PERMISSION_ACCESS_NETWORK_CONDITIONS);
+        mDependencies.sendNetworkConditionsBroadcast(mContext, latencyBroadcast);
     }
 
     private void logNetworkEvent(int evtype) {
@@ -1889,7 +1888,7 @@
     }
 
     @VisibleForTesting
-    static class Dependencies {
+    public static class Dependencies {
         public Network getPrivateDnsBypassNetwork(Network network) {
             return new OneAddressPerFamilyNetwork(network);
         }
@@ -1948,6 +1947,15 @@
             return NetworkStackUtils.getDeviceConfigPropertyInt(namespace, name, defaultValue);
         }
 
+        /**
+         * 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);
+        }
+
         public static final Dependencies DEFAULT = new Dependencies();
     }
 
diff --git a/src/com/android/server/util/NetworkStackConstants.java b/src/com/android/server/util/NetworkStackConstants.java
index 804765e..3174a9b 100644
--- a/src/com/android/server/util/NetworkStackConstants.java
+++ b/src/com/android/server/util/NetworkStackConstants.java
@@ -54,6 +54,7 @@
     public static final int ETHER_TYPE_IPV4 = 0x0800;
     public static final int ETHER_TYPE_IPV6 = 0x86dd;
     public static final int ETHER_HEADER_LEN = 14;
+    public static final int ETHER_MTU = 1500;
 
     /**
      * ARP constants.
@@ -97,6 +98,7 @@
     public static final int IPV6_PROTOCOL_OFFSET = 6;
     public static final int IPV6_SRC_ADDR_OFFSET = 8;
     public static final int IPV6_DST_ADDR_OFFSET = 24;
+    public static final int IPV6_MIN_MTU = 1280;
 
     /**
      * ICMPv6 constants.
diff --git a/src/com/android/server/util/PermissionUtil.java b/src/com/android/server/util/PermissionUtil.java
index c4d736b..28dad25 100644
--- a/src/com/android/server/util/PermissionUtil.java
+++ b/src/com/android/server/util/PermissionUtil.java
@@ -34,7 +34,7 @@
      * Check that the caller is allowed to communicate with the network stack.
      * @throws SecurityException The caller is not allowed to communicate with the network stack.
      */
-    public static void checkNetworkStackCallingPermission() {
+    public static void enforceNetworkStackCallingPermission() {
         final int caller = getCallingUid();
         if (caller == Process.SYSTEM_UID) {
             checkConsistentSystemPid();
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index ec8257f..89b3968 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -23,7 +23,8 @@
         "androidx.annotation_annotation",
         "androidx.test.rules",
         "mockito-target-extended-minus-junit4",
-        "NetworkStackBase",
+        "net-tests-utils",
+        "NetworkStackApiCurrentLib",
         "testables",
     ],
     libs: [
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
index 16e92ef..cb7d418 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
@@ -34,6 +34,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
@@ -49,6 +50,7 @@
 import android.net.ConnectivityManager;
 import android.net.INetd;
 import android.net.InetAddresses;
+import android.net.LinkProperties;
 import android.net.NetworkStackIpMemoryStore;
 import android.net.TestNetworkInterface;
 import android.net.TestNetworkManager;
@@ -68,6 +70,8 @@
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.system.ErrnoException;
+import android.system.Os;
 
 import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
@@ -77,6 +81,7 @@
 import com.android.server.NetworkObserverRegistry;
 import com.android.server.NetworkStackService.NetworkStackServiceManager;
 import com.android.server.connectivity.ipmemorystore.IpMemoryStoreService;
+import com.android.testutils.HandlerUtilsKt;
 
 import org.junit.After;
 import org.junit.Before;
@@ -91,6 +96,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.net.Inet4Address;
+import java.net.NetworkInterface;
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.Collections;
@@ -112,7 +118,7 @@
 
     @Mock private Context mContext;
     @Mock private ConnectivityManager mCm;
-    @Mock private INetd mNetd;
+    @Mock private INetd mMockNetd;
     @Mock private Resources mResources;
     @Mock private IIpClientCallbacks mCb;
     @Mock private AlarmManager mAlarm;
@@ -122,6 +128,7 @@
     @Mock private IpMemoryStoreService mIpMemoryStoreService;
 
     private String mIfaceName;
+    private INetd mNetd;
     private HandlerThread mPacketReaderThread;
     private TapPacketReader mPacketReader;
     private IpClient mIpc;
@@ -158,7 +165,8 @@
     private static final Inet4Address BROADCAST_ADDR = getBroadcastAddress(
             SERVER_ADDR, PREFIX_LENGTH);
     private static final String HOSTNAME = "testhostname";
-    private static final short MTU = 1500;
+    private static final int TEST_DEFAULT_MTU = 1500;
+    private static final int TEST_MIN_MTU = 1280;
 
     private static class TapPacketReader extends PacketReader {
         private final ParcelFileDescriptor mTapFd;
@@ -214,7 +222,7 @@
 
         @Override
         public INetd getNetd(Context context) {
-            return mNetd;
+            return mMockNetd;
         }
 
         @Override
@@ -287,7 +295,6 @@
             inst.getUiAutomation().dropShellPermissionIdentity();
         }
         mIfaceName = iface.getInterfaceName();
-
         mPacketReaderThread = new HandlerThread(IpClientIntegrationTest.class.getSimpleName());
         mPacketReaderThread.start();
 
@@ -300,12 +307,12 @@
         final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
         final IBinder netdIBinder =
                 (IBinder) inst.getContext().getSystemService(Context.NETD_SERVICE);
-        final INetd netd = INetd.Stub.asInterface(netdIBinder);
+        mNetd = INetd.Stub.asInterface(netdIBinder);
         when(mContext.getSystemService(eq(Context.NETD_SERVICE))).thenReturn(netdIBinder);
-        assertNotNull(netd);
+        assertNotNull(mNetd);
 
         final NetworkObserverRegistry reg = new NetworkObserverRegistry();
-        reg.register(netd);
+        reg.register(mNetd);
         mIpc = new IpClient(mContext, mIfaceName, mCb, reg, mNetworkStackServiceManager,
                 mDependencies);
     }
@@ -345,7 +352,7 @@
     }
 
     private static ByteBuffer buildDhcpOfferPacket(final DhcpPacket packet,
-            final Integer leaseTimeSec) {
+            final Integer leaseTimeSec, final short mtu) {
         return DhcpPacket.buildOfferPacket(DhcpPacket.ENCAP_L2, packet.getTransactionId(),
                 false /* broadcast */, SERVER_ADDR, INADDR_ANY /* relayIp */,
                 CLIENT_ADDR /* yourIp */, packet.getClientMac(), leaseTimeSec,
@@ -353,11 +360,11 @@
                 Collections.singletonList(SERVER_ADDR) /* gateways */,
                 Collections.singletonList(SERVER_ADDR) /* dnsServers */,
                 SERVER_ADDR /* dhcpServerIdentifier */, null /* domainName */, HOSTNAME,
-                false /* metered */, MTU);
+                false /* metered */, mtu);
     }
 
     private static ByteBuffer buildDhcpAckPacket(final DhcpPacket packet,
-            final Integer leaseTimeSec) {
+            final Integer leaseTimeSec, final short mtu) {
         return DhcpPacket.buildAckPacket(DhcpPacket.ENCAP_L2, packet.getTransactionId(),
                 false /* broadcast */, SERVER_ADDR, INADDR_ANY /* relayIp */,
                 CLIENT_ADDR /* yourIp */, CLIENT_ADDR /* requestIp */, packet.getClientMac(),
@@ -365,7 +372,7 @@
                 Collections.singletonList(SERVER_ADDR) /* gateways */,
                 Collections.singletonList(SERVER_ADDR) /* dnsServers */,
                 SERVER_ADDR /* dhcpServerIdentifier */, null /* domainName */, HOSTNAME,
-                false /* metered */, MTU);
+                false /* metered */, mtu);
     }
 
     private static ByteBuffer buildDhcpNakPacket(final DhcpPacket packet) {
@@ -391,13 +398,13 @@
         mDependencies.setDhcpRapidCommitEnabled(isDhcpRapidCommitEnabled);
         mIpc.setL2KeyAndGroupHint(TEST_L2KEY, TEST_GROUPHINT);
         mIpc.startProvisioning(config);
-        verify(mCb, times(1)).setNeighborDiscoveryOffload(true);
-        verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setFallbackMulticastFilter(false);
+        verify(mCb).setNeighborDiscoveryOffload(true);
+        verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(false);
         verify(mCb, never()).onProvisioningFailure(any());
     }
 
     private void assertIpMemoryStoreNetworkAttributes(final Integer leaseTimeSec,
-            final long startTime) {
+            final long startTime, final int mtu) {
         final ArgumentCaptor<NetworkAttributes> networkAttributes =
                 ArgumentCaptor.forClass(NetworkAttributes.class);
 
@@ -416,7 +423,7 @@
             assertTrue(lowerBound < expiry);
         }
         assertEquals(Collections.singletonList(SERVER_ADDR), naValueCaptured.dnsAddresses);
-        assertEquals(new Integer((int) MTU), naValueCaptured.mtu);
+        assertEquals(new Integer(mtu), naValueCaptured.mtu);
     }
 
     private void assertIpMemoryNeverStoreNetworkAttributes() {
@@ -426,20 +433,20 @@
     // Helper method to complete DHCP 2-way or 4-way handshake
     private void performDhcpHandshake(final boolean isSuccessLease,
             final Integer leaseTimeSec, final boolean isDhcpLeaseCacheEnabled,
-            final boolean isDhcpRapidCommitEnabled) throws Exception {
+            final boolean isDhcpRapidCommitEnabled, final int mtu) throws Exception {
         startIpClientProvisioning(isDhcpLeaseCacheEnabled, isDhcpRapidCommitEnabled);
 
         DhcpPacket packet;
         while ((packet = getNextDhcpPacket()) != null) {
             if (packet instanceof DhcpDiscoverPacket) {
                 if (isDhcpRapidCommitEnabled) {
-                    sendResponse(buildDhcpAckPacket(packet, leaseTimeSec));
+                    sendResponse(buildDhcpAckPacket(packet, leaseTimeSec, (short) mtu));
                 } else {
-                    sendResponse(buildDhcpOfferPacket(packet, leaseTimeSec));
+                    sendResponse(buildDhcpOfferPacket(packet, leaseTimeSec, (short) mtu));
                 }
             } else if (packet instanceof DhcpRequestPacket) {
                 final ByteBuffer byteBuffer = isSuccessLease
-                        ? buildDhcpAckPacket(packet, leaseTimeSec)
+                        ? buildDhcpAckPacket(packet, leaseTimeSec, (short) mtu)
                         : buildDhcpNakPacket(packet);
                 sendResponse(byteBuffer);
             } else {
@@ -474,6 +481,53 @@
         return getNextDhcpPacket();
     }
 
+    private void removeTapInterface(final FileDescriptor fd) {
+        try {
+            Os.close(fd);
+        } catch (ErrnoException e) {
+            fail("Fail to close file descriptor: " + e);
+        }
+    }
+
+    private void verifyAfterIpClientShutdown() throws RemoteException {
+        final LinkProperties emptyLp = new LinkProperties();
+        emptyLp.setInterfaceName(mIfaceName);
+        verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(emptyLp);
+    }
+
+    private void doRestoreInitialMtuTest(final boolean shouldChangeMtu,
+            final boolean shouldRemoveTapInterface) throws Exception {
+        final long currentTime = System.currentTimeMillis();
+        int mtu = TEST_DEFAULT_MTU;
+
+        if (shouldChangeMtu) mtu = TEST_MIN_MTU;
+        performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
+                true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */, mtu);
+        assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, mtu);
+
+        if (shouldChangeMtu) {
+            // Pretend that ConnectivityService set the MTU.
+            mNetd.interfaceSetMtu(mIfaceName, mtu);
+            assertEquals(NetworkInterface.getByName(mIfaceName).getMTU(), mtu);
+        }
+
+        if (shouldRemoveTapInterface) removeTapInterface(mPacketReader.createFd());
+        try {
+            mIpc.shutdown();
+            HandlerUtilsKt.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
+            if (shouldRemoveTapInterface) {
+                verify(mMockNetd, never()).interfaceSetMtu(mIfaceName, TEST_DEFAULT_MTU);
+            } else {
+                // Verify that MTU indeed has been restored or not.
+                verify(mMockNetd, times(shouldChangeMtu ? 1 : 0))
+                        .interfaceSetMtu(mIfaceName, TEST_DEFAULT_MTU);
+            }
+            verifyAfterIpClientShutdown();
+        } catch (Exception e) {
+            fail("Exception should not have been thrown after shutdown: " + e);
+        }
+    }
+
     @Test
     public void testDhcpInit() throws Exception {
         startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */,
@@ -486,14 +540,16 @@
     public void testHandleSuccessDhcpLease() throws Exception {
         final long currentTime = System.currentTimeMillis();
         performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
-                true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */);
-        assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime);
+                true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */,
+                TEST_DEFAULT_MTU);
+        assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
     }
 
     @Test
     public void testHandleFailureDhcpLease() throws Exception {
         performDhcpHandshake(false /* isSuccessLease */, TEST_LEASE_DURATION_S,
-                true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */);
+                true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */,
+                TEST_DEFAULT_MTU);
         assertIpMemoryNeverStoreNetworkAttributes();
     }
 
@@ -501,22 +557,25 @@
     public void testHandleInfiniteLease() throws Exception {
         final long currentTime = System.currentTimeMillis();
         performDhcpHandshake(true /* isSuccessLease */, INFINITE_LEASE,
-                true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */);
-        assertIpMemoryStoreNetworkAttributes(INFINITE_LEASE, currentTime);
+                true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */,
+                TEST_DEFAULT_MTU);
+        assertIpMemoryStoreNetworkAttributes(INFINITE_LEASE, currentTime, TEST_DEFAULT_MTU);
     }
 
     @Test
     public void testHandleNoLease() throws Exception {
         final long currentTime = System.currentTimeMillis();
         performDhcpHandshake(true /* isSuccessLease */, null /* no lease time */,
-                true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */);
-        assertIpMemoryStoreNetworkAttributes(null, currentTime);
+                true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */,
+                TEST_DEFAULT_MTU);
+        assertIpMemoryStoreNetworkAttributes(null, currentTime, TEST_DEFAULT_MTU);
     }
 
     @Test
     public void testHandleDisableInitRebootState() throws Exception {
         performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
-                false /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */);
+                false /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */,
+                TEST_DEFAULT_MTU);
         assertIpMemoryNeverStoreNetworkAttributes();
     }
 
@@ -526,8 +585,9 @@
         // TODO: remove @Ignore after supporting rapid commit option in DHCP server
         final long currentTime = System.currentTimeMillis();
         performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
-                true /* isDhcpLeaseCacheEnabled */, true /* isDhcpRapidCommitEnabled */);
-        assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime);
+                true /* isDhcpLeaseCacheEnabled */, true /* isDhcpRapidCommitEnabled */,
+                TEST_DEFAULT_MTU);
+        assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
     }
 
     @Test
@@ -536,7 +596,7 @@
                 new NetworkAttributes.Builder()
                     .setAssignedV4Address(CLIENT_ADDR)
                     .setAssignedV4AddressExpiry(Long.MAX_VALUE) // lease is always valid
-                    .setMtu(new Integer(MTU))
+                    .setMtu(new Integer(TEST_DEFAULT_MTU))
                     .setGroupHint(TEST_GROUPHINT)
                     .setDnsAddresses(Collections.singletonList(SERVER_ADDR))
                     .build(), false /* timeout */);
@@ -549,7 +609,7 @@
                  new NetworkAttributes.Builder()
                     .setAssignedV4Address(CLIENT_ADDR)
                     .setAssignedV4AddressExpiry(EXPIRED_LEASE)
-                    .setMtu(new Integer(MTU))
+                    .setMtu(new Integer(TEST_DEFAULT_MTU))
                     .setGroupHint(TEST_GROUPHINT)
                     .setDnsAddresses(Collections.singletonList(SERVER_ADDR))
                     .build(), false /* timeout */);
@@ -568,7 +628,7 @@
                 new NetworkAttributes.Builder()
                     .setAssignedV4Address(CLIENT_ADDR)
                     .setAssignedV4AddressExpiry(System.currentTimeMillis() + 3_600_000)
-                    .setMtu(new Integer(MTU))
+                    .setMtu(new Integer(TEST_DEFAULT_MTU))
                     .setGroupHint(TEST_GROUPHINT)
                     .setDnsAddresses(Collections.singletonList(SERVER_ADDR))
                     .build(), true /* timeout */);
@@ -579,7 +639,7 @@
     public void testDhcpClientStartWithCachedLeaseWithoutIPAddress() throws Exception {
         final DhcpPacket packet = getReplyFromDhcpLease(
                 new NetworkAttributes.Builder()
-                    .setMtu(new Integer(MTU))
+                    .setMtu(new Integer(TEST_DEFAULT_MTU))
                     .setGroupHint(TEST_GROUPHINT)
                     .setDnsAddresses(Collections.singletonList(SERVER_ADDR))
                     .build(), false /* timeout */);
@@ -593,4 +653,42 @@
         final DhcpPacket packet = getNextDhcpPacket();
         assertTrue(DhcpDiscoverPacket.class.isInstance(packet));
     }
+
+    @Test
+    public void testRestoreInitialInterfaceMtu() throws Exception {
+        doRestoreInitialMtuTest(true /* shouldChangeMtu */, false /* shouldRemoveTapInterface */);
+    }
+
+    @Test
+    public void testRestoreInitialInterfaceMtu_WithoutMtuChange() throws Exception {
+        doRestoreInitialMtuTest(false /* shouldChangeMtu */, false /* shouldRemoveTapInterface */);
+    }
+
+    @Test
+    public void testRestoreInitialInterfaceMtu_WithException() throws Exception {
+        doThrow(new RemoteException("NetdNativeService::interfaceSetMtu")).when(mMockNetd)
+                .interfaceSetMtu(mIfaceName, TEST_DEFAULT_MTU);
+
+        doRestoreInitialMtuTest(true /* shouldChangeMtu */, false /* shouldRemoveTapInterface */);
+        assertEquals(NetworkInterface.getByName(mIfaceName).getMTU(), TEST_MIN_MTU);
+    }
+
+    @Test
+    public void testRestoreInitialInterfaceMtu_NotFoundInterfaceWhenStopping() throws Exception {
+        doRestoreInitialMtuTest(true /* shouldChangeMtu */, true /* shouldRemoveTapInterface */);
+    }
+
+    @Test
+    public void testRestoreInitialInterfaceMtu_NotFoundInterfaceWhenStartingProvisioning()
+            throws Exception {
+        removeTapInterface(mPacketReader.createFd());
+        ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+                .withoutIpReachabilityMonitor()
+                .withoutIPv6()
+                .build();
+
+        mIpc.startProvisioning(config);
+        verify(mCb).onProvisioningFailure(any());
+        verify(mCb, never()).setNeighborDiscoveryOffload(true);
+    }
 }
diff --git a/tests/lib/Android.bp b/tests/lib/Android.bp
index 1d52fa0..1db4054 100644
--- a/tests/lib/Android.bp
+++ b/tests/lib/Android.bp
@@ -23,6 +23,7 @@
     defaults: ["lib_mockito_extended"],
     static_libs: [
         "kotlin-test",
+        "junit",
     ],
 }
 
diff --git a/tests/lib/src/com/android/testutils/ConcurrentIntepreter.kt b/tests/lib/src/com/android/testutils/ConcurrentIntepreter.kt
index 5e1e005..25b1e0f 100644
--- a/tests/lib/src/com/android/testutils/ConcurrentIntepreter.kt
+++ b/tests/lib/src/com/android/testutils/ConcurrentIntepreter.kt
@@ -4,6 +4,7 @@
 import java.util.concurrent.CyclicBarrier
 import kotlin.system.measureTimeMillis
 import kotlin.test.assertEquals
+import kotlin.test.assertFails
 import kotlin.test.assertNull
 import kotlin.test.assertTrue
 
@@ -64,7 +65,15 @@
 
     // Spins as many threads as needed by the test spec and interpret each program concurrently,
     // having all threads waiting on a CyclicBarrier after each line.
-    fun interpretTestSpec(spec: String, initial: T, threadTransform: (T) -> T = { it }) {
+    // |lineShift| says how many lines after the call the spec starts. This is used for error
+    // reporting. Unfortunately AFAICT there is no way to get the line of an argument rather
+    // than the line at which the expression starts.
+    fun interpretTestSpec(
+        spec: String,
+        initial: T,
+        lineShift: Int = 0,
+        threadTransform: (T) -> T = { it }
+    ) {
         // For nice stack traces
         val callSite = getCallingMethod()
         val lines = spec.trim().trim('\n').split("\n").map { it.split("|") }
@@ -91,7 +100,8 @@
                         // testing. Instead, catch the exception, cancel other threads, and report
                         // nicely. Catch throwable because fail() is AssertionError, which inherits
                         // from Error.
-                        crash = InterpretException(threadIndex, callSite.lineNumber + lineNum,
+                        crash = InterpretException(threadIndex, it,
+                                callSite.lineNumber + lineNum + lineShift,
                                 callSite.className, callSite.methodName, callSite.fileName, e)
                     }
                     barrier.await()
@@ -103,18 +113,29 @@
     }
 
     // Helper to get the stack trace for a calling method
-    protected fun getCallingMethod(depth: Int): StackTraceElement {
+    private fun getCallingStackTrace(): Array<StackTraceElement> {
         try {
             throw RuntimeException()
         } catch (e: RuntimeException) {
-            return e.stackTrace[depth]
+            return e.stackTrace
         }
     }
 
-    // Override this if you don't call interpretTestSpec directly to get the correct file
-    // and line for failure in the error message.
-    // 0 is this method here, 1 is getCallingMethod(int), 2 is interpretTestSpec, 3 the lambda
-    open fun getCallingMethod() = getCallingMethod(4)
+    // Find the calling method. This is the first method in the stack trace that is annotated
+    // with @Test.
+    fun getCallingMethod(): StackTraceElement {
+        val stackTrace = getCallingStackTrace()
+        return stackTrace.find { element ->
+            val clazz = Class.forName(element.className)
+            // Because the stack trace doesn't list the formal arguments, find all methods with
+            // this name and return this name if any of them is annotated with @Test.
+            clazz.declaredMethods
+                    .filter { method -> method.name == element.methodName }
+                    .any { method -> method.getAnnotation(org.junit.Test::class.java) != null }
+        } ?: stackTrace[3]
+        // If no method is annotated return the 4th one, because that's what it usually is :
+        // 0 is getCallingStackTrace, 1 is this method, 2 is ConcurrentInterpreter#interpretTestSpec
+    }
 }
 
 private fun <T> getDefaultInstructions() = listOf<InterpretMatcher<T>>(
@@ -124,7 +145,8 @@
     Regex("(.*)//.*") to { i, t, r -> i.interpret(r.strArg(1), t) },
     // Interpret "XXX time x..y" : run XXX and check it took at least x and not more than y
     Regex("""(.*)\s*time\s*(\d+)\.\.(\d+)""") to { i, t, r ->
-        assertTrue(measureTimeMillis { i.interpret(r.strArg(1), t) } in r.timeArg(2)..r.timeArg(3))
+        val time = measureTimeMillis { i.interpret(r.strArg(1), t) }
+        assertTrue(time in r.timeArg(2)..r.timeArg(3), "$time not in ${r.timeArg(2)..r.timeArg(3)}")
     },
     // Interpret "XXX = YYY" : run XXX and assert its return value is equal to YYY. "null" supported
     Regex("""(.*)\s*=\s*(null|\d+)""") to { i, t, r ->
@@ -135,18 +157,22 @@
     // Interpret sleep. Optional argument for the count, in INTERPRET_TIME_UNIT units.
     Regex("""sleep(\((\d+)\))?""") to { i, t, r ->
         SystemClock.sleep(if (r.strArg(2).isEmpty()) i.interpretTimeUnit else r.timeArg(2))
+    },
+    Regex("""(.*)\s*fails""") to { i, t, r ->
+        assertFails { i.interpret(r.strArg(1), t) }
     }
 )
 
 class SyntaxException(msg: String, cause: Throwable? = null) : RuntimeException(msg, cause)
 class InterpretException(
     threadIndex: Int,
+    instr: String,
     lineNum: Int,
     className: String,
     methodName: String,
     fileName: String,
     cause: Throwable
-) : RuntimeException(cause) {
+) : RuntimeException("Failure: $instr", cause) {
     init {
         stackTrace = arrayOf(StackTraceElement(
                 className,
diff --git a/tests/lib/src/com/android/testutils/TestableNetworkCallback.kt b/tests/lib/src/com/android/testutils/TestableNetworkCallback.kt
index 1cc1168..bbb279e 100644
--- a/tests/lib/src/com/android/testutils/TestableNetworkCallback.kt
+++ b/tests/lib/src/com/android/testutils/TestableNetworkCallback.kt
@@ -21,11 +21,15 @@
 import android.net.Network
 import android.net.NetworkCapabilities
 import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
-import com.android.testutils.RecorderCallback.CallbackRecord.Available
-import com.android.testutils.RecorderCallback.CallbackRecord.BlockedStatus
-import com.android.testutils.RecorderCallback.CallbackRecord.CapabilitiesChanged
-import com.android.testutils.RecorderCallback.CallbackRecord.LinkPropertiesChanged
-import com.android.testutils.RecorderCallback.CallbackRecord.Lost
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
+import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.Losing
+import com.android.testutils.RecorderCallback.CallbackEntry.Lost
+import com.android.testutils.RecorderCallback.CallbackEntry.Resumed
+import com.android.testutils.RecorderCallback.CallbackEntry.Suspended
+import com.android.testutils.RecorderCallback.CallbackEntry.Unavailable
 import kotlin.reflect.KClass
 import kotlin.test.assertEquals
 import kotlin.test.assertTrue
@@ -35,38 +39,43 @@
 
 private val Int.capabilityName get() = NetworkCapabilities.capabilityNameOf(this)
 
-open class RecorderCallback : NetworkCallback() {
-    sealed class CallbackRecord {
+open class RecorderCallback private constructor(
+    private val backingRecord: ArrayTrackRecord<CallbackEntry>
+) : NetworkCallback() {
+    public constructor() : this(ArrayTrackRecord())
+    protected constructor(src: RecorderCallback?): this(src?.backingRecord ?: ArrayTrackRecord())
+
+    sealed class CallbackEntry {
         // To get equals(), hashcode(), componentN() etc for free, the child classes of
         // this class are data classes. But while data classes can inherit from other classes,
         // they may only have visible members in the constructors, so they couldn't declare
-        // a constructor with a non-val arg to pass to CallbackRecord. Instead, force all
+        // a constructor with a non-val arg to pass to CallbackEntry. Instead, force all
         // subclasses to implement a `network' property, which can be done in a data class
         // constructor by specifying override.
         abstract val network: Network
 
-        data class Available(override val network: Network) : CallbackRecord()
+        data class Available(override val network: Network) : CallbackEntry()
         data class CapabilitiesChanged(
             override val network: Network,
             val caps: NetworkCapabilities
-        ) : CallbackRecord()
+        ) : CallbackEntry()
         data class LinkPropertiesChanged(
             override val network: Network,
             val lp: LinkProperties
-        ) : CallbackRecord()
-        data class Suspended(override val network: Network) : CallbackRecord()
-        data class Resumed(override val network: Network) : CallbackRecord()
-        data class Losing(override val network: Network, val maxMsToLive: Int) : CallbackRecord()
-        data class Lost(override val network: Network) : CallbackRecord()
+        ) : CallbackEntry()
+        data class Suspended(override val network: Network) : CallbackEntry()
+        data class Resumed(override val network: Network) : CallbackEntry()
+        data class Losing(override val network: Network, val maxMsToLive: Int) : CallbackEntry()
+        data class Lost(override val network: Network) : CallbackEntry()
         data class Unavailable private constructor(
             override val network: Network
-        ) : CallbackRecord() {
+        ) : CallbackEntry() {
             constructor() : this(NULL_NETWORK)
         }
         data class BlockedStatus(
             override val network: Network,
             val blocked: Boolean
-        ) : CallbackRecord()
+        ) : CallbackEntry()
 
         // Convenience constants for expecting a type
         companion object {
@@ -91,12 +100,15 @@
         }
     }
 
-    protected val history = ArrayTrackRecord<CallbackRecord>().newReadHead()
+    protected val history = backingRecord.newReadHead()
 
     override fun onAvailable(network: Network) {
         history.add(Available(network))
     }
 
+    // PreCheck is not used in the tests today. For backward compatibility with existing tests that
+    // expect the callbacks not to record this, do not listen to PreCheck here.
+
     override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) {
         history.add(CapabilitiesChanged(network, caps))
     }
@@ -110,39 +122,46 @@
     }
 
     override fun onNetworkSuspended(network: Network) {
-        history.add(CallbackRecord.Suspended(network))
+        history.add(Suspended(network))
     }
 
     override fun onNetworkResumed(network: Network) {
-        history.add(CallbackRecord.Resumed(network))
+        history.add(Resumed(network))
     }
 
     override fun onLosing(network: Network, maxMsToLive: Int) {
-        history.add(CallbackRecord.Losing(network, maxMsToLive))
+        history.add(Losing(network, maxMsToLive))
     }
 
     override fun onLost(network: Network) {
-        history.add(CallbackRecord.Lost(network))
+        history.add(Lost(network))
     }
 
     override fun onUnavailable() {
-        history.add(CallbackRecord.Unavailable())
+        history.add(Unavailable())
     }
 }
 
-typealias CallbackType = KClass<out RecorderCallback.CallbackRecord>
-const val DEFAULT_TIMEOUT = 200L // ms
+private const val DEFAULT_TIMEOUT = 200L // ms
 
-open class TestableNetworkCallback(val defaultTimeoutMs: Long = DEFAULT_TIMEOUT)
-        : RecorderCallback() {
-    // The last available network. Null if the last available network was lost since.
+open class TestableNetworkCallback private constructor(
+    src: TestableNetworkCallback?,
+    val defaultTimeoutMs: Long = DEFAULT_TIMEOUT
+) : RecorderCallback(src) {
+    @JvmOverloads
+    constructor(timeoutMs: Long = DEFAULT_TIMEOUT): this(null, timeoutMs)
+
+    fun createLinkedCopy() = TestableNetworkCallback(this, defaultTimeoutMs)
+
+    // The last available network, or null if any network was lost since the last call to
+    // onAvailable. TODO : fix this by fixing the tests that rely on this behavior
     val lastAvailableNetwork: Network?
         get() = when (val it = history.lastOrNull { it is Available || it is Lost }) {
             is Available -> it.network
             else -> null
         }
 
-    fun pollForNextCallback(timeoutMs: Long = defaultTimeoutMs): CallbackRecord {
+    fun pollForNextCallback(timeoutMs: Long = defaultTimeoutMs): CallbackEntry {
         return history.poll(timeoutMs) ?: fail("Did not receive callback after ${timeoutMs}ms")
     }
 
@@ -153,7 +172,7 @@
         if (null != cb) fail("Expected no callback but got $cb")
     }
 
-    inline fun <reified T : CallbackRecord> expectCallback(
+    inline fun <reified T : CallbackEntry> expectCallback(
         network: Network,
         timeoutMs: Long = defaultTimeoutMs
     ): T = pollForNextCallback(timeoutMs).let {
@@ -166,7 +185,7 @@
 
     fun expectCallbackThat(
         timeoutMs: Long = defaultTimeoutMs,
-        valid: (CallbackRecord) -> Boolean
+        valid: (CallbackEntry) -> Boolean
     ) = pollForNextCallback(timeoutMs).also { assertTrue(valid(it), "Unexpected callback : $it") }
 
     fun expectCapabilitiesThat(
@@ -209,7 +228,7 @@
     ) {
         expectCallback<Available>(net, tmt)
         if (suspended) {
-            expectCallback<CallbackRecord.Suspended>(net, tmt)
+            expectCallback<CallbackEntry.Suspended>(net, tmt)
         }
         expectCapabilitiesThat(net, tmt) { validated == it.hasCapability(NET_CAPABILITY_VALIDATED) }
         expectCallback<LinkPropertiesChanged>(net, tmt)
@@ -257,7 +276,7 @@
     }
 
     @JvmOverloads
-    open fun <T : CallbackRecord> expectCallback(
+    open fun <T : CallbackEntry> expectCallback(
         type: KClass<T>,
         n: HasNetwork?,
         timeoutMs: Long = defaultTimeoutMs
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 9c1c791..03bcf95 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -22,9 +22,10 @@
     resource_dirs: ["res"],
     static_libs: [
         "androidx.test.rules",
+        "kotlin-reflect",
         "mockito-target-extended-minus-junit4",
         "net-tests-utils",
-        "NetworkStackBase",
+        "NetworkStackApiCurrentLib",
         "testables",
     ],
     libs: [
@@ -60,8 +61,6 @@
         "libdl_android",
         "libhidl-gen-utils",
         "libhidlbase",
-        "libhidltransport",
-        "libhwbinder",
         "libjsoncpp",
         "liblog",
         "liblzma",
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java
index 8f2b968..8b02b49 100644
--- a/tests/unit/src/android/net/apf/ApfTest.java
+++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -28,6 +28,7 @@
 
 import static com.android.internal.util.BitUtils.bytesToBEInt;
 import static com.android.server.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
+import static com.android.server.util.NetworkStackConstants.IPV6_ADDR_LEN;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -38,6 +39,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
+import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NattKeepalivePacketDataParcelable;
@@ -85,6 +87,7 @@
 import java.io.OutputStream;
 import java.net.InetAddress;
 import java.nio.ByteBuffer;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Random;
 
@@ -119,7 +122,7 @@
     private static final int MIN_PKT_SIZE = 15;
 
     private static final ApfCapabilities MOCK_APF_CAPABILITIES =
-      new ApfCapabilities(2, 1700, ARPHRD_ETHER);
+            new ApfCapabilities(2, 4096, ARPHRD_ETHER);
 
     private static final boolean DROP_MULTICAST = true;
     private static final boolean ALLOW_MULTICAST = false;
@@ -184,7 +187,7 @@
 
     private void assertProgramEquals(byte[] expected, byte[] program) throws AssertionError {
         // assertArrayEquals() would only print one byte, making debugging difficult.
-        if (!java.util.Arrays.equals(expected, program)) {
+        if (!Arrays.equals(expected, program)) {
             throw new AssertionError(
                     "\nexpected: " + HexDump.toHexString(expected) +
                     "\nactual:   " + HexDump.toHexString(program));
@@ -197,7 +200,7 @@
         assertReturnCodesEqual(expected, apfSimulate(program, packet, data, 0 /* filterAge */));
 
         // assertArrayEquals() would only print one byte, making debugging difficult.
-        if (!java.util.Arrays.equals(expected_data, data)) {
+        if (!Arrays.equals(expected_data, data)) {
             throw new Exception(
                     "\nprogram:     " + HexDump.toHexString(program) +
                     "\ndata memory: " + HexDump.toHexString(data) +
@@ -1030,6 +1033,7 @@
             {(byte) 255, (byte) 255, (byte) 255, (byte) 255};
 
     private static final int IPV6_HEADER_LEN             = 40;
+    private static final int IPV6_PAYLOAD_LENGTH_OFFSET  = ETH_HEADER_LEN + 4;
     private static final int IPV6_NEXT_HEADER_OFFSET     = ETH_HEADER_LEN + 6;
     private static final int IPV6_SRC_ADDR_OFFSET        = ETH_HEADER_LEN + 8;
     private static final int IPV6_DEST_ADDR_OFFSET       = ETH_HEADER_LEN + 24;
@@ -1799,6 +1803,111 @@
         return packet.array();
     }
 
+    private void addRdnssOption(ByteBuffer packet, int lifetime, String... servers)
+            throws Exception {
+        int optionLength = 1 + 2 * servers.length;   // In 8-byte units
+        packet.put((byte) ICMP6_RDNSS_OPTION_TYPE);  // Type
+        packet.put((byte) optionLength);             // Length
+        packet.putShort((short) 0);                  // Reserved
+        packet.putInt(lifetime);                     // Lifetime
+        for (String server : servers) {
+            packet.put(InetAddress.getByName(server).getAddress());
+        }
+    }
+
+    private void addRioOption(ByteBuffer packet, int lifetime, String prefixString)
+            throws Exception {
+        IpPrefix prefix = new IpPrefix(prefixString);
+
+        int optionLength;
+        if (prefix.getPrefixLength() == 0) {
+            optionLength = 1;
+        } else if (prefix.getPrefixLength() <= 64) {
+            optionLength = 2;
+        } else {
+            optionLength = 3;
+        }
+
+        packet.put((byte) ICMP6_ROUTE_INFO_OPTION_TYPE);  // Type
+        packet.put((byte) optionLength);                  // Length in 8-byte units
+        packet.put((byte) prefix.getPrefixLength());      // Prefix length
+        packet.put((byte) 0b00011000);                    // Pref = high
+        packet.putInt(lifetime);                          // Lifetime
+
+        byte[] prefixBytes = prefix.getRawAddress();
+        packet.put(prefixBytes, 0, (optionLength - 1) * 8);
+    }
+
+    private void addPioOption(ByteBuffer packet, int valid, int preferred, String prefixString) {
+        IpPrefix prefix = new IpPrefix(prefixString);
+        packet.put((byte) ICMP6_PREFIX_OPTION_TYPE);  // Type
+        packet.put((byte) 4);                         // Length in 8-byte units
+        packet.put((byte) prefix.getPrefixLength());  // Prefix length
+        packet.put((byte) 0b11000000);                // L = 1, A = 1
+        packet.putInt(valid);
+        packet.putInt(preferred);
+        packet.putInt(0);                             // Reserved
+        packet.put(prefix.getRawAddress());
+    }
+
+    private byte[] buildLargeRa() throws Exception {
+        InetAddress src = InetAddress.getByName("fe80::1234:abcd");
+
+        ByteBuffer packet = ByteBuffer.wrap(new byte[1514]);
+        packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IPV6);
+        packet.position(ETH_HEADER_LEN);
+
+        packet.putInt(0x60012345);                                  // Version, tclass, flowlabel
+        packet.putShort((short) 0);                                 // Payload length; updated later
+        packet.put((byte) IPPROTO_ICMPV6);                          // Next header
+        packet.put((byte) 0xff);                                    // Hop limit
+        packet.put(src.getAddress());                               // Source address
+        packet.put(IPV6_ALL_NODES_ADDRESS);                         // Destination address
+
+        packet.put((byte) ICMP6_ROUTER_ADVERTISEMENT);              // Type
+        packet.put((byte) 0);                                       // Code (0)
+        packet.putShort((short) 0);                                 // Checksum (ignored)
+        packet.put((byte) 64);                                      // Hop limit
+        packet.put((byte) 0);                                       // M/O, reserved
+        packet.putShort((short) 1800);                              // Router lifetime
+        packet.putInt(30_000);                                      // Reachable time
+        packet.putInt(1000);                                        // Retrans timer
+
+        addRioOption(packet, 1200, "64:ff9b::/96");
+        addRdnssOption(packet, 7200, "2001:db8:1::1", "2001:db8:1::2");
+        addRioOption(packet, 2100, "2000::/3");
+        addRioOption(packet, 2400, "::/0");
+        addPioOption(packet, 600, 300, "2001:db8:a::/64");
+        addRioOption(packet, 1500, "2001:db8:c:d::/64");
+        addPioOption(packet, 86400, 43200, "fd95:d1e:12::/64");
+
+        int length = packet.position();
+        packet.putShort(IPV6_PAYLOAD_LENGTH_OFFSET, (short) length);
+
+        // Don't pass the Ra constructor a packet that is longer than the actual RA.
+        // This relies on the fact that all the relative writes to the byte buffer are at the end.
+        byte[] packetArray = new byte[length];
+        packet.rewind();
+        packet.get(packetArray);
+        return packetArray;
+    }
+
+    @Test
+    public void testRaToString() throws Exception {
+        MockIpClientCallback cb = new MockIpClientCallback();
+        ApfConfiguration config = getDefaultConfig();
+        TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog);
+
+        byte[] packet = buildLargeRa();
+        ApfFilter.Ra ra = apfFilter.new Ra(packet, packet.length);
+        String expected = "RA fe80::1234:abcd -> ff02::1 1800s "
+                + "2001:db8:a::/64 600s/300s fd95:d1e:12::/64 86400s/43200s "
+                + "DNS 7200s 2001:db8:1::1 2001:db8:1::2 "
+                + "RIO 1200s 64:ff9b::/96 RIO 2100s 2000::/3 "
+                + "RIO 2400s ::/0 RIO 1500s 2001:db8:c:d::/64 ";
+        assertEquals(expected, ra.toString());
+    }
+
     // Verify that the last program pushed to the IpClient.Callback properly filters the
     // given packet for the given lifetime.
     private void verifyRaLifetime(byte[] program, ByteBuffer packet, int lifetime) {
@@ -1934,38 +2043,27 @@
                 new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_LEN]);
         basePacket.clear();
         prefixOptionPacket.put(basePacket);
-        prefixOptionPacket.put((byte)ICMP6_PREFIX_OPTION_TYPE);
-        prefixOptionPacket.put((byte)(ICMP6_PREFIX_OPTION_LEN / 8));
-        prefixOptionPacket.putInt(
-                ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET,
-                PREFIX_PREFERRED_LIFETIME);
-        prefixOptionPacket.putInt(
-                ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET,
-                PREFIX_VALID_LIFETIME);
+        addPioOption(prefixOptionPacket, PREFIX_VALID_LIFETIME, PREFIX_PREFERRED_LIFETIME,
+                "2001:db8::/64");
         verifyRaLifetime(
                 apfFilter, ipClientCallback, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
         verifyRaEvent(new RaEvent(
                 ROUTER_LIFETIME, PREFIX_VALID_LIFETIME, PREFIX_PREFERRED_LIFETIME, -1, -1, -1));
 
         ByteBuffer rdnssOptionPacket = ByteBuffer.wrap(
-                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
+                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN + 2 * IPV6_ADDR_LEN]);
         basePacket.clear();
         rdnssOptionPacket.put(basePacket);
-        rdnssOptionPacket.put((byte)ICMP6_RDNSS_OPTION_TYPE);
-        rdnssOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
-        rdnssOptionPacket.putInt(
-                ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, RDNSS_LIFETIME);
+        addRdnssOption(rdnssOptionPacket, RDNSS_LIFETIME,
+                "2001:4860:4860::8888", "2001:4860:4860::8844");
         verifyRaLifetime(apfFilter, ipClientCallback, rdnssOptionPacket, RDNSS_LIFETIME);
         verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, RDNSS_LIFETIME, -1));
 
         ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap(
-                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
+                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN + IPV6_ADDR_LEN]);
         basePacket.clear();
         routeInfoOptionPacket.put(basePacket);
-        routeInfoOptionPacket.put((byte)ICMP6_ROUTE_INFO_OPTION_TYPE);
-        routeInfoOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
-        routeInfoOptionPacket.putInt(
-                ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, ROUTE_LIFETIME);
+        addRioOption(routeInfoOptionPacket, ROUTE_LIFETIME, "64:ff9b::/96");
         verifyRaLifetime(apfFilter, ipClientCallback, routeInfoOptionPacket, ROUTE_LIFETIME);
         verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, ROUTE_LIFETIME, -1, -1));
 
@@ -1980,7 +2078,11 @@
         verifyRaLifetime(apfFilter, ipClientCallback, dnsslOptionPacket, ROUTER_LIFETIME);
         verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, -1, DNSSL_LIFETIME));
 
-        // Verify that current program filters all five RAs:
+        ByteBuffer largeRaPacket = ByteBuffer.wrap(buildLargeRa());
+        verifyRaLifetime(apfFilter, ipClientCallback, largeRaPacket, 300);
+        verifyRaEvent(new RaEvent(1800, 600, 300, 1200, 7200, -1));
+
+        // Verify that current program filters all the RAs:
         program = ipClientCallback.getApfProgram();
         verifyRaLifetime(program, basePacket, ROUTER_LIFETIME);
         verifyRaLifetime(program, newFlowLabelPacket, ROUTER_LIFETIME);
@@ -1988,6 +2090,7 @@
         verifyRaLifetime(program, rdnssOptionPacket, RDNSS_LIFETIME);
         verifyRaLifetime(program, routeInfoOptionPacket, ROUTE_LIFETIME);
         verifyRaLifetime(program, dnsslOptionPacket, ROUTER_LIFETIME);
+        verifyRaLifetime(program, largeRaPacket, 300);
 
         apfFilter.shutdown();
     }
diff --git a/tests/unit/src/android/net/ip/IpClientTest.java b/tests/unit/src/android/net/ip/IpClientTest.java
index 1d6ce6e..b3e652e 100644
--- a/tests/unit/src/android/net/ip/IpClientTest.java
+++ b/tests/unit/src/android/net/ip/IpClientTest.java
@@ -16,6 +16,8 @@
 
 package android.net.ip;
 
+import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -31,12 +33,15 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import static java.util.Collections.emptySet;
+
 import android.app.AlarmManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.INetd;
+import android.net.InetAddresses;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
@@ -51,11 +56,11 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.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.HandlerUtilsKt;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -64,6 +69,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.util.Arrays;
 import java.util.HashSet;
@@ -77,8 +84,6 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class IpClientTest {
-    private static final int DEFAULT_AVOIDBADWIFI_CONFIG_VALUE = 1;
-
     private static final String VALID = "VALID";
     private static final String INVALID = "INVALID";
     private static final String TEST_IFNAME = "test_wlan0";
@@ -89,6 +94,19 @@
     private static final String TEST_L2KEY = "some l2key";
     private static final String TEST_GROUPHINT = "some grouphint";
 
+    private static final String TEST_GLOBAL_ADDRESS = "1234:4321::548d:2db2:4fcf:ef75/64";
+    private static final String[] TEST_LOCAL_ADDRESSES = {
+            "fe80::a4be:f92:e1f7:22d1/64",
+            "fe80::f04a:8f6:6a32:d756/64",
+            "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"
+    };
+    private static final String TEST_IPV4_LINKADDRESS = "192.168.42.24/24";
+    private static final String[] TEST_PREFIXES = { "fe80::/64", "fd2c:4e57:8e3c::/64" };
+    private static final String[] TEST_DNSES = { "fd2c:4e57:8e3c::42" };
+    private static final String TEST_IPV6_GATEWAY = "fd2c:4e57:8e3c::43";
+    private static final String TEST_IPV4_GATEWAY = "192.168.42.11";
+    private static final long TEST_DNS_LIFETIME = 3600;
+
     @Mock private Context mContext;
     @Mock private ConnectivityManager mCm;
     @Mock private NetworkObserverRegistry mObserverRegistry;
@@ -114,8 +132,7 @@
         when(mContext.getSystemService(eq(ConnectivityManager.class))).thenReturn(mCm);
         when(mContext.getResources()).thenReturn(mResources);
         when(mDependencies.getNetd(any())).thenReturn(mNetd);
-        when(mResources.getInteger(R.integer.config_networkAvoidBadWifi))
-                .thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE);
+        when(mCm.shouldAvoidBadWifi()).thenReturn(true);
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
         when(mNetworkStackServiceManager.getIpMemoryStoreService())
                 .thenReturn(mIpMemoryStoreService);
@@ -209,10 +226,20 @@
         ipc.shutdown();
     }
 
-    @Test
-    public void testDefaultProvisioningConfiguration() throws Exception {
-        final String iface = TEST_IFNAME;
-        final IpClient ipc = makeIpClient(iface);
+    private LinkProperties makeIPv6ProvisionedLinkProperties() {
+        // Add local addresses, and a global address with global scope
+        final Set<LinkAddress> addresses = links(TEST_LOCAL_ADDRESSES);
+        addresses.add(new LinkAddress(TEST_GLOBAL_ADDRESS, 0, RT_SCOPE_UNIVERSE));
+
+        // Add a route on the interface for each prefix, and a global route
+        final Set<RouteInfo> routes = routes(TEST_PREFIXES);
+        routes.add(defaultIPV6Route(TEST_IPV6_GATEWAY));
+
+        return linkproperties(addresses, routes, ips(TEST_DNSES));
+    }
+
+    private IpClient doProvisioningWithDefaultConfiguration() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
 
         ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
                 .withoutIPv4()
@@ -224,34 +251,115 @@
         ipc.startProvisioning(config);
         verify(mCb, times(1)).setNeighborDiscoveryOffload(true);
         verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setFallbackMulticastFilter(false);
+
+        final LinkProperties lp = makeIPv6ProvisionedLinkProperties();
+        lp.getRoutes().forEach(mObserver::onRouteUpdated);
+        lp.getLinkAddresses().forEach(la -> mObserver.onInterfaceAddressUpdated(la, TEST_IFNAME));
+        mObserver.onInterfaceDnsServerInfo(TEST_IFNAME, TEST_DNS_LIFETIME,
+                lp.getDnsServers().stream().map(InetAddress::getHostAddress)
+                        .toArray(String[]::new));
+
+        HandlerUtilsKt.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
         verify(mCb, never()).onProvisioningFailure(any());
         verify(mIpMemoryStore, never()).storeNetworkAttributes(any(), any(), any());
 
-        ipc.shutdown();
-        verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false);
-        verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface);
-        verify(mCb, timeout(TEST_TIMEOUT_MS).times(1))
-                .onLinkPropertiesChange(makeEmptyLinkProperties(iface));
+        verify(mCb).onProvisioningSuccess(lp);
+        return ipc;
+    }
+
+    private void addIPv4Provisioning(LinkProperties lp) {
+        final LinkAddress la = new LinkAddress(TEST_IPV4_LINKADDRESS);
+        final RouteInfo defaultRoute = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
+                InetAddresses.parseNumericAddress(TEST_IPV4_GATEWAY), TEST_IFNAME);
+        mObserver.onInterfaceAddressUpdated(la, TEST_IFNAME);
+        mObserver.onRouteUpdated(defaultRoute);
+
+        lp.addLinkAddress(la);
+        lp.addRoute(defaultRoute);
+    }
+
+    /**
+     * Simulate loss of IPv6 provisioning (default route lost).
+     *
+     * @return The expected new LinkProperties.
+     */
+    private void doIPv6ProvisioningLoss(LinkProperties lp) {
+        final RouteInfo defaultRoute = defaultIPV6Route(TEST_IPV6_GATEWAY);
+        mObserver.onRouteRemoved(defaultRoute);
+
+        lp.removeRoute(defaultRoute);
+    }
+
+    private void doDefaultIPv6ProvisioningConfigurationAndProvisioningLossTest(boolean avoidBadWifi)
+            throws Exception {
+        when(mCm.shouldAvoidBadWifi()).thenReturn(avoidBadWifi);
+        final IpClient ipc = doProvisioningWithDefaultConfiguration();
+        final LinkProperties lp = makeIPv6ProvisionedLinkProperties();
+
+        reset(mCb);
+        doIPv6ProvisioningLoss(lp);
+        HandlerUtilsKt.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
+        verify(mCb).onProvisioningFailure(lp);
+        verify(mCb).onLinkPropertiesChange(makeEmptyLinkProperties(TEST_IFNAME));
+
+        verifyShutdown(ipc);
     }
 
     @Test
+    public void testDefaultIPv6ProvisioningConfiguration_AvoidBadWifi() throws Exception {
+        doDefaultIPv6ProvisioningConfigurationAndProvisioningLossTest(true /* avoidBadWifi */);
+    }
+
+    @Test
+    public void testDefaultIPv6ProvisioningConfiguration_StayOnBadWifi() throws Exception {
+        // Even when avoidBadWifi=false, if IPv6 only, loss of all provisioning causes
+        // onProvisioningFailure to be called.
+        doDefaultIPv6ProvisioningConfigurationAndProvisioningLossTest(false /* avoidBadWifi */);
+    }
+
+    private void doDefaultDualStackProvisioningConfigurationTest(
+            boolean avoidBadWifi) throws Exception {
+        when(mCm.shouldAvoidBadWifi()).thenReturn(avoidBadWifi);
+        final IpClient ipc = doProvisioningWithDefaultConfiguration();
+        final LinkProperties lp = makeIPv6ProvisionedLinkProperties();
+        addIPv4Provisioning(lp);
+        HandlerUtilsKt.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
+
+        reset(mCb);
+        doIPv6ProvisioningLoss(lp);
+        HandlerUtilsKt.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
+        if (avoidBadWifi) { // Provisioning failure is expected only when avoidBadWifi is true
+            verify(mCb).onProvisioningFailure(lp);
+            verify(mCb).onLinkPropertiesChange(makeEmptyLinkProperties(TEST_IFNAME));
+        } else {
+            verify(mCb, never()).onProvisioningFailure(any());
+            verify(mCb).onLinkPropertiesChange(lp);
+        }
+
+        verifyShutdown(ipc);
+    }
+
+    @Test
+    public void testDefaultDualStackProvisioningConfiguration_AvoidBadWifi() throws Exception {
+        doDefaultDualStackProvisioningConfigurationTest(true /* avoidBadWifi */);
+    }
+
+    @Test
+    public void testDefaultDualStackProvisioningConfiguration_StayOnBadWifi() throws Exception {
+        doDefaultDualStackProvisioningConfigurationTest(false /* avoidBadWifi */);
+    }
+
     public void testProvisioningWithInitialConfiguration() throws Exception {
         final String iface = TEST_IFNAME;
         final IpClient ipc = makeIpClient(iface);
         final String l2Key = TEST_L2KEY;
         final String groupHint = TEST_GROUPHINT;
 
-        String[] addresses = {
-            "fe80::a4be:f92:e1f7:22d1/64",
-            "fe80::f04a:8f6:6a32:d756/64",
-            "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"
-        };
-        String[] prefixes = { "fe80::/64", "fd2c:4e57:8e3c::/64" };
-
         ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
                 .withoutIPv4()
                 .withoutIpReachabilityMonitor()
-                .withInitialConfiguration(conf(links(addresses), prefixes(prefixes), ips()))
+                .withInitialConfiguration(
+                        conf(links(TEST_LOCAL_ADDRESSES), prefixes(TEST_PREFIXES), ips()))
                 .build();
 
         ipc.startProvisioning(config);
@@ -260,35 +368,38 @@
         verify(mCb, never()).onProvisioningFailure(any());
         ipc.setL2KeyAndGroupHint(l2Key, groupHint);
 
-        for (String addr : addresses) {
+        for (String addr : TEST_LOCAL_ADDRESSES) {
             String[] parts = addr.split("/");
             verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1))
                     .interfaceAddAddress(iface, parts[0], Integer.parseInt(parts[1]));
         }
 
-        final int lastAddr = addresses.length - 1;
+        final int lastAddr = TEST_LOCAL_ADDRESSES.length - 1;
 
         // Add N - 1 addresses
         for (int i = 0; i < lastAddr; i++) {
-            mObserver.onInterfaceAddressUpdated(new LinkAddress(addresses[i]), iface);
+            mObserver.onInterfaceAddressUpdated(new LinkAddress(TEST_LOCAL_ADDRESSES[i]), iface);
             verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(any());
             reset(mCb);
         }
 
         // Add Nth address
-        mObserver.onInterfaceAddressUpdated(new LinkAddress(addresses[lastAddr]), iface);
-        LinkProperties want = linkproperties(links(addresses), routes(prefixes));
+        mObserver.onInterfaceAddressUpdated(new LinkAddress(TEST_LOCAL_ADDRESSES[lastAddr]), iface);
+        LinkProperties want = linkproperties(links(TEST_LOCAL_ADDRESSES),
+                routes(TEST_PREFIXES), emptySet() /* dnses */);
         want.setInterfaceName(iface);
         verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(want);
         verifyNetworkAttributesStored(l2Key, new NetworkAttributes.Builder()
                 .setGroupHint(groupHint)
                 .build());
+    }
 
+    private void verifyShutdown(IpClient ipc) throws Exception {
         ipc.shutdown();
-        verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false);
-        verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface);
+        verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(TEST_IFNAME, false);
+        verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(TEST_IFNAME);
         verify(mCb, timeout(TEST_TIMEOUT_MS).times(1))
-                .onLinkPropertiesChange(makeEmptyLinkProperties(iface));
+                .onLinkPropertiesChange(makeEmptyLinkProperties(TEST_IFNAME));
         verifyNoMoreInteractions(mIpMemoryStore);
     }
 
@@ -360,7 +471,7 @@
             Set<RouteInfo> lpRoutes, Set<InetAddress> lpDns, InitialConfiguration config) {
         IsProvisionedTestCase testcase = new IsProvisionedTestCase();
         testcase.isProvisioned = isProvisioned;
-        testcase.lp = new LinkProperties();
+        testcase.lp = makeEmptyLinkProperties(TEST_IFNAME);
         testcase.lp.setLinkAddresses(lpAddrs);
         for (RouteInfo route : lpRoutes) {
             testcase.lp.addRoute(route);
@@ -456,12 +567,12 @@
         return testcase;
     }
 
-    static LinkProperties linkproperties(Set<LinkAddress> addresses, Set<RouteInfo> routes) {
-        LinkProperties lp = new LinkProperties();
+    static LinkProperties linkproperties(Set<LinkAddress> addresses,
+            Set<RouteInfo> routes, Set<InetAddress> dnses) {
+        LinkProperties lp = makeEmptyLinkProperties(TEST_IFNAME);
         lp.setLinkAddresses(addresses);
-        for (RouteInfo route : routes) {
-            lp.addRoute(route);
-        }
+        routes.forEach(lp::addRoute);
+        dnses.forEach(lp::addDnsServer);
         return lp;
     }
 
@@ -479,7 +590,13 @@
     }
 
     static Set<RouteInfo> routes(String... routes) {
-        return mapIntoSet(routes, (r) -> new RouteInfo(new IpPrefix(r)));
+        return mapIntoSet(routes, (r) -> new RouteInfo(new IpPrefix(r), null /* gateway */,
+                TEST_IFNAME));
+    }
+
+    static RouteInfo defaultIPV6Route(String gateway) {
+        return new RouteInfo(new IpPrefix(Inet6Address.ANY, 0),
+                InetAddresses.parseNumericAddress(gateway), TEST_IFNAME);
     }
 
     static Set<IpPrefix> prefixes(String... prefixes) {
diff --git a/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt b/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt
new file mode 100644
index 0000000..4e4d25a
--- /dev/null
+++ b/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt
@@ -0,0 +1,293 @@
+package android.net.testutils
+
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.Network
+import android.net.NetworkCapabilities
+import com.android.testutils.ConcurrentIntepreter
+import com.android.testutils.InterpretMatcher
+import com.android.testutils.RecorderCallback.CallbackEntry
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
+import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
+import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.intArg
+import com.android.testutils.strArg
+import com.android.testutils.timeArg
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.reflect.KClass
+import kotlin.test.assertEquals
+import kotlin.test.assertFails
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+const val SHORT_TIMEOUT_MS = 20L
+const val DEFAULT_LINGER_DELAY_MS = 30000
+const val NOT_METERED = NetworkCapabilities.NET_CAPABILITY_NOT_METERED
+const val WIFI = NetworkCapabilities.TRANSPORT_WIFI
+const val CELLULAR = NetworkCapabilities.TRANSPORT_CELLULAR
+const val TEST_INTERFACE_NAME = "testInterfaceName"
+
+@RunWith(JUnit4::class)
+class TestableNetworkCallbackTest {
+    private lateinit var mCallback: TestableNetworkCallback
+
+    private fun makeHasNetwork(netId: Int) = object : TestableNetworkCallback.HasNetwork {
+        override val network: Network = Network(netId)
+    }
+
+    @Before
+    fun setUp() {
+        mCallback = TestableNetworkCallback()
+    }
+
+    @Test
+    fun testLastAvailableNetwork() {
+        // Make sure there is no last available network at first, then the last available network
+        // is returned after onAvailable is called.
+        val net2097 = Network(2097)
+        assertNull(mCallback.lastAvailableNetwork)
+        mCallback.onAvailable(net2097)
+        assertEquals(mCallback.lastAvailableNetwork, net2097)
+
+        // Make sure calling onCapsChanged/onLinkPropertiesChanged don't affect the last available
+        // network.
+        mCallback.onCapabilitiesChanged(net2097, NetworkCapabilities())
+        mCallback.onLinkPropertiesChanged(net2097, LinkProperties())
+        assertEquals(mCallback.lastAvailableNetwork, net2097)
+
+        // Make sure onLost clears the last available network.
+        mCallback.onLost(net2097)
+        assertNull(mCallback.lastAvailableNetwork)
+
+        // Do the same but with a different network after onLost : make sure the last available
+        // network is the new one, not the original one.
+        val net2098 = Network(2098)
+        mCallback.onAvailable(net2098)
+        mCallback.onCapabilitiesChanged(net2098, NetworkCapabilities())
+        mCallback.onLinkPropertiesChanged(net2098, LinkProperties())
+        assertEquals(mCallback.lastAvailableNetwork, net2098)
+
+        // Make sure onAvailable changes the last available network even if onLost was not called.
+        val net2099 = Network(2099)
+        mCallback.onAvailable(net2099)
+        assertEquals(mCallback.lastAvailableNetwork, net2099)
+
+        // For legacy reasons, lastAvailableNetwork is null as soon as any is lost, not necessarily
+        // the last available one. Check that behavior.
+        mCallback.onLost(net2098)
+        assertNull(mCallback.lastAvailableNetwork)
+
+        // Make sure that losing the really last available one still results in null.
+        mCallback.onLost(net2099)
+        assertNull(mCallback.lastAvailableNetwork)
+
+        // Make sure multiple onAvailable in a row then onLost still results in null.
+        mCallback.onAvailable(net2097)
+        mCallback.onAvailable(net2098)
+        mCallback.onAvailable(net2099)
+        mCallback.onLost(net2097)
+        assertNull(mCallback.lastAvailableNetwork)
+    }
+
+    @Test
+    fun testAssertNoCallback() {
+        mCallback.assertNoCallback(SHORT_TIMEOUT_MS)
+        mCallback.onAvailable(Network(100))
+        assertFails { mCallback.assertNoCallback(SHORT_TIMEOUT_MS) }
+    }
+
+    @Test
+    fun testCapabilitiesWithAndWithout() {
+        val net = Network(101)
+        val matcher = makeHasNetwork(101)
+        val meteredNc = NetworkCapabilities()
+        val unmeteredNc = NetworkCapabilities().addCapability(NOT_METERED)
+        // Check that expecting caps (with or without) fails when no callback has been received.
+        assertFails { mCallback.expectCapabilitiesWith(NOT_METERED, matcher, SHORT_TIMEOUT_MS) }
+        assertFails { mCallback.expectCapabilitiesWithout(NOT_METERED, matcher, SHORT_TIMEOUT_MS) }
+
+        // Add NOT_METERED and check that With succeeds and Without fails.
+        mCallback.onCapabilitiesChanged(net, unmeteredNc)
+        mCallback.expectCapabilitiesWith(NOT_METERED, matcher)
+        mCallback.onCapabilitiesChanged(net, unmeteredNc)
+        assertFails { mCallback.expectCapabilitiesWithout(NOT_METERED, matcher, SHORT_TIMEOUT_MS) }
+
+        // Don't add NOT_METERED and check that With fails and Without succeeds.
+        mCallback.onCapabilitiesChanged(net, meteredNc)
+        assertFails { mCallback.expectCapabilitiesWith(NOT_METERED, matcher, SHORT_TIMEOUT_MS) }
+        mCallback.onCapabilitiesChanged(net, meteredNc)
+        mCallback.expectCapabilitiesWithout(NOT_METERED, matcher)
+    }
+
+    @Test
+    fun testExpectCallbackThat() {
+        val net = Network(193)
+        val netCaps = NetworkCapabilities().addTransportType(CELLULAR)
+        // Check that expecting callbackThat anything fails when no callback has been received.
+        assertFails { mCallback.expectCallbackThat(SHORT_TIMEOUT_MS) { true } }
+
+        // Basic test for true and false
+        mCallback.onAvailable(net)
+        mCallback.expectCallbackThat { true }
+        mCallback.onAvailable(net)
+        assertFails { mCallback.expectCallbackThat(SHORT_TIMEOUT_MS) { false } }
+
+        // Try a positive and a negative case
+        mCallback.onBlockedStatusChanged(net, true)
+        mCallback.expectCallbackThat { cb -> cb is BlockedStatus && cb.blocked }
+        mCallback.onCapabilitiesChanged(net, netCaps)
+        assertFails { mCallback.expectCallbackThat(SHORT_TIMEOUT_MS) { cb ->
+            cb is CapabilitiesChanged && cb.caps.hasTransport(WIFI)
+        } }
+    }
+
+    @Test
+    fun testCapabilitiesThat() {
+        val net = Network(101)
+        val netCaps = NetworkCapabilities().addCapability(NOT_METERED).addTransportType(WIFI)
+        // Check that expecting capabilitiesThat anything fails when no callback has been received.
+        assertFails { mCallback.expectCapabilitiesThat(net, SHORT_TIMEOUT_MS) { true } }
+
+        // Basic test for true and false
+        mCallback.onCapabilitiesChanged(net, netCaps)
+        mCallback.expectCapabilitiesThat(net) { true }
+        mCallback.onCapabilitiesChanged(net, netCaps)
+        assertFails { mCallback.expectCapabilitiesThat(net, SHORT_TIMEOUT_MS) { false } }
+
+        // Try a positive and a negative case
+        mCallback.onCapabilitiesChanged(net, netCaps)
+        mCallback.expectCapabilitiesThat(net) { caps ->
+            caps.hasCapability(NOT_METERED) &&
+                    caps.hasTransport(WIFI) &&
+                    !caps.hasTransport(CELLULAR)
+        }
+        mCallback.onCapabilitiesChanged(net, netCaps)
+        assertFails { mCallback.expectCapabilitiesThat(net, SHORT_TIMEOUT_MS) { caps ->
+            caps.hasTransport(CELLULAR)
+        } }
+
+        // Try a matching callback on the wrong network
+        mCallback.onCapabilitiesChanged(net, netCaps)
+        assertFails { mCallback.expectCapabilitiesThat(Network(100), SHORT_TIMEOUT_MS) { true } }
+    }
+
+    @Test
+    fun testLinkPropertiesThat() {
+        val net = Network(112)
+        val linkAddress = LinkAddress("fe80::ace:d00d/64")
+        val mtu = 1984
+        val linkProps = LinkProperties().apply {
+            this.mtu = mtu
+            interfaceName = TEST_INTERFACE_NAME
+            addLinkAddress(linkAddress)
+        }
+
+        // Check that expecting linkPropsThat anything fails when no callback has been received.
+        assertFails { mCallback.expectLinkPropertiesThat(net, SHORT_TIMEOUT_MS) { true } }
+
+        // Basic test for true and false
+        mCallback.onLinkPropertiesChanged(net, linkProps)
+        mCallback.expectLinkPropertiesThat(net) { true }
+        mCallback.onLinkPropertiesChanged(net, linkProps)
+        assertFails { mCallback.expectLinkPropertiesThat(net, SHORT_TIMEOUT_MS) { false } }
+
+        // Try a positive and negative case
+        mCallback.onLinkPropertiesChanged(net, linkProps)
+        mCallback.expectLinkPropertiesThat(net) { lp ->
+            lp.interfaceName == TEST_INTERFACE_NAME &&
+                    lp.linkAddresses.contains(linkAddress) &&
+                    lp.mtu == mtu
+        }
+        mCallback.onLinkPropertiesChanged(net, linkProps)
+        assertFails { mCallback.expectLinkPropertiesThat(net, SHORT_TIMEOUT_MS) { lp ->
+            lp.interfaceName != TEST_INTERFACE_NAME
+        } }
+
+        // Try a matching callback on the wrong network
+        mCallback.onLinkPropertiesChanged(net, linkProps)
+        assertFails { mCallback.expectLinkPropertiesThat(Network(114), SHORT_TIMEOUT_MS) { lp ->
+            lp.interfaceName == TEST_INTERFACE_NAME
+        } }
+    }
+
+    @Test
+    fun testExpectCallback() {
+        val net = Network(103)
+        // Test expectCallback fails when nothing was sent.
+        assertFails { mCallback.expectCallback<BlockedStatus>(net, SHORT_TIMEOUT_MS) }
+
+        // Test onAvailable is seen and can be expected
+        mCallback.onAvailable(net)
+        mCallback.expectCallback<Available>(net, SHORT_TIMEOUT_MS)
+
+        // Test onAvailable won't return calls with a different network
+        mCallback.onAvailable(Network(106))
+        assertFails { mCallback.expectCallback<Available>(net, SHORT_TIMEOUT_MS) }
+
+        // Test onAvailable won't return calls with a different callback
+        mCallback.onAvailable(net)
+        assertFails { mCallback.expectCallback<BlockedStatus>(net, SHORT_TIMEOUT_MS) }
+    }
+
+    @Test
+    fun testPollForNextCallback() {
+        assertFails { mCallback.pollForNextCallback(SHORT_TIMEOUT_MS) }
+        TNCInterpreter.interpretTestSpec(initial = mCallback, lineShift = 1,
+                threadTransform = { cb -> cb.createLinkedCopy() }, spec = """
+            sleep; onAvailable(133)    | poll(2) = Available(133) time 1..4
+                                       | poll(1) fails
+            onCapabilitiesChanged(108) | poll(1) = CapabilitiesChanged(108) time 0..3
+            onBlockedStatus(199)       | poll(1) = BlockedStatus(199) time 0..3
+        """)
+    }
+}
+
+private object TNCInterpreter : ConcurrentIntepreter<TestableNetworkCallback>(interpretTable)
+
+val EntryList = CallbackEntry::class.sealedSubclasses.map { it.simpleName }.joinToString("|")
+private fun callbackEntryFromString(name: String): KClass<out CallbackEntry> {
+    return CallbackEntry::class.sealedSubclasses.first { it.simpleName == name }
+}
+
+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
+    // CallbackEntry and reading their simpleName.
+    Regex("""(.*)\s+=\s+($EntryList)\((\d+)\)""") to { i, cb, t ->
+        val record = i.interpret(t.strArg(1), cb)
+        assertTrue(callbackEntryFromString(t.strArg(2)).isInstance(record))
+        // Strictly speaking testing for is CallbackEntry is useless as it's been tested above
+        // but the compiler can't figure things out from the isInstance call. It does understand
+        // from the assertTrue(is CallbackEntry) that this is true, which allows to access
+        // the 'network' member below.
+        assertTrue(record is CallbackEntry)
+        assertEquals(record.network.netId, t.intArg(3))
+    },
+    // Interpret "onAvailable(xx)" as calling "onAvailable" with a netId of xx, and likewise for
+    // all callback types. NetworkCapabilities and LinkProperties just get an empty object
+    // as their argument. Losing gets the default linger timer. Blocked gets false.
+    Regex("""on($EntryList)\((\d+)\)""") to { i, cb, t ->
+        val net = Network(t.intArg(2))
+        when (t.strArg(1)) {
+            "Available" -> cb.onAvailable(net)
+            // PreCheck not used in tests. Add it here if it becomes useful.
+            "CapabilitiesChanged" -> cb.onCapabilitiesChanged(net, NetworkCapabilities())
+            "LinkPropertiesChanged" -> cb.onLinkPropertiesChanged(net, LinkProperties())
+            "Suspended" -> cb.onNetworkSuspended(net)
+            "Resumed" -> cb.onNetworkResumed(net)
+            "Losing" -> cb.onLosing(net, DEFAULT_LINGER_DELAY_MS)
+            "Lost" -> cb.onLost(net)
+            "Unavailable" -> cb.onUnavailable()
+            "BlockedStatus" -> cb.onBlockedStatusChanged(net, false)
+            else -> fail("Unknown callback type")
+        }
+    },
+    Regex("""poll\((\d+)\)""") to { i, cb, t ->
+        cb.pollForNextCallback(t.timeArg(1))
+    }
+)
diff --git a/tests/unit/src/android/net/testutils/TrackRecordTest.kt b/tests/unit/src/android/net/testutils/TrackRecordTest.kt
index f9d3558..995d537 100644
--- a/tests/unit/src/android/net/testutils/TrackRecordTest.kt
+++ b/tests/unit/src/android/net/testutils/TrackRecordTest.kt
@@ -352,12 +352,11 @@
 
 private object TRTInterpreter : ConcurrentIntepreter<TrackRecord<Int>>(interpretTable) {
     fun interpretTestSpec(spec: String, useReadHeads: Boolean) = if (useReadHeads) {
-        interpretTestSpec(spec, ArrayTrackRecord(), { (it as ArrayTrackRecord).newReadHead() })
+        interpretTestSpec(spec, initial = ArrayTrackRecord(),
+                threadTransform = { (it as ArrayTrackRecord).newReadHead() })
     } else {
         interpretTestSpec(spec, ArrayTrackRecord())
     }
-
-    override fun getCallingMethod() = getCallingMethod(4)
 }
 
 /*
diff --git a/tests/unit/src/android/net/util/NetworkStackUtilsTest.java b/tests/unit/src/android/net/util/NetworkStackUtilsTest.java
new file mode 100644
index 0000000..806054d
--- /dev/null
+++ b/tests/unit/src/android/net/util/NetworkStackUtilsTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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 com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.provider.DeviceConfig;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+
+
+/**
+ * Tests for NetworkStackUtils.
+ *
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkStackUtilsTest {
+    private static final String TEST_NAME_SPACE = "connectivity";
+    private static final String TEST_EXPERIMENT_FLAG = "experiment_flag";
+    private static final int TEST_FLAG_VERSION = 28;
+    private static final String TEST_FLAG_VERSION_STRING = "28";
+    private static final int TEST_DEFAULT_FLAG_VERSION = 0;
+    private static final long TEST_PACKAGE_VERSION = 290000000;
+    private static final String TEST_PACKAGE_NAME = "NetworkStackUtilsTest";
+    private MockitoSession mSession;
+
+    @Mock private Context mContext;
+    @Mock private PackageManager mPm;
+    @Mock private PackageInfo mPi;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mSession = mockitoSession().spyStatic(DeviceConfig.class).startMocking();
+
+        final PackageInfo pi = new PackageInfo();
+        pi.setLongVersionCode(TEST_PACKAGE_VERSION);
+
+        doReturn(mPm).when(mContext).getPackageManager();
+        doReturn(TEST_PACKAGE_NAME).when(mContext).getPackageName();
+        doReturn(pi).when(mPm).getPackageInfo(anyString(), anyInt());
+    }
+
+    @After
+    public void tearDown() {
+        mSession.finishMocking();
+    }
+
+    @Test
+    public void testGetDeviceConfigPropertyInt_Null() {
+        doReturn(null).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
+                eq(TEST_EXPERIMENT_FLAG)));
+        assertEquals(TEST_DEFAULT_FLAG_VERSION, NetworkStackUtils.getDeviceConfigPropertyInt(
+                TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG,
+                TEST_DEFAULT_FLAG_VERSION /* default value */));
+    }
+
+    @Test
+    public void testGetDeviceConfigPropertyInt_NotNull() {
+        doReturn(TEST_FLAG_VERSION_STRING).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
+                eq(TEST_EXPERIMENT_FLAG)));
+        assertEquals(TEST_FLAG_VERSION, NetworkStackUtils.getDeviceConfigPropertyInt(
+                TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG,
+                TEST_DEFAULT_FLAG_VERSION /* default value */));
+    }
+
+    @Test
+    public void testGetDeviceConfigPropertyBoolean_Null() {
+        doReturn(null).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
+                eq(TEST_EXPERIMENT_FLAG)));
+        assertFalse(NetworkStackUtils.getDeviceConfigPropertyBoolean(
+                TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG,
+                false /* default value */));
+    }
+
+    @Test
+    public void testGetDeviceConfigPropertyBoolean_NotNull() {
+        doReturn("true").when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
+                eq(TEST_EXPERIMENT_FLAG)));
+        assertTrue(NetworkStackUtils.getDeviceConfigPropertyBoolean(
+                TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG,
+                false /* default value */));
+    }
+
+    @Test
+    public void testFeatureIsEnabledWithExceptionsEnabled() {
+        doReturn(TEST_FLAG_VERSION_STRING).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
+                eq(TEST_EXPERIMENT_FLAG)));
+        assertTrue(NetworkStackUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG));
+    }
+
+    @Test
+    public void testFeatureIsNotEnabled() {
+        doReturn(null).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
+                eq(TEST_EXPERIMENT_FLAG)));
+        assertFalse(NetworkStackUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG));
+    }
+
+    @Test
+    public void testFeatureIsEnabledWithException() throws Exception {
+        doReturn(TEST_FLAG_VERSION_STRING).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
+                eq(TEST_EXPERIMENT_FLAG)));
+        doThrow(NameNotFoundException.class).when(mPm).getPackageInfo(anyString(), anyInt());
+        assertFalse(NetworkStackUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG));
+    }
+}
diff --git a/tests/unit/src/android/networkstack/metrics/DataStallStatsUtilsTest.kt b/tests/unit/src/android/networkstack/metrics/DataStallStatsUtilsTest.kt
new file mode 100644
index 0000000..3b53b5f
--- /dev/null
+++ b/tests/unit/src/android/networkstack/metrics/DataStallStatsUtilsTest.kt
@@ -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 permissions and
+ * limitations under the License.
+ */
+
+package android.networkstack.metrics
+
+import android.net.captiveportal.CaptivePortalProbeResult
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.networkstack.metrics.DataStallStatsUtils
+import com.android.server.connectivity.nano.DataStallEventProto
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class DataStallStatsUtilsTest {
+    @Test
+    fun testProbeResultToEnum() {
+        assertEquals(DataStallStatsUtils.probeResultToEnum(null), DataStallEventProto.INVALID)
+        assertEquals(DataStallStatsUtils.probeResultToEnum(CaptivePortalProbeResult.FAILED),
+                DataStallEventProto.INVALID)
+        assertEquals(DataStallStatsUtils.probeResultToEnum(CaptivePortalProbeResult.SUCCESS),
+                DataStallEventProto.VALID)
+        assertEquals(DataStallStatsUtils.probeResultToEnum(CaptivePortalProbeResult.PARTIAL),
+                DataStallEventProto.PARTIAL)
+        assertEquals(DataStallStatsUtils.probeResultToEnum(
+                CaptivePortalProbeResult(CaptivePortalProbeResult.PORTAL_CODE)),
+                DataStallEventProto.PORTAL)
+    }
+}