Snap for 11507696 from 3fdcd1485fb2d9261c17bef35bb2d27223e2c870 to mainline-cellbroadcast-release

Change-Id: I16623a72fdfc3ba59ee78cc907550d3e53e9d5b3
diff --git a/Android.bp b/Android.bp
index a7f57ae..9af9da4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -42,6 +42,7 @@
 
 // Common defaults to define SDK level
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -125,9 +126,6 @@
     srcs: ["apishim/common/**/*.java"],
     sdk_version: "system_current",
     visibility: ["//visibility:private"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // Each level of the shims (29, 30, ...) is its own java_library compiled against the corresponding
@@ -141,9 +139,6 @@
     ],
     sdk_version: "system_29",
     visibility: ["//visibility:private"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 java_library {
@@ -160,7 +155,7 @@
     visibility: ["//visibility:private"],
     lint: {
         strict_updatability_linting: true,
-        baseline_filename: "lint-baseline.xml",
+
     },
 }
 
@@ -184,9 +179,6 @@
     ],
     sdk_version: "module_31",
     visibility: ["//visibility:private"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 java_library {
@@ -207,9 +199,6 @@
     ],
     sdk_version: "module_33",
     visibility: ["//visibility:private"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 java_library {
@@ -231,9 +220,6 @@
     ],
     sdk_version: module_34_version,
     visibility: ["//visibility:private"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // Shims for APIs being added to the current development version of Android. These APIs are not
@@ -267,9 +253,6 @@
     ],
     sdk_version: "module_current",
     visibility: ["//visibility:private"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // API current uses the API current shims directly.
@@ -297,9 +280,6 @@
         "//packages/modules/Connectivity/service-t",
         "//packages/modules/Connectivity/tests:__subpackages__",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // API stable uses jarjar to rename the latest stable apishim package from
@@ -326,9 +306,6 @@
         "//packages/modules/Connectivity/service-t",
         "//packages/modules/Connectivity/tests:__subpackages__",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // Common defaults for android libraries containing network stack code, used to compile variants of
@@ -380,10 +357,6 @@
         "//packages/modules/NetworkStack/tests/unit",
         "//packages/modules/NetworkStack/tests/integration",
     ],
-    lint: {
-        strict_updatability_linting: true,
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 android_library {
@@ -408,10 +381,6 @@
         "//packages/modules/NetworkStack/tests/unit",
         "//packages/modules/NetworkStack/tests/integration",
     ],
-    lint: {
-        strict_updatability_linting: true,
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 java_library {
@@ -437,9 +406,6 @@
         "//packages/modules/Connectivity/Tethering/tests/integration",
         "//packages/modules/Connectivity/tests/cts/net",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 java_genrule {
@@ -508,9 +474,6 @@
     required: [
         "PlatformCaptivePortalLogin",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // Pre-merge the AndroidManifest for NetworkStackNext, so that its manifest can be merged on top
@@ -523,9 +486,6 @@
     ],
     static_libs: ["NetworkStackApiCurrentLib"],
     manifest: "AndroidManifest.xml",
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // NetworkStack build targeting the current API release, for testing on in-development SDK
@@ -543,10 +503,6 @@
         "privapp_whitelist_com.android.networkstack",
     ],
     updatable: true,
-    lint: {
-        strict_updatability_linting: true,
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // Updatable network stack for finalized API
@@ -563,10 +519,6 @@
         "privapp_whitelist_com.android.networkstack",
     ],
     updatable: true,
-    lint: {
-        strict_updatability_linting: true,
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 cc_library_shared {
@@ -649,9 +601,6 @@
     required: [
         "privapp_whitelist_com.android.networkstack",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // When adding or modifying protos, the jarjar rules and possibly proguard rules need
@@ -668,7 +617,4 @@
         "networkstackprotos",
     ],
     defaults: ["NetworkStackReleaseApiLevel"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
diff --git a/common/captiveportal/Android.bp b/common/captiveportal/Android.bp
index ad860c9..b1e64de 100644
--- a/common/captiveportal/Android.bp
+++ b/common/captiveportal/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp
index 060f0da..c566715 100644
--- a/common/networkstackclient/Android.bp
+++ b/common/networkstackclient/Android.bp
@@ -16,6 +16,7 @@
 
 // AIDL interfaces between the core system and the networking mainline module.
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -47,6 +48,9 @@
         cpp: {
             enabled: false,
         },
+        rust: {
+            enabled: false,
+        },
     },
 
     visibility: [
@@ -159,6 +163,9 @@
         cpp: {
             enabled: false,
         },
+        rust: {
+            enabled: false,
+        },
     },
     imports: ["ipmemorystore-aidl-interfaces-V10"],
     // TODO: have tethering depend on networkstack-client and set visibility to private
@@ -201,6 +208,10 @@
             version: "20",
             imports: ["ipmemorystore-aidl-interfaces-V10"],
         },
+        {
+            version: "21",
+            imports: ["ipmemorystore-aidl-interfaces-V10"],
+        },
 
     ],
     frozen: true,
@@ -212,7 +223,7 @@
     min_sdk_version: "30",
     static_libs: [
         "ipmemorystore-aidl-interfaces-V10-java",
-        "networkstack-aidl-interfaces-V20-java",
+        "networkstack-aidl-interfaces-V21-java",
     ],
     visibility: ["//packages/modules/NetworkStack:__subpackages__"],
     apex_available: [
@@ -221,9 +232,6 @@
         "com.android.tethering",
         "com.android.wifi",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 java_library {
@@ -268,7 +276,4 @@
         "com.android.tethering",
         "com.android.wifi",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/.hash b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/.hash
new file mode 100644
index 0000000..2959bec
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/.hash
@@ -0,0 +1 @@
+9bd9d687ddb816baf1faabcad0d56ac15b22c56e
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/DataStallReportParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/DataStallReportParcelable.aidl
new file mode 100644
index 0000000..771deda
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/DataStallReportParcelable.aidl
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable DataStallReportParcelable {
+  long timestampMillis = 0;
+  int detectionMethod = 1;
+  int tcpPacketFailRate = 2;
+  int tcpMetricsCollectionPeriodMillis = 3;
+  int dnsConsecutiveTimeouts = 4;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/DhcpResultsParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/DhcpResultsParcelable.aidl
new file mode 100644
index 0000000..31f2194
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/DhcpResultsParcelable.aidl
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable DhcpResultsParcelable {
+  android.net.StaticIpConfiguration baseConfiguration;
+  int leaseDuration;
+  int mtu;
+  String serverAddress;
+  String vendorInfo;
+  @nullable String serverHostName;
+  @nullable String captivePortalApiUrl;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/INetworkMonitor.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/INetworkMonitor.aidl
new file mode 100644
index 0000000..fb13c0c
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/INetworkMonitor.aidl
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetworkMonitor {
+  oneway void start();
+  oneway void launchCaptivePortalApp();
+  oneway void notifyCaptivePortalAppFinished(int response);
+  oneway void setAcceptPartialConnectivity();
+  oneway void forceReevaluation(int uid);
+  oneway void notifyPrivateDnsChanged(in android.net.PrivateDnsConfigParcel config);
+  oneway void notifyDnsResponse(int returnCode);
+  oneway void notifyNetworkConnected(in android.net.LinkProperties lp, in android.net.NetworkCapabilities nc);
+  oneway void notifyNetworkDisconnected();
+  oneway void notifyLinkPropertiesChanged(in android.net.LinkProperties lp);
+  oneway void notifyNetworkCapabilitiesChanged(in android.net.NetworkCapabilities nc);
+  oneway void notifyNetworkConnectedParcel(in android.net.networkstack.aidl.NetworkMonitorParameters params);
+  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 = 0x01;
+  const int NETWORK_VALIDATION_RESULT_PARTIAL = 0x02;
+  const int NETWORK_VALIDATION_RESULT_SKIPPED = 0x04;
+  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;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/INetworkMonitorCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/INetworkMonitorCallbacks.aidl
new file mode 100644
index 0000000..36eda8e
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/INetworkMonitorCallbacks.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetworkMonitorCallbacks {
+  oneway void onNetworkMonitorCreated(in android.net.INetworkMonitor networkMonitor) = 0;
+  oneway void notifyNetworkTested(int testResult, @nullable String redirectUrl) = 1;
+  oneway void notifyPrivateDnsConfigResolved(in android.net.PrivateDnsConfigParcel config) = 2;
+  oneway void showProvisioningNotification(String action, String packageName) = 3;
+  oneway void hideProvisioningNotification() = 4;
+  oneway void notifyProbeStatusChanged(int probesCompleted, int probesSucceeded) = 5;
+  oneway void notifyNetworkTestedWithExtras(in android.net.NetworkTestResultParcelable result) = 6;
+  oneway void notifyDataStallSuspected(in android.net.DataStallReportParcelable report) = 7;
+  oneway void notifyCaptivePortalDataChanged(in android.net.CaptivePortalData data) = 8;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/INetworkStackConnector.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/INetworkStackConnector.aidl
new file mode 100644
index 0000000..8120ffc
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/INetworkStackConnector.aidl
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetworkStackConnector {
+  oneway void makeDhcpServer(in String ifName, in android.net.dhcp.DhcpServingParamsParcel params, in android.net.dhcp.IDhcpServerCallbacks cb);
+  oneway void makeNetworkMonitor(in android.net.Network network, String name, in android.net.INetworkMonitorCallbacks cb);
+  oneway void makeIpClient(in String ifName, in android.net.ip.IIpClientCallbacks callbacks);
+  oneway void fetchIpMemoryStore(in android.net.IIpMemoryStoreCallbacks cb);
+  oneway void allowTestUid(int uid, in android.net.INetworkStackStatusCallback cb);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/INetworkStackStatusCallback.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/INetworkStackStatusCallback.aidl
new file mode 100644
index 0000000..0b6b778
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/INetworkStackStatusCallback.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetworkStackStatusCallback {
+  oneway void onStatusAvailable(int statusCode);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/InformationElementParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/InformationElementParcelable.aidl
new file mode 100644
index 0000000..6103774
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/InformationElementParcelable.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable InformationElementParcelable {
+  int id;
+  byte[] payload;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/InitialConfigurationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/InitialConfigurationParcelable.aidl
new file mode 100644
index 0000000..6a597e6
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/InitialConfigurationParcelable.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable InitialConfigurationParcelable {
+  android.net.LinkAddress[] ipAddresses;
+  android.net.IpPrefix[] directlyConnectedRoutes;
+  String[] dnsServers;
+  String gateway;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/Layer2InformationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/Layer2InformationParcelable.aidl
new file mode 100644
index 0000000..83796ee
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/Layer2InformationParcelable.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable Layer2InformationParcelable {
+  String l2Key;
+  String cluster;
+  android.net.MacAddress bssid;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/Layer2PacketParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/Layer2PacketParcelable.aidl
new file mode 100644
index 0000000..4b3fff5
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/Layer2PacketParcelable.aidl
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable Layer2PacketParcelable {
+  android.net.MacAddress dstMacAddress;
+  byte[] payload;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/NattKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/NattKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..18cf954
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/NattKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable NattKeepalivePacketDataParcelable {
+  byte[] srcAddress;
+  int srcPort;
+  byte[] dstAddress;
+  int dstPort;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/NetworkTestResultParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/NetworkTestResultParcelable.aidl
new file mode 100644
index 0000000..4d6d5a2
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/NetworkTestResultParcelable.aidl
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable NetworkTestResultParcelable {
+  long timestampMillis;
+  int result;
+  int probesSucceeded;
+  int probesAttempted;
+  String redirectUrl;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/PrivateDnsConfigParcel.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/PrivateDnsConfigParcel.aidl
new file mode 100644
index 0000000..ab62fe7
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/PrivateDnsConfigParcel.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(equals=true, toString=true)
+parcelable PrivateDnsConfigParcel {
+  String hostname;
+  String[] ips;
+  int privateDnsMode = (-1) /* -1 */;
+  String dohName = "";
+  String[] dohIps = {};
+  String dohPath = "";
+  int dohPort = (-1) /* -1 */;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/ProvisioningConfigurationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/ProvisioningConfigurationParcelable.aidl
new file mode 100644
index 0000000..0ce91f0
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/ProvisioningConfigurationParcelable.aidl
@@ -0,0 +1,65 @@
+/*
+**
+** Copyright (C) 2019 The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable ProvisioningConfigurationParcelable {
+  /**
+   * @deprecated use ipv4ProvisioningMode instead.
+   */
+  boolean enableIPv4;
+  /**
+   * @deprecated use ipv6ProvisioningMode instead.
+   */
+  boolean enableIPv6;
+  boolean usingMultinetworkPolicyTracker;
+  boolean usingIpReachabilityMonitor;
+  int requestedPreDhcpActionMs;
+  android.net.InitialConfigurationParcelable initialConfig;
+  android.net.StaticIpConfiguration staticIpConfig;
+  android.net.apf.ApfCapabilities apfCapabilities;
+  int provisioningTimeoutMs;
+  int ipv6AddrGenMode;
+  android.net.Network network;
+  String displayName;
+  boolean enablePreconnection;
+  @nullable android.net.ScanResultInfoParcelable scanResultInfo;
+  @nullable android.net.Layer2InformationParcelable layer2Info;
+  @nullable List<android.net.networkstack.aidl.dhcp.DhcpOption> options;
+  int ipv4ProvisioningMode;
+  int ipv6ProvisioningMode;
+  boolean uniqueEui64AddressesOnly;
+  int creatorUid;
+  int hostnameSetting;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/ScanResultInfoParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/ScanResultInfoParcelable.aidl
new file mode 100644
index 0000000..94fc27f
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/ScanResultInfoParcelable.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable ScanResultInfoParcelable {
+  String ssid;
+  String bssid;
+  android.net.InformationElementParcelable[] informationElements;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/TcpKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/TcpKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..0e1c21c
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/TcpKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable TcpKeepalivePacketDataParcelable {
+  byte[] srcAddress;
+  int srcPort;
+  byte[] dstAddress;
+  int dstPort;
+  int seq;
+  int ack;
+  int rcvWnd;
+  int rcvWndScale;
+  int tos;
+  int ttl;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/dhcp/DhcpLeaseParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/dhcp/DhcpLeaseParcelable.aidl
new file mode 100644
index 0000000..3cd8860
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/dhcp/DhcpLeaseParcelable.aidl
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.dhcp;
+@JavaDerive(toString=true)
+parcelable DhcpLeaseParcelable {
+  byte[] clientId;
+  byte[] hwAddr;
+  int netAddr;
+  int prefixLength;
+  long expTime;
+  String hostname;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/dhcp/DhcpServingParamsParcel.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/dhcp/DhcpServingParamsParcel.aidl
new file mode 100644
index 0000000..7997936
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/dhcp/DhcpServingParamsParcel.aidl
@@ -0,0 +1,49 @@
+/**
+ *
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.dhcp;
+@JavaDerive(toString=true)
+parcelable DhcpServingParamsParcel {
+  int serverAddr;
+  int serverAddrPrefixLength;
+  int[] defaultRouters;
+  int[] dnsServers;
+  int[] excludedAddrs;
+  long dhcpLeaseTimeSecs;
+  int linkMtu;
+  boolean metered;
+  int singleClientAddr = 0;
+  boolean changePrefixOnDecline = false;
+  int leasesSubnetPrefixLength = 0;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/dhcp/IDhcpEventCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/dhcp/IDhcpEventCallbacks.aidl
new file mode 100644
index 0000000..9312f47
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/dhcp/IDhcpEventCallbacks.aidl
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.dhcp;
+interface IDhcpEventCallbacks {
+  oneway void onLeasesChanged(in List<android.net.dhcp.DhcpLeaseParcelable> newLeases);
+  oneway void onNewPrefixRequest(in android.net.IpPrefix currentPrefix);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/dhcp/IDhcpServer.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/dhcp/IDhcpServer.aidl
new file mode 100644
index 0000000..1109f35
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/dhcp/IDhcpServer.aidl
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.dhcp;
+/* @hide */
+interface IDhcpServer {
+  oneway void start(in android.net.INetworkStackStatusCallback cb) = 0;
+  oneway void startWithCallbacks(in android.net.INetworkStackStatusCallback statusCb, in android.net.dhcp.IDhcpEventCallbacks eventCb) = 3;
+  oneway void updateParams(in android.net.dhcp.DhcpServingParamsParcel params, in android.net.INetworkStackStatusCallback cb) = 1;
+  oneway void stop(in android.net.INetworkStackStatusCallback cb) = 2;
+  const int STATUS_UNKNOWN = 0;
+  const int STATUS_SUCCESS = 1;
+  const int STATUS_INVALID_ARGUMENT = 2;
+  const int STATUS_UNKNOWN_ERROR = 3;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/dhcp/IDhcpServerCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/dhcp/IDhcpServerCallbacks.aidl
new file mode 100644
index 0000000..ab8577c
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/dhcp/IDhcpServerCallbacks.aidl
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.dhcp;
+/* @hide */
+interface IDhcpServerCallbacks {
+  oneway void onDhcpServerCreated(int statusCode, in android.net.dhcp.IDhcpServer server);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/ip/IIpClient.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/ip/IIpClient.aidl
new file mode 100644
index 0000000..87de4a6
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/ip/IIpClient.aidl
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.ip;
+/* @hide */
+interface IIpClient {
+  oneway void completedPreDhcpAction();
+  oneway void confirmConfiguration();
+  oneway void readPacketFilterComplete(in byte[] data);
+  oneway void shutdown();
+  oneway void startProvisioning(in android.net.ProvisioningConfigurationParcelable req);
+  oneway void stop();
+  oneway void setTcpBufferSizes(in String tcpBufferSizes);
+  oneway void setHttpProxy(in android.net.ProxyInfo proxyInfo);
+  oneway void setMulticastFilter(boolean enabled);
+  oneway void addKeepalivePacketFilter(int slot, in android.net.TcpKeepalivePacketDataParcelable pkt);
+  oneway void removeKeepalivePacketFilter(int slot);
+  oneway void setL2KeyAndGroupHint(in String l2Key, in String cluster);
+  oneway void addNattKeepalivePacketFilter(int slot, in android.net.NattKeepalivePacketDataParcelable pkt);
+  oneway void notifyPreconnectionComplete(boolean success);
+  oneway void updateLayer2Information(in android.net.Layer2InformationParcelable info);
+  oneway void updateApfCapabilities(in android.net.apf.ApfCapabilities apfCapabilities);
+  const int PROV_IPV4_DISABLED = 0x00;
+  const int PROV_IPV4_STATIC = 0x01;
+  const int PROV_IPV4_DHCP = 0x02;
+  const int PROV_IPV6_DISABLED = 0x00;
+  const int PROV_IPV6_SLAAC = 0x01;
+  const int PROV_IPV6_LINKLOCAL = 0x02;
+  const int HOSTNAME_SETTING_UNSET = 0x00;
+  const int HOSTNAME_SETTING_SEND = 0x01;
+  const int HOSTNAME_SETTING_DO_NOT_SEND = 0x02;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/ip/IIpClientCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/ip/IIpClientCallbacks.aidl
new file mode 100644
index 0000000..9d36419
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/ip/IIpClientCallbacks.aidl
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.ip;
+/* @hide */
+interface IIpClientCallbacks {
+  oneway void onIpClientCreated(in android.net.ip.IIpClient ipClient);
+  oneway void onPreDhcpAction();
+  oneway void onPostDhcpAction();
+  oneway void onNewDhcpResults(in android.net.DhcpResultsParcelable dhcpResults);
+  oneway void onProvisioningSuccess(in android.net.LinkProperties newLp);
+  oneway void onProvisioningFailure(in android.net.LinkProperties newLp);
+  oneway void onLinkPropertiesChange(in android.net.LinkProperties newLp);
+  oneway void onReachabilityLost(in String logMsg);
+  oneway void onQuit();
+  oneway void installPacketFilter(in byte[] filter);
+  oneway void startReadPacketFilter();
+  oneway void setFallbackMulticastFilter(boolean enabled);
+  oneway void setNeighborDiscoveryOffload(boolean enable);
+  oneway void onPreconnectionStart(in List<android.net.Layer2PacketParcelable> packets);
+  oneway void onReachabilityFailure(in android.net.networkstack.aidl.ip.ReachabilityLossInfoParcelable lossInfo);
+  oneway void setMaxDtimMultiplier(int multiplier);
+  const int DTIM_MULTIPLIER_RESET = 0;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/networkstack/aidl/NetworkMonitorParameters.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/networkstack/aidl/NetworkMonitorParameters.aidl
new file mode 100644
index 0000000..2ab9db0
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/networkstack/aidl/NetworkMonitorParameters.aidl
@@ -0,0 +1,41 @@
+/**
+ *
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.networkstack.aidl;
+@JavaDerive(equals=true, toString=true)
+parcelable NetworkMonitorParameters {
+  android.net.NetworkAgentConfig networkAgentConfig;
+  android.net.NetworkCapabilities networkCapabilities;
+  android.net.LinkProperties linkProperties;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/networkstack/aidl/dhcp/DhcpOption.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/networkstack/aidl/dhcp/DhcpOption.aidl
new file mode 100644
index 0000000..eea3e0d
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/networkstack/aidl/dhcp/DhcpOption.aidl
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.networkstack.aidl.dhcp;
+@JavaDerive(toString=true)
+parcelable DhcpOption {
+  byte type;
+  @nullable byte[] value;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/networkstack/aidl/ip/ReachabilityLossInfoParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/networkstack/aidl/ip/ReachabilityLossInfoParcelable.aidl
new file mode 100644
index 0000000..bb88434
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/networkstack/aidl/ip/ReachabilityLossInfoParcelable.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.networkstack.aidl.ip;
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable ReachabilityLossInfoParcelable {
+  String message;
+  android.net.networkstack.aidl.ip.ReachabilityLossReason reason;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/networkstack/aidl/ip/ReachabilityLossReason.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/networkstack/aidl/ip/ReachabilityLossReason.aidl
new file mode 100644
index 0000000..f9bb3c4
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/21/android/net/networkstack/aidl/ip/ReachabilityLossReason.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.networkstack.aidl.ip;
+@Backing(type="int")
+enum ReachabilityLossReason {
+  ROAM,
+  CONFIRM,
+  ORGANIC,
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ProvisioningConfigurationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ProvisioningConfigurationParcelable.aidl
index 7061f1e..0ce91f0 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ProvisioningConfigurationParcelable.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ProvisioningConfigurationParcelable.aidl
@@ -61,4 +61,5 @@
   int ipv6ProvisioningMode;
   boolean uniqueEui64AddressesOnly;
   int creatorUid;
+  int hostnameSetting;
 }
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ip/IIpClient.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ip/IIpClient.aidl
index b81ec20..87de4a6 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ip/IIpClient.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ip/IIpClient.aidl
@@ -56,4 +56,7 @@
   const int PROV_IPV6_DISABLED = 0x00;
   const int PROV_IPV6_SLAAC = 0x01;
   const int PROV_IPV6_LINKLOCAL = 0x02;
+  const int HOSTNAME_SETTING_UNSET = 0x00;
+  const int HOSTNAME_SETTING_SEND = 0x01;
+  const int HOSTNAME_SETTING_DO_NOT_SEND = 0x02;
 }
diff --git a/common/networkstackclient/lint-baseline.xml b/common/networkstackclient/lint-baseline.xml
deleted file mode 100644
index 046b948..0000000
--- a/common/networkstackclient/lint-baseline.xml
+++ /dev/null
@@ -1,169 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.KeepalivePacketData#getDstAddress`"
-        errorLine1="        final InetAddress dstAddress = pkt.getDstAddress();"
-        errorLine2="                                           ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/common/networkstackclient/src/android/net/util/KeepalivePacketDataUtil.java"
-            line="54"
-            column="44"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.KeepalivePacketData#getDstAddress`"
-        errorLine1="        final InetAddress dstAddress = pkt.getDstAddress();"
-        errorLine2="                                           ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/common/networkstackclient/src/android/net/util/KeepalivePacketDataUtil.java"
-            line="70"
-            column="44"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.KeepalivePacketData#getDstAddress`"
-        errorLine1="        p.dstAddress = data.getDstAddress().getAddress();"
-        errorLine2="                            ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/common/networkstackclient/src/android/net/util/KeepalivePacketDataUtil.java"
-            line="136"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.KeepalivePacketData#getDstPort`"
-        errorLine1="        p.dstPort = data.getDstPort();"
-        errorLine2="                         ~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/common/networkstackclient/src/android/net/util/KeepalivePacketDataUtil.java"
-            line="137"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.KeepalivePacketData#getDstPort`"
-        errorLine1="        parcel.dstPort = pkt.getDstPort();"
-        errorLine2="                             ~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/common/networkstackclient/src/android/net/util/KeepalivePacketDataUtil.java"
-            line="58"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.KeepalivePacketData#getDstPort`"
-        errorLine1="        parcel.dstPort = pkt.getDstPort();"
-        errorLine2="                             ~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/common/networkstackclient/src/android/net/util/KeepalivePacketDataUtil.java"
-            line="74"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.KeepalivePacketData#getPacket`"
-        errorLine1="        final ByteBuffer buffer = ByteBuffer.wrap(data.getPacket());"
-        errorLine2="                                                       ~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/common/networkstackclient/src/android/net/util/KeepalivePacketDataUtil.java"
-            line="110"
-            column="56"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.KeepalivePacketData#getSrcAddress`"
-        errorLine1="        final InetAddress srcAddress = pkt.getSrcAddress();"
-        errorLine2="                                           ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/common/networkstackclient/src/android/net/util/KeepalivePacketDataUtil.java"
-            line="53"
-            column="44"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.KeepalivePacketData#getSrcAddress`"
-        errorLine1="        final InetAddress srcAddress = pkt.getSrcAddress();"
-        errorLine2="                                           ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/common/networkstackclient/src/android/net/util/KeepalivePacketDataUtil.java"
-            line="69"
-            column="44"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.KeepalivePacketData#getSrcAddress`"
-        errorLine1="        p.srcAddress = data.getSrcAddress().getAddress();"
-        errorLine2="                            ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/common/networkstackclient/src/android/net/util/KeepalivePacketDataUtil.java"
-            line="134"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.KeepalivePacketData#getSrcPort`"
-        errorLine1="        p.srcPort = data.getSrcPort();"
-        errorLine2="                         ~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/common/networkstackclient/src/android/net/util/KeepalivePacketDataUtil.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.KeepalivePacketData#getSrcPort`"
-        errorLine1="        parcel.srcPort = pkt.getSrcPort();"
-        errorLine2="                             ~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/common/networkstackclient/src/android/net/util/KeepalivePacketDataUtil.java"
-            line="56"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.KeepalivePacketData#getSrcPort`"
-        errorLine1="        parcel.srcPort = pkt.getSrcPort();"
-        errorLine2="                             ~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/common/networkstackclient/src/android/net/util/KeepalivePacketDataUtil.java"
-            line="72"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.NetworkStack#getService`"
-        errorLine1="            while ((nss = NetworkStack.getService()) == null) {"
-        errorLine2="                                       ~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/common/networkstackclient/src/android/net/networkstack/ModuleNetworkStackClient.java"
-            line="87"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.NetworkStack#getService`"
-        errorLine1="        final IBinder nss = NetworkStack.getService();"
-        errorLine2="                                         ~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/common/networkstackclient/src/android/net/networkstack/ModuleNetworkStackClient.java"
-            line="68"
-            column="42"/>
-    </issue>
-
-</issues>
\ No newline at end of file
diff --git a/common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl b/common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl
index 7ab612f..fa4b263 100644
--- a/common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl
+++ b/common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl
@@ -51,4 +51,5 @@
     int ipv6ProvisioningMode;
     boolean uniqueEui64AddressesOnly;
     int creatorUid;
+    int hostnameSetting;
 }
diff --git a/common/networkstackclient/src/android/net/ip/IIpClient.aidl b/common/networkstackclient/src/android/net/ip/IIpClient.aidl
index 6c3346e..ad5b2e2 100644
--- a/common/networkstackclient/src/android/net/ip/IIpClient.aidl
+++ b/common/networkstackclient/src/android/net/ip/IIpClient.aidl
@@ -54,6 +54,21 @@
      */
     const int PROV_IPV6_LINKLOCAL = 0x02;
 
+    /**
+     * Unset hostname setting.
+     */
+    const int HOSTNAME_SETTING_UNSET = 0x00;
+
+    /**
+     * Send hostname to IP provisioning server.
+     */
+    const int HOSTNAME_SETTING_SEND = 0x01;
+
+    /**
+     * Do not send hostname to IP provisioning server.
+     */
+    const int HOSTNAME_SETTING_DO_NOT_SEND = 0x02;
+
     void completedPreDhcpAction();
     void confirmConfiguration();
     void readPacketFilterComplete(in byte[] data);
diff --git a/common/networkstackclient/src/android/net/shared/ProvisioningConfiguration.java b/common/networkstackclient/src/android/net/shared/ProvisioningConfiguration.java
index 16ad201..d4383d2 100644
--- a/common/networkstackclient/src/android/net/shared/ProvisioningConfiguration.java
+++ b/common/networkstackclient/src/android/net/shared/ProvisioningConfiguration.java
@@ -16,6 +16,7 @@
 
 package android.net.shared;
 
+import static android.net.ip.IIpClient.HOSTNAME_SETTING_UNSET;
 import static android.net.ip.IIpClient.PROV_IPV4_DHCP;
 import static android.net.ip.IIpClient.PROV_IPV4_DISABLED;
 import static android.net.ip.IIpClient.PROV_IPV4_STATIC;
@@ -290,6 +291,17 @@
         }
 
         /**
+         * Specify the hostname setting to use during IP provisioning.
+         *     - {@link IIpClient#HOSTNAME_SETTING_UNSET}: Default value.
+         *     - {@link IIpClient#HOSTNAME_SETTING_SEND}: Send the hostname.
+         *     - {@link IIpClient#HOSTNAME_SETTING_DO_NOT_SEND}: Don't send the hostname.
+         */
+        public Builder withHostnameSetting(int setting) {
+            mConfig.mHostnameSetting = setting;
+            return this;
+        }
+
+        /**
          * Build the configuration using previously specified parameters.
          */
         public ProvisioningConfiguration build() {
@@ -501,6 +513,7 @@
     public int mIPv4ProvisioningMode = PROV_IPV4_DHCP;
     public int mIPv6ProvisioningMode = PROV_IPV6_SLAAC;
     public int mCreatorUid;
+    public int mHostnameSetting = HOSTNAME_SETTING_UNSET;
 
     public ProvisioningConfiguration() {} // used by Builder
 
@@ -525,6 +538,7 @@
         mDhcpOptions = other.mDhcpOptions;
         mIPv4ProvisioningMode = other.mIPv4ProvisioningMode;
         mIPv6ProvisioningMode = other.mIPv6ProvisioningMode;
+        mHostnameSetting = other.mHostnameSetting;
     }
 
     /**
@@ -554,6 +568,7 @@
         p.scanResultInfo = (mScanResultInfo == null) ? null : mScanResultInfo.toStableParcelable();
         p.layer2Info = (mLayer2Info == null) ? null : mLayer2Info.toStableParcelable();
         p.options = (mDhcpOptions == null) ? null : new ArrayList<>(mDhcpOptions);
+        p.hostnameSetting = mHostnameSetting;
         return p;
     }
 
@@ -594,6 +609,7 @@
             config.mIPv4ProvisioningMode = p.ipv4ProvisioningMode;
             config.mIPv6ProvisioningMode = p.ipv6ProvisioningMode;
         }
+        config.mHostnameSetting = p.hostnameSetting;
         return config;
     }
 
@@ -648,6 +664,7 @@
                 .add("mDhcpOptions: " + mDhcpOptions)
                 .add("mIPv4ProvisioningMode: " + ipv4ProvisioningMode)
                 .add("mIPv6ProvisioningMode: " + ipv6ProvisioningMode)
+                .add("mHostnameSetting: " + mHostnameSetting)
                 .toString();
     }
 
@@ -694,7 +711,8 @@
                 && dhcpOptionListEquals(mDhcpOptions, other.mDhcpOptions)
                 && mIPv4ProvisioningMode == other.mIPv4ProvisioningMode
                 && mIPv6ProvisioningMode == other.mIPv6ProvisioningMode
-                && mCreatorUid == other.mCreatorUid;
+                && mCreatorUid == other.mCreatorUid
+                && mHostnameSetting == other.mHostnameSetting;
     }
 
     public boolean isValid() {
diff --git a/lint-baseline.xml b/lint-baseline.xml
deleted file mode 100644
index 1517328..0000000
--- a/lint-baseline.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.CaptivePortalData#getUserPortalUrl`"
-        errorLine1="                                mLinkProperties.getCaptivePortalData().getUserPortalUrl()"
-        errorLine2="                                                                       ~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java"
-            line="1734"
-            column="72"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.CaptivePortalData#getUserPortalUrl`"
-        errorLine1="                        mLinkProperties.getCaptivePortalData().getUserPortalUrl()"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java"
-            line="1370"
-            column="64"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.LinkProperties#getCaptivePortalData`"
-        errorLine1="                                mLinkProperties.getCaptivePortalData().getUserPortalUrl()"
-        errorLine2="                                                ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java"
-            line="1734"
-            column="49"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.LinkProperties#getCaptivePortalData`"
-        errorLine1="                        mLinkProperties.getCaptivePortalData().getUserPortalUrl()"
-        errorLine2="                                        ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java"
-            line="1370"
-            column="41"/>
-    </issue>
-
-</issues>
\ No newline at end of file
diff --git a/src/android/net/apf/ApfCounterTracker.java b/src/android/net/apf/ApfCounterTracker.java
index b02efa0..b2b52e9 100644
--- a/src/android/net/apf/ApfCounterTracker.java
+++ b/src/android/net/apf/ApfCounterTracker.java
@@ -17,6 +17,7 @@
 package android.net.apf;
 
 import android.util.ArrayMap;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -44,6 +45,9 @@
         TOTAL_PACKETS,           // hardcoded in APFv6 interpreter
         PASSED_ALLOCATE_FAILURE, // hardcoded in APFv6 interpreter
         PASSED_TRANSMIT_FAILURE, // hardcoded in APFv6 interpreter
+        CORRUPT_DNS_PACKET,      // hardcoded in APFv6 interpreter
+        FILTER_AGE_SECONDS,
+        FILTER_AGE_16384THS,
         PASSED_ARP,
         PASSED_DHCP,
         PASSED_IPV4,
@@ -89,6 +93,14 @@
         }
 
         /**
+         * Returns the counter sequence number from the end of the APF data segment for
+         * a given counter.
+         */
+        public int value() {
+            return this.ordinal();
+        }
+
+        /**
          * Returns the total size of the data segment in bytes.
          */
         public static int totalSize() {
@@ -96,6 +108,8 @@
         }
     }
 
+    private static final String TAG = ApfCounterTracker.class.getSimpleName();
+
     private final List<Counter> mCounterList;
     // Store the counters' value
     private final Map<Counter, Long> mCounters = new ArrayMap<>();
@@ -110,17 +124,31 @@
      */
     public static long getCounterValue(byte[] data, Counter counter)
             throws ArrayIndexOutOfBoundsException {
+        int offset = data.length + Counter.ENDIANNESS.offset();
+        int endianness = 0;
+        for (int i = 0; i < 4; i++) {
+            endianness = endianness << 8 | (data[offset + i] & 0xff);
+        }
         // Follow the same wrap-around addressing scheme of the interpreter.
-        int offset = counter.offset();
-        if (offset < 0) {
-            offset = data.length + offset;
+        offset = data.length + counter.offset();
+
+        boolean isBe = true;
+        switch (endianness) {
+            case 0:
+            case 0x12345678:
+                isBe = true;
+                break;
+            case 0x78563412:
+                isBe = false;
+                break;
+            default:
+                Log.wtf(TAG, "Unknown endianness: 0x" + Integer.toHexString(endianness));
         }
 
         // Decode 32bit big-endian integer into a long so we can count up beyond 2^31.
         long value = 0;
         for (int i = 0; i < 4; i++) {
-            value = value << 8 | (data[offset] & 0xFF);
-            offset++;
+            value = value << 8 | (data[offset + (isBe ? i : 3 - i)] & 0xff);
         }
         return value;
     }
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index 9a3d58b..0b2c101 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -16,6 +16,8 @@
 
 package android.net.apf;
 
+import static android.net.apf.BaseApfGenerator.Register.R0;
+import static android.net.apf.BaseApfGenerator.Register.R1;
 import static android.net.util.SocketUtils.makePacketSocketAddress;
 import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
 import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED;
@@ -46,8 +48,7 @@
 import android.net.NattKeepalivePacketDataParcelable;
 import android.net.TcpKeepalivePacketDataParcelable;
 import android.net.apf.ApfCounterTracker.Counter;
-import android.net.apf.ApfV4Generator.IllegalInstructionException;
-import android.net.apf.ApfV4Generator.Register;
+import android.net.apf.BaseApfGenerator.IllegalInstructionException;
 import android.net.ip.IpClient.IpClientCallbacksWrapper;
 import android.os.PowerManager;
 import android.os.SystemClock;
@@ -141,7 +142,7 @@
      */
     private void maybeSetupCounter(ApfV4Generator gen, Counter c) {
         if (mApfCapabilities.hasDataAccess()) {
-            gen.addLoadImmediate(Register.R1, c.offset());
+            gen.addLoadImmediate(R1, c.offset());
         }
     }
 
@@ -1049,10 +1050,10 @@
                     //
                     // if lft < (oldLft + 2) // 3 -> PASS
                     // if lft > oldLft            -> PASS
-                    // gen.addJumpIfR0LessThan((int) ((section.lifetime + 2) / 3),
+                    // gen.addJumpIfR0LessThan(((section.lifetime + 2) / 3),
                     //        nextFilterLabel);
                     if (lft < (section.lifetime + 2) / 3) return MatchType.MATCH_PASS;
-                    // gen.addJumpIfR0GreaterThan((int) section.lifetime, nextFilterLabel);
+                    // gen.addJumpIfR0GreaterThan(section.lifetime, nextFilterLabel);
                     if (lft > section.lifetime) return MatchType.MATCH_PASS;
                 } else if (section.lifetime < section.min) {
                     // Case 2a) 0 < old lft < min
@@ -1076,7 +1077,7 @@
                     // if lft > oldLft -> PASS
                     // gen.addJumpIfR0Equals(0, nextFilterLabel);
                     if (lft == 0) return MatchType.MATCH_PASS;
-                    // gen.addJumpIfR0GreaterThan((int) section.lifetime, nextFilterLabel);
+                    // gen.addJumpIfR0GreaterThan(section.lifetime, nextFilterLabel);
                     if (lft > section.lifetime) return MatchType.MATCH_PASS;
                 } else {
                     // Case 4a) otherwise
@@ -1089,10 +1090,10 @@
                     if (lft == 0) return MatchType.MATCH_PASS;
                     // gen.addJumpIfR0LessThan(section.min, continueLabel);
                     if (lft < section.min) continue;
-                    // gen.addJumpIfR0LessThan((int) ((section.lifetime + 2) / 3),
+                    // gen.addJumpIfR0LessThan(((section.lifetime + 2) / 3),
                     //         nextFilterLabel);
                     if (lft < (section.lifetime + 2) / 3) return MatchType.MATCH_PASS;
-                    // gen.addJumpIfR0GreaterThan((int) section.lifetime, nextFilterLabel);
+                    // gen.addJumpIfR0GreaterThan(section.lifetime, nextFilterLabel);
                     if (lft > section.lifetime) return MatchType.MATCH_PASS;
                 }
             }
@@ -1138,15 +1139,15 @@
                 throws IllegalInstructionException {
             String nextFilterLabel = "Ra" + getUniqueNumberLocked();
             // Skip if packet is not the right size
-            gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
+            gen.addLoadFromMemory(R0, gen.PACKET_SIZE_MEMORY_SLOT);
             gen.addJumpIfR0NotEquals(mPacket.capacity(), nextFilterLabel);
             // Skip filter if expired
-            gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
+            gen.addLoadFromMemory(R0, gen.FILTER_AGE_MEMORY_SLOT);
             gen.addJumpIfR0GreaterThan(getRemainingFilterLft(timeSeconds), nextFilterLabel);
             for (PacketSection section : mPacketSections) {
                 // Generate code to match the packet bytes.
                 if (section.type == PacketSection.Type.MATCH) {
-                    gen.addLoadImmediate(Register.R0, section.start);
+                    gen.addLoadImmediate(R0, section.start);
                     gen.addJumpIfBytesAtR0NotEqual(
                             Arrays.copyOfRange(mPacket.array(), section.start,
                                     section.start + section.length),
@@ -1154,8 +1155,8 @@
                 } else {
                     switch (section.length) {
                         // length asserted to be either 2 or 4 on PacketSection construction
-                        case 2: gen.addLoad16(Register.R0, section.start); break;
-                        case 4: gen.addLoad32(Register.R0, section.start); break;
+                        case 2: gen.addLoad16(R0, section.start); break;
+                        case 4: gen.addLoad32(R0, section.start); break;
                     }
 
                     // WARNING: keep this in sync with matches()!
@@ -1177,9 +1178,9 @@
                         //
                         // if lft < (oldLft + 2) // 3 -> PASS
                         // if lft > oldLft            -> PASS
-                        gen.addJumpIfR0LessThan((int) ((section.lifetime + 2) / 3),
+                        gen.addJumpIfR0LessThan(((section.lifetime + 2) / 3),
                                 nextFilterLabel);
-                        gen.addJumpIfR0GreaterThan((int) section.lifetime, nextFilterLabel);
+                        gen.addJumpIfR0GreaterThan(section.lifetime, nextFilterLabel);
                     } else if (section.lifetime < section.min) {
                         // Case 2a) 0 < old lft < min
                         //
@@ -1199,7 +1200,7 @@
                         // if lft == 0     -> PASS
                         // if lft > oldLft -> PASS
                         gen.addJumpIfR0Equals(0, nextFilterLabel);
-                        gen.addJumpIfR0GreaterThan((int) section.lifetime, nextFilterLabel);
+                        gen.addJumpIfR0GreaterThan(section.lifetime, nextFilterLabel);
                     } else {
                         final String continueLabel = "Continue" + getUniqueNumberLocked();
                         // Case 4a) otherwise
@@ -1210,9 +1211,9 @@
                         // if lft > oldLft              -> PASS
                         gen.addJumpIfR0Equals(0, nextFilterLabel);
                         gen.addJumpIfR0LessThan(section.min, continueLabel);
-                        gen.addJumpIfR0LessThan((int) ((section.lifetime + 2) / 3),
+                        gen.addJumpIfR0LessThan(((section.lifetime + 2) / 3),
                                 nextFilterLabel);
-                        gen.addJumpIfR0GreaterThan((int) section.lifetime, nextFilterLabel);
+                        gen.addJumpIfR0GreaterThan(section.lifetime, nextFilterLabel);
 
                         // CONTINUE
                         gen.defineLabel(continueLabel);
@@ -1282,21 +1283,21 @@
         void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException {
             final String nextFilterLabel = "natt_keepalive_filter" + getUniqueNumberLocked();
 
-            gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
+            gen.addLoadImmediate(R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
             gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel);
 
             // A NAT-T keepalive packet contains 1 byte payload with the value 0xff
             // Check payload length is 1
-            gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+            gen.addLoadFromMemory(R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
             gen.addAdd(UDP_HEADER_LEN);
             gen.addSwap();
-            gen.addLoad16(Register.R0, IPV4_TOTAL_LENGTH_OFFSET);
-            gen.addNeg(Register.R1);
+            gen.addLoad16(R0, IPV4_TOTAL_LENGTH_OFFSET);
+            gen.addNeg(R1);
             gen.addAddR1();
             gen.addJumpIfR0NotEquals(1, nextFilterLabel);
 
             // Check that the ports match
-            gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+            gen.addLoadFromMemory(R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
             gen.addAdd(ETH_HEADER_LEN);
             gen.addJumpIfBytesAtR0NotEqual(mPortFingerprint, nextFilterLabel);
 
@@ -1398,28 +1399,28 @@
         void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException {
             final String nextFilterLabel = "keepalive_ack" + getUniqueNumberLocked();
 
-            gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
+            gen.addLoadImmediate(R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
             gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel);
 
             // Skip to the next filter if it's not zero-sized :
             // TCP_HEADER_SIZE + IPV4_HEADER_SIZE - ipv4_total_length == 0
             // Load the IP header size into R1
-            gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+            gen.addLoadFromMemory(R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
             // Load the TCP header size into R0 (it's indexed by R1)
-            gen.addLoad8Indexed(Register.R0, ETH_HEADER_LEN + TCP_HEADER_SIZE_OFFSET);
+            gen.addLoad8Indexed(R0, ETH_HEADER_LEN + TCP_HEADER_SIZE_OFFSET);
             // Size offset is in the top nibble, but it must be multiplied by 4, and the two
             // top bits of the low nibble are guaranteed to be zeroes. Right-shift R0 by 2.
             gen.addRightShift(2);
             // R0 += R1 -> R0 contains TCP + IP headers length
             gen.addAddR1();
             // Load IPv4 total length
-            gen.addLoad16(Register.R1, IPV4_TOTAL_LENGTH_OFFSET);
-            gen.addNeg(Register.R0);
+            gen.addLoad16(R1, IPV4_TOTAL_LENGTH_OFFSET);
+            gen.addNeg(R0);
             gen.addAddR1();
             gen.addJumpIfR0NotEquals(0, nextFilterLabel);
             // Add IPv4 header length
-            gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
-            gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN);
+            gen.addLoadFromMemory(R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+            gen.addLoadImmediate(R0, ETH_HEADER_LEN);
             gen.addAddR1();
             gen.addJumpIfBytesAtR0NotEqual(mPortSeqAckFingerprint, nextFilterLabel);
 
@@ -1521,23 +1522,23 @@
         final String checkTargetIPv4 = "checkTargetIPv4";
 
         // Drop if not ARP IPv4.
-        gen.addLoadImmediate(Register.R0, ARP_HEADER_OFFSET);
+        gen.addLoadImmediate(R0, ARP_HEADER_OFFSET);
         maybeSetupCounter(gen, Counter.DROPPED_ARP_NON_IPV4);
         gen.addJumpIfBytesAtR0NotEqual(ARP_IPV4_HEADER, mCountAndDropLabel);
 
         // Drop if unknown ARP opcode.
-        gen.addLoad16(Register.R0, ARP_OPCODE_OFFSET);
+        gen.addLoad16(R0, ARP_OPCODE_OFFSET);
         gen.addJumpIfR0Equals(ARP_OPCODE_REQUEST, checkTargetIPv4); // Skip to unicast check
         maybeSetupCounter(gen, Counter.DROPPED_ARP_UNKNOWN);
         gen.addJumpIfR0NotEquals(ARP_OPCODE_REPLY, mCountAndDropLabel);
 
         // Drop if ARP reply source IP is 0.0.0.0
-        gen.addLoad32(Register.R0, ARP_SOURCE_IP_ADDRESS_OFFSET);
+        gen.addLoad32(R0, ARP_SOURCE_IP_ADDRESS_OFFSET);
         maybeSetupCounter(gen, Counter.DROPPED_ARP_REPLY_SPA_NO_HOST);
         gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel);
 
         // Pass if non-broadcast reply.
-        gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
+        gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
         maybeSetupCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY);
         gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
 
@@ -1545,13 +1546,13 @@
         gen.defineLabel(checkTargetIPv4);
         if (mIPv4Address == null) {
             // When there is no IPv4 address, drop GARP replies (b/29404209).
-            gen.addLoad32(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
+            gen.addLoad32(R0, ARP_TARGET_IP_ADDRESS_OFFSET);
             maybeSetupCounter(gen, Counter.DROPPED_GARP_REPLY);
             gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel);
         } else {
             // When there is an IPv4 address, drop unicast/broadcast requests
             // and broadcast replies with a different target IPv4 address.
-            gen.addLoadImmediate(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
+            gen.addLoadImmediate(R0, ARP_TARGET_IP_ADDRESS_OFFSET);
             maybeSetupCounter(gen, Counter.DROPPED_ARP_OTHER_HOST);
             gen.addJumpIfBytesAtR0NotEqual(mIPv4Address, mCountAndDropLabel);
         }
@@ -1588,17 +1589,17 @@
 
             // Pass DHCP addressed to us.
             // Check it's UDP.
-            gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET);
+            gen.addLoad8(R0, IPV4_PROTOCOL_OFFSET);
             gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipDhcpv4Filter);
             // Check it's not a fragment or is the initial fragment.
-            gen.addLoad16(Register.R0, IPV4_FRAGMENT_OFFSET_OFFSET);
+            gen.addLoad16(R0, IPV4_FRAGMENT_OFFSET_OFFSET);
             gen.addJumpIfR0AnyBitsSet(IPV4_FRAGMENT_OFFSET_MASK, skipDhcpv4Filter);
             // Check it's addressed to DHCP client port.
-            gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
-            gen.addLoad16Indexed(Register.R0, TCP_UDP_DESTINATION_PORT_OFFSET);
+            gen.addLoadFromMemory(R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+            gen.addLoad16Indexed(R0, TCP_UDP_DESTINATION_PORT_OFFSET);
             gen.addJumpIfR0NotEquals(DHCP_CLIENT_PORT, skipDhcpv4Filter);
             // Check it's DHCP to our MAC address.
-            gen.addLoadImmediate(Register.R0, DHCP_CLIENT_MAC_OFFSET);
+            gen.addLoadImmediate(R0, DHCP_CLIENT_MAC_OFFSET);
             // NOTE: Relies on R1 containing IPv4 header offset.
             gen.addAddR1();
             gen.addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipDhcpv4Filter);
@@ -1609,14 +1610,14 @@
             gen.defineLabel(skipDhcpv4Filter);
 
             // If IPv4 destination address is in multicast range, drop.
-            gen.addLoad8(Register.R0, IPV4_DEST_ADDR_OFFSET);
+            gen.addLoad8(R0, IPV4_DEST_ADDR_OFFSET);
             gen.addAnd(0xf0);
             maybeSetupCounter(gen, Counter.DROPPED_IPV4_MULTICAST);
             gen.addJumpIfR0Equals(0xe0, mCountAndDropLabel);
 
             // If IPv4 broadcast packet, drop regardless of L2 (b/30231088).
             maybeSetupCounter(gen, Counter.DROPPED_IPV4_BROADCAST_ADDR);
-            gen.addLoad32(Register.R0, IPV4_DEST_ADDR_OFFSET);
+            gen.addLoad32(R0, IPV4_DEST_ADDR_OFFSET);
             gen.addJumpIfR0Equals(IPV4_BROADCAST_ADDRESS, mCountAndDropLabel);
             if (mIPv4Address != null && mIPv4PrefixLength < 31) {
                 maybeSetupCounter(gen, Counter.DROPPED_IPV4_BROADCAST_NET);
@@ -1639,7 +1640,7 @@
             // If L2 broadcast packet, drop.
             // TODO: can we invert this condition to fall through to the common pass case below?
             maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST);
-            gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
+            gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
             gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
             maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST);
             gen.addJump(mCountAndDropLabel);
@@ -1660,7 +1661,7 @@
         if (!haveKeepaliveResponses) return;
 
         // If not the right proto, skip keepalive filters
-        gen.addLoad8(Register.R0, offset);
+        gen.addLoad8(R0, offset);
         gen.addJumpIfR0NotEquals(proto, label);
 
         // Drop Keepalive responses
@@ -1709,7 +1710,7 @@
         // if keepalive ack
         //   drop
 
-        gen.addLoad8(Register.R0, IPV6_NEXT_HEADER_OFFSET);
+        gen.addLoad8(R0, IPV6_NEXT_HEADER_OFFSET);
 
         // MLD packets set the router-alert hop-by-hop option.
         // TODO: be smarter about not blindly passing every packet with HBH options.
@@ -1728,7 +1729,7 @@
 
                 // ICMPv6 but not ECHO? -> Skip the multicast filter.
                 // (ICMPv6 ECHO requests will go through the multicast filter below).
-                gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET);
+                gen.addLoad8(R0, ICMP6_TYPE_OFFSET);
                 gen.addJumpIfR0NotEquals(ICMPV6_ECHO_REQUEST_TYPE, skipIPv6MulticastFilterLabel);
             } else {
                 gen.addJumpIfR0Equals(IPPROTO_ICMPV6, skipIPv6MulticastFilterLabel);
@@ -1737,7 +1738,7 @@
             // Drop all other packets sent to ff00::/8 (multicast prefix).
             gen.defineLabel(dropAllIPv6MulticastsLabel);
             maybeSetupCounter(gen, Counter.DROPPED_IPV6_NON_ICMP_MULTICAST);
-            gen.addLoad8(Register.R0, IPV6_DEST_ADDR_OFFSET);
+            gen.addLoad8(R0, IPV6_DEST_ADDR_OFFSET);
             gen.addJumpIfR0Equals(0xff, mCountAndDropLabel);
             // If any keepalive filter matches, drop
             generateV6KeepaliveFilters(gen);
@@ -1756,7 +1757,7 @@
 
         // Add unsolicited multicast neighbor announcements filter
         String skipUnsolicitedMulticastNALabel = "skipUnsolicitedMulticastNA";
-        gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET);
+        gen.addLoad8(R0, ICMP6_TYPE_OFFSET);
         // Drop all router solicitations (b/32833400)
         maybeSetupCounter(gen, Counter.DROPPED_IPV6_ROUTER_SOLICITATION);
         gen.addJumpIfR0Equals(ICMPV6_ROUTER_SOLICITATION, mCountAndDropLabel);
@@ -1766,12 +1767,15 @@
         // This is a way to cover ff02::1 and ff02::2 with a single JNEBS.
         // TODO: Drop only if they don't contain the address of on-link neighbours.
         final byte[] unsolicitedNaDropPrefix = Arrays.copyOf(IPV6_ALL_NODES_ADDRESS, 15);
-        gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET);
+        gen.addLoadImmediate(R0, IPV6_DEST_ADDR_OFFSET);
         gen.addJumpIfBytesAtR0NotEqual(unsolicitedNaDropPrefix, skipUnsolicitedMulticastNALabel);
 
         maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA);
         gen.addJump(mCountAndDropLabel);
         gen.defineLabel(skipUnsolicitedMulticastNALabel);
+
+        // Note that this is immediately followed emitEpilogue which will:
+        // maybeSetupCounter(gen, Counter.PASSED_IPV6_ICMP);
     }
 
     /** Encodes qname in TLV pattern. */
@@ -1823,24 +1827,24 @@
         //   3. it is a UDP packet with port 5353
 
         // Check it's L2 mDNS multicast address.
-        gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
+        gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
         gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V4_MAC_ADDRESS, skipMdnsv4Filter);
 
         // Checks it's IPv4.
-        gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
+        gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET);
         gen.addJumpIfR0NotEquals(ETH_P_IP, skipMdnsFilter);
 
         // Check it's not a fragment.
-        gen.addLoad16(Register.R0, IPV4_FRAGMENT_OFFSET_OFFSET);
+        gen.addLoad16(R0, IPV4_FRAGMENT_OFFSET_OFFSET);
         gen.addJumpIfR0AnyBitsSet(IPV4_FRAGMENT_MORE_FRAGS_MASK | IPV4_FRAGMENT_OFFSET_MASK,
                 skipMdnsFilter);
 
         // Checks it's UDP.
-        gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET);
+        gen.addLoad8(R0, IPV4_PROTOCOL_OFFSET);
         gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipMdnsFilter);
 
         // Set R1 to IPv4 header.
-        gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+        gen.addLoadFromMemory(R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
         gen.addJump(checkMdnsUdpPort);
 
         gen.defineLabel(skipMdnsv4Filter);
@@ -1850,28 +1854,28 @@
         gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V6_MAC_ADDRESS, skipMdnsFilter);
 
         // Checks it's IPv6.
-        gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
+        gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET);
         gen.addJumpIfR0NotEquals(ETH_P_IPV6, skipMdnsFilter);
 
         // Checks it's UDP.
-        gen.addLoad8(Register.R0, IPV6_NEXT_HEADER_OFFSET);
+        gen.addLoad8(R0, IPV6_NEXT_HEADER_OFFSET);
         gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipMdnsFilter);
 
         // Set R1 to IPv6 header.
-        gen.addLoadImmediate(Register.R1, IPV6_HEADER_LEN);
+        gen.addLoadImmediate(R1, IPV6_HEADER_LEN);
 
         // Checks it's mDNS UDP port
         gen.defineLabel(checkMdnsUdpPort);
-        gen.addLoad16Indexed(Register.R0, TCP_UDP_DESTINATION_PORT_OFFSET);
+        gen.addLoad16Indexed(R0, TCP_UDP_DESTINATION_PORT_OFFSET);
         gen.addJumpIfR0NotEquals(MDNS_PORT, skipMdnsFilter);
 
-        gen.addLoad16Indexed(Register.R0, MDNS_QDCOUNT_OFFSET);
+        gen.addLoad16Indexed(R0, MDNS_QDCOUNT_OFFSET);
         // If QDCOUNT != 1, pass the packet
         gen.addJumpIfR0NotEquals(1, mDnsAcceptPacket);
 
         // If QDCOUNT == 1, matches the QNAME with allowlist.
         // Load offset for the first QNAME.
-        gen.addLoadImmediate(Register.R0, MDNS_QNAME_OFFSET);
+        gen.addLoadImmediate(R0, MDNS_QNAME_OFFSET);
         gen.addAddR1();
 
         // Check first QNAME against allowlist
@@ -1909,16 +1913,16 @@
         final String skipPort7V4Filter = "skip_port7_v4_filter";
 
         // Check it's TCP.
-        gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET);
+        gen.addLoad8(R0, IPV4_PROTOCOL_OFFSET);
         gen.addJumpIfR0NotEquals(IPPROTO_TCP, skipPort7V4Filter);
 
         // Check it's not a fragment or is the initial fragment.
-        gen.addLoad16(Register.R0, IPV4_FRAGMENT_OFFSET_OFFSET);
+        gen.addLoad16(R0, IPV4_FRAGMENT_OFFSET_OFFSET);
         gen.addJumpIfR0AnyBitsSet(IPV4_FRAGMENT_OFFSET_MASK, skipPort7V4Filter);
 
         // Check it's destination port 7.
-        gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
-        gen.addLoad16Indexed(Register.R0, TCP_UDP_DESTINATION_PORT_OFFSET);
+        gen.addLoadFromMemory(R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+        gen.addLoad16Indexed(R0, TCP_UDP_DESTINATION_PORT_OFFSET);
         gen.addJumpIfR0NotEquals(ECHO_PORT, skipPort7V4Filter);
 
         // Drop it.
@@ -1964,9 +1968,18 @@
         if (mApfCapabilities.hasDataAccess()) {
             // Increment TOTAL_PACKETS
             maybeSetupCounter(gen, Counter.TOTAL_PACKETS);
-            gen.addLoadData(Register.R0, 0);  // load counter
+            gen.addLoadData(R0, 0);  // load counter
             gen.addAdd(1);
-            gen.addStoreData(Register.R0, 0);  // write-back counter
+            gen.addStoreData(R0, 0);  // write-back counter
+
+            maybeSetupCounter(gen, Counter.FILTER_AGE_SECONDS);
+            gen.addLoadFromMemory(R0, 15);  // m[15] is filter age in seconds
+            gen.addStoreData(R0, 0);  // store 'counter'
+
+            // requires a new enough APFv5+ interpreter, otherwise will be 0
+            maybeSetupCounter(gen, Counter.FILTER_AGE_16384THS);
+            gen.addLoadFromMemory(R0, 9);  // m[9] is filter age in 16384ths
+            gen.addStoreData(R0, 0);  // store 'counter'
         }
 
         // Here's a basic summary of what the initial program does:
@@ -1985,7 +1998,7 @@
         //   pass
         // insert IPv6 filter to drop, pass, or fall off the end for ICMPv6 packets
 
-        gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
+        gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET);
 
         if (mDrop802_3Frames) {
             // drop 802.3 frames (ethtype < 0x0600)
@@ -2007,7 +2020,7 @@
 
         // Add mDNS filter:
         generateMdnsFilterLocked(gen);
-        gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
+        gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET);
 
         // Add IPv4 filters:
         String skipIPv4FiltersLabel = "skipIPv4Filters";
@@ -2023,7 +2036,7 @@
         gen.addJumpIfR0Equals(ETH_P_IPV6, ipv6FilterLabel);
 
         // Drop non-IP non-ARP broadcasts, pass the rest
-        gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
+        gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
         maybeSetupCounter(gen, Counter.PASSED_NON_IP_UNICAST);
         gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
         maybeSetupCounter(gen, Counter.DROPPED_ETH_BROADCAST);
@@ -2055,16 +2068,16 @@
         // pointed to by R1, then jumps to the pass label. This saves a few bytes over inserting
         // the entire sequence inline for every counter.
         gen.defineLabel(mCountAndPassLabel);
-        gen.addLoadData(Register.R0, 0);   // R0 = *(R1 + 0)
+        gen.addLoadData(R0, 0);   // R0 = *(R1 + 0)
         gen.addAdd(1);                     // R0++
-        gen.addStoreData(Register.R0, 0);  // *(R1 + 0) = R0
+        gen.addStoreData(R0, 0);  // *(R1 + 0) = R0
         gen.addJump(gen.PASS_LABEL);
 
         // Same as above for the count & drop trampoline.
         gen.defineLabel(mCountAndDropLabel);
-        gen.addLoadData(Register.R0, 0);   // R0 = *(R1 + 0)
+        gen.addLoadData(R0, 0);   // R0 = *(R1 + 0)
         gen.addAdd(1);                     // R0++
-        gen.addStoreData(Register.R0, 0);  // *(R1 + 0) = R0
+        gen.addStoreData(R0, 0);  // *(R1 + 0) = R0
         gen.addJump(gen.DROP_LABEL);
     }
 
diff --git a/src/android/net/apf/ApfV4Generator.java b/src/android/net/apf/ApfV4Generator.java
index a7986e6..e1b0fc3 100644
--- a/src/android/net/apf/ApfV4Generator.java
+++ b/src/android/net/apf/ApfV4Generator.java
@@ -16,655 +16,40 @@
 
 package android.net.apf;
 
-import static android.net.apf.ApfV4Generator.Register.R0;
-import static android.net.apf.ApfV4Generator.Register.R1;
-
-import androidx.annotation.NonNull;
+import static android.net.apf.BaseApfGenerator.Rbit.Rbit0;
+import static android.net.apf.BaseApfGenerator.Register.R1;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.net.module.util.HexDump;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
 
 /**
  * APF assembler/generator.  A tool for generating an APF program.
  *
  * Call add*() functions to add instructions to the program, then call
- * {@link ApfV4Generator#generate} to get the APF bytecode for the program.
+ * {@link BaseApfGenerator#generate} to get the APF bytecode for the program.
+ *
+ * @param <Type> the generator class
  *
  * @hide
  */
-public class ApfV4Generator {
-    /**
-     * This exception is thrown when an attempt is made to generate an illegal instruction.
-     */
-    public static class IllegalInstructionException extends Exception {
-        IllegalInstructionException(String msg) {
-            super(msg);
-        }
-    }
-    private enum Opcodes {
-        LABEL(-1),
-        // Unconditionally pass (if R=0) or drop (if R=1) packet.
-        // An optional unsigned immediate value can be provided to encode the counter number.
-        // If the value is non-zero, the instruction increments the counter.
-        // The counter is located (-4 * counter number) bytes from the end of the data region.
-        // It is a U32 big-endian value and is always incremented by 1.
-        // This is more or less equivalent to: lddw R0, -N4; add R0,1; stdw R0, -N4; {pass,drop}
-        // e.g. "pass", "pass 1", "drop", "drop 1"
-        PASSDROP(0),
-        LDB(1),    // Load 1 byte from immediate offset, e.g. "ldb R0, [5]"
-        LDH(2),    // Load 2 bytes from immediate offset, e.g. "ldh R0, [5]"
-        LDW(3),    // Load 4 bytes from immediate offset, e.g. "ldw R0, [5]"
-        LDBX(4),   // Load 1 byte from immediate offset plus register, e.g. "ldbx R0, [5]R0"
-        LDHX(5),   // Load 2 byte from immediate offset plus register, e.g. "ldhx R0, [5]R0"
-        LDWX(6),   // Load 4 byte from immediate offset plus register, e.g. "ldwx R0, [5]R0"
-        ADD(7),    // Add, e.g. "add R0,5"
-        MUL(8),    // Multiply, e.g. "mul R0,5"
-        DIV(9),    // Divide, e.g. "div R0,5"
-        AND(10),   // And, e.g. "and R0,5"
-        OR(11),    // Or, e.g. "or R0,5"
-        SH(12),    // Left shift, e.g, "sh R0, 5" or "sh R0, -5" (shifts right)
-        LI(13),    // Load immediate, e.g. "li R0,5" (immediate encoded as signed value)
-        // Jump, e.g. "jmp label"
-        // In APFv6, we use JMP(R=1) to encode the DATA instruction. DATA is executed as a jump.
-        // It tells how many bytes of the program regions are used to store the data and followed
-        // by the actual data bytes.
-        // "e.g. data 5, abcde"
-        JMP(14),
-        JEQ(15),   // Compare equal and branch, e.g. "jeq R0,5,label"
-        JNE(16),   // Compare not equal and branch, e.g. "jne R0,5,label"
-        JGT(17),   // Compare greater than and branch, e.g. "jgt R0,5,label"
-        JLT(18),   // Compare less than and branch, e.g. "jlt R0,5,label"
-        JSET(19),  // Compare any bits set and branch, e.g. "jset R0,5,label"
-        JNEBS(20), // Compare not equal byte sequence, e.g. "jnebs R0,5,label,0x1122334455"
-        EXT(21),   // Followed by immediate indicating ExtendedOpcodes.
-        LDDW(22),  // Load 4 bytes from data memory address (register + immediate): "lddw R0, [5]R1"
-        STDW(23),  // Store 4 bytes to data memory address (register + immediate): "stdw R0, [5]R1"
-        // Write 1, 2 or 4 bytes immediate to the output buffer and auto-increment the pointer to
-        // write. e.g. "write 5"
-        WRITE(24),
-        // Copy bytes from input packet/APF program/data region to output buffer and
-        // auto-increment the output buffer pointer.
-        // Register bit is used to specify the source of data copy.
-        // R=0 means copy from packet.
-        // R=1 means copy from APF program/data region.
-        // The copy length is stored in (u8)imm2.
-        // e.g. "pktcopy 5, 5" "datacopy 5, 5"
-        PKTDATACOPY(25);
-
-        final int value;
-
-        private Opcodes(int value) {
-            this.value = value;
-        }
-    }
-    // Extended opcodes. Primary opcode is Opcodes.EXT. ExtendedOpcodes are encoded in the immediate
-    // field.
-    private enum ExtendedOpcodes {
-        LDM(0),   // Load from memory, e.g. "ldm R0,5"
-        STM(16),  // Store to memory, e.g. "stm R0,5"
-        NOT(32),  // Not, e.g. "not R0"
-        NEG(33),  // Negate, e.g. "neg R0"
-        SWAP(34), // Swap, e.g. "swap R0,R1"
-        MOVE(35),  // Move, e.g. "move R0,R1"
-        // Allocate writable output buffer.
-        // R=0, use register R0 to store the length. R=1, encode the length in the u16 int imm2.
-        // "e.g. allocate R0"
-        // "e.g. allocate 123"
-        ALLOCATE(36),
-        //  Transmit and deallocate the buffer (transmission can be delayed until the program
-        //  terminates). R=0 means discard the buffer, R=1 means transmit the buffer.
-        // "e.g. transmit"
-        // "e.g. discard"
-        TRANSMITDISCARD(37),
-        // Write 1, 2 or 4 byte value from register to the output buffer and auto-increment the
-        // output buffer pointer.
-        // e.g. "ewrite1 r0"
-        EWRITE1(38),
-        EWRITE2(39),
-        EWRITE4(40),
-        // Copy bytes from input packet/APF program/data region to output buffer and
-        // auto-increment the output buffer pointer.
-        // The copy src offset is stored in R0.
-        // when R=0, the copy length is stored in (u8)imm2.
-        // when R=1, the copy length is stored in R1.
-        // e.g. "pktcopy r0, 5", "pktcopy r0, r1", "datacopy r0, 5", "datacopy r0, r1"
-        EPKTCOPY(41),
-        EDATACOPY(42),
-        // Jumps if the UDP payload content (starting at R0) does not contain one
-        // of the specified QNAMEs, applying case insensitivity.
-        // R0: Offset to UDP payload content
-        // R=0/1 meaning 'does not match'/'matches'
-        // imm1: Opcode
-        // imm2: Label offset
-        // imm3(u8): Question type (PTR/SRV/TXT/A/AAAA)
-        // imm4(bytes): TLV-encoded QNAME list (null-terminated)
-        // e.g.: "jdnsqmatch R0,label,0x0c,\002aa\005local\0\0"
-        JDNSQMATCH(43),
-        // Jumps if the UDP payload content (starting at R0) does not contain one
-        // of the specified NAMEs in answers/authority/additional records, applying
-        // case insensitivity.
-        // R=0/1 meaning 'does not match'/'matches'
-        // R0: Offset to UDP payload content
-        // imm1: Opcode
-        // imm2: Label offset
-        // imm3(bytes): TLV-encoded QNAME list (null-terminated)
-        // e.g.: "jdnsamatch R0,label,0x0c,\002aa\005local\0\0"
-        JDNSAMATCH(44);
-
-        final int value;
-
-        private ExtendedOpcodes(int value) {
-            this.value = value;
-        }
-    }
-    public enum Register {
-        R0(0),
-        R1(1);
-
-        final int value;
-
-        private Register(int value) {
-            this.value = value;
-        }
-    }
-
-    private enum IntImmediateType {
-        INDETERMINATE_SIZE_SIGNED,
-        INDETERMINATE_SIZE_UNSIGNED,
-        SIGNED_8,
-        UNSIGNED_8,
-        SIGNED_BE16,
-        UNSIGNED_BE16,
-        SIGNED_BE32,
-        UNSIGNED_BE32;
-    }
-
-    private static class IntImmediate {
-        public final IntImmediateType mImmediateType;
-        public final int mValue;
-
-        IntImmediate(int value, IntImmediateType type) {
-            mImmediateType = type;
-            mValue = value;
-        }
-
-        private int calculateIndeterminateSize() {
-            switch (mImmediateType) {
-                case INDETERMINATE_SIZE_SIGNED:
-                    return calculateImmSize(mValue, true /* signed */);
-                case INDETERMINATE_SIZE_UNSIGNED:
-                    return calculateImmSize(mValue, false /* signed */);
-                default:
-                    // For IMM with determinate size, return 0 to allow Math.max() calculation in
-                    // caller function.
-                    return 0;
-            }
-        }
-
-        private int getEncodingSize(int immFieldSize) {
-            switch (mImmediateType) {
-                case SIGNED_8:
-                case UNSIGNED_8:
-                    return 1;
-                case SIGNED_BE16:
-                case UNSIGNED_BE16:
-                    return 2;
-                case SIGNED_BE32:
-                case UNSIGNED_BE32:
-                    return 4;
-                case INDETERMINATE_SIZE_SIGNED:
-                case INDETERMINATE_SIZE_UNSIGNED: {
-                    int minSizeRequired = calculateIndeterminateSize();
-                    if (minSizeRequired > immFieldSize) {
-                        throw new IllegalStateException(
-                                String.format("immFieldSize: %d is too small to encode value %d",
-                                        immFieldSize, mValue));
-                    }
-                    return immFieldSize;
-                }
-            }
-            throw new IllegalStateException("UnhandledInvalid IntImmediateType: " + mImmediateType);
-        }
-
-        private int writeValue(byte[] bytecode, Integer writingOffset, int immFieldSize) {
-            return Instruction.writeValue(mValue, bytecode, writingOffset,
-                    getEncodingSize(immFieldSize));
-        }
-
-        public static IntImmediate newSigned(int imm) {
-            return new IntImmediate(imm, IntImmediateType.INDETERMINATE_SIZE_SIGNED);
-        }
-
-        public static IntImmediate newUnsigned(long imm) {
-            // upperBound is 2^32 - 1
-            checkRange("Unsigned IMM", imm, 0 /* lowerBound */,
-                    4294967295L /* upperBound */);
-            return new IntImmediate((int) imm, IntImmediateType.INDETERMINATE_SIZE_UNSIGNED);
-        }
-
-        public static IntImmediate newTwosComplementUnsigned(long imm) {
-            checkRange("Unsigned TwosComplement IMM", imm, Integer.MIN_VALUE,
-                    4294967295L /* upperBound */);
-            return new IntImmediate((int) imm, IntImmediateType.INDETERMINATE_SIZE_UNSIGNED);
-        }
-
-        public static IntImmediate newTwosComplementSigned(long imm) {
-            checkRange("Signed TwosComplement IMM", imm, Integer.MIN_VALUE,
-                    4294967295L /* upperBound */);
-            return new IntImmediate((int) imm, IntImmediateType.INDETERMINATE_SIZE_SIGNED);
-        }
-
-        public static IntImmediate newS8(byte imm) {
-            checkRange("S8 IMM", imm, Byte.MIN_VALUE, Byte.MAX_VALUE);
-            return new IntImmediate(imm, IntImmediateType.SIGNED_8);
-        }
-
-        public static IntImmediate newU8(int imm) {
-            checkRange("U8 IMM", imm, 0, 255);
-            return new IntImmediate(imm, IntImmediateType.UNSIGNED_8);
-        }
-
-        public static IntImmediate newS16(short imm) {
-            return new IntImmediate(imm, IntImmediateType.SIGNED_BE16);
-        }
-
-        public static IntImmediate newU16(int imm) {
-            checkRange("U16 IMM", imm, 0, 65535);
-            return new IntImmediate(imm, IntImmediateType.UNSIGNED_BE16);
-        }
-
-        public static IntImmediate newS32(int imm) {
-            return new IntImmediate(imm, IntImmediateType.SIGNED_BE32);
-        }
-
-        public static IntImmediate newU32(long imm) {
-            // upperBound is 2^32 - 1
-            checkRange("U32 IMM", imm, 0 /* lowerBound */,
-                    4294967295L /* upperBound */);
-            return new IntImmediate((int) imm, IntImmediateType.UNSIGNED_BE32);
-        }
-
-        @Override
-        public String toString() {
-            return "IntImmediate{" + "mImmediateType=" + mImmediateType + ", mValue=" + mValue
-                    + '}';
-        }
-    }
-
-    private class Instruction {
-        private final byte mOpcode;   // A "Opcode" value.
-        private final byte mRegister; // A "Register" value.
-        public final List<IntImmediate> mIntImms = new ArrayList<>();
-        // When mOpcode is a jump:
-        private int mTargetLabelSize;
-        private int mLenFieldOverride = -1;
-        private String mTargetLabel;
-        // When mOpcode == Opcodes.LABEL:
-        private String mLabel;
-        private byte[] mBytesImm;
-        // Offset in bytes from the beginning of this program. Set by {@link ApfGenerator#generate}.
-        int offset;
-
-        Instruction(Opcodes opcode, Register register) {
-            mOpcode = (byte) opcode.value;
-            mRegister = (byte) register.value;
-        }
-
-        Instruction(ExtendedOpcodes extendedOpcodes, Register register) {
-            this(Opcodes.EXT, register);
-            addUnsigned(extendedOpcodes.value);
-        }
-
-        Instruction(ExtendedOpcodes extendedOpcodes, int slot, Register register)
-                throws IllegalInstructionException {
-            this(Opcodes.EXT, register);
-            if (slot < 0 || slot >= MEMORY_SLOTS) {
-                throw new IllegalInstructionException("illegal memory slot number: " + slot);
-            }
-            addUnsigned(extendedOpcodes.value + slot);
-        }
-
-        Instruction(Opcodes opcode) {
-            this(opcode, R0);
-        }
-
-        Instruction(ExtendedOpcodes extendedOpcodes) {
-            this(extendedOpcodes, R0);
-        }
-
-        Instruction addSigned(int imm) {
-            mIntImms.add(IntImmediate.newSigned(imm));
-            return this;
-        }
-
-        Instruction addUnsigned(int imm) {
-            mIntImms.add(IntImmediate.newUnsigned(imm));
-            return this;
-        }
-
-
-        Instruction addTwosCompSigned(int imm) {
-            mIntImms.add(IntImmediate.newTwosComplementSigned(imm));
-            return this;
-        }
-
-
-        Instruction addTwosCompUnsigned(int imm) {
-            mIntImms.add(IntImmediate.newTwosComplementUnsigned(imm));
-            return this;
-        }
-
-        Instruction addS8(byte imm) {
-            mIntImms.add(IntImmediate.newS8(imm));
-            return this;
-        }
-
-        Instruction addU8(int imm) {
-            mIntImms.add(IntImmediate.newU8(imm));
-            return this;
-        }
-
-        Instruction addS16(short imm) {
-            mIntImms.add(IntImmediate.newS16(imm));
-            return this;
-        }
-
-        Instruction addU16(int imm) {
-            mIntImms.add(IntImmediate.newU16(imm));
-            return this;
-        }
-
-        Instruction addS32(int imm) {
-            mIntImms.add(IntImmediate.newS32(imm));
-            return this;
-        }
-
-        Instruction addU32(long imm) {
-            mIntImms.add(IntImmediate.newU32(imm));
-            return this;
-        }
-
-        Instruction setLabel(String label) throws IllegalInstructionException {
-            if (mLabels.containsKey(label)) {
-                throw new IllegalInstructionException("duplicate label " + label);
-            }
-            if (mOpcode != Opcodes.LABEL.value) {
-                throw new IllegalStateException("adding label to non-label instruction");
-            }
-            mLabel = label;
-            mLabels.put(label, this);
-            return this;
-        }
-
-        Instruction setTargetLabel(String label) {
-            mTargetLabel = label;
-            mTargetLabelSize = 4; // May shrink later on in generate().
-            return this;
-        }
-
-        Instruction overrideLenField(int size) {
-            mLenFieldOverride = size;
-            return this;
-        }
-
-        Instruction setBytesImm(byte[] bytes) {
-            mBytesImm = bytes;
-            return this;
-        }
-
-        /**
-         * @return size of instruction in bytes.
-         */
-        int size() {
-            if (mOpcode == Opcodes.LABEL.value) {
-                return 0;
-            }
-            int size = 1;
-            int indeterminateSize = calculateRequiredIndeterminateSize();
-            for (IntImmediate imm : mIntImms) {
-                size += imm.getEncodingSize(indeterminateSize);
-            }
-            if (mTargetLabel != null) {
-                size += indeterminateSize;
-            }
-            if (mBytesImm != null) {
-                size += mBytesImm.length;
-            }
-            return size;
-        }
-
-        /**
-         * Resize immediate value field so that it's only as big as required to
-         * contain the offset of the jump destination.
-         * @return {@code true} if shrunk.
-         */
-        boolean shrink() throws IllegalInstructionException {
-            if (mTargetLabel == null) {
-                return false;
-            }
-            int oldTargetLabelSize = mTargetLabelSize;
-            mTargetLabelSize = calculateImmSize(calculateTargetLabelOffset(), false);
-            if (mTargetLabelSize > oldTargetLabelSize) {
-                throw new IllegalStateException("instruction grew");
-            }
-            return mTargetLabelSize < oldTargetLabelSize;
-        }
-
-        /**
-         * Assemble value for instruction size field.
-         */
-        private int generateImmSizeField() {
-            // If we already know the size the length field, just use it
-            switch (mLenFieldOverride) {
-                case -1:
-                    break;
-                case 1:
-                    return 1;
-                case 2:
-                    return 2;
-                case 4:
-                    return 3;
-                default:
-                    throw new IllegalStateException(
-                            "mLenFieldOverride has invalid value: " + mLenFieldOverride);
-            }
-            // Otherwise, calculate
-            int immSize = calculateRequiredIndeterminateSize();
-            // Encode size field to fit in 2 bits: 0->0, 1->1, 2->2, 3->4.
-            return immSize == 4 ? 3 : immSize;
-        }
-
-        /**
-         * Assemble first byte of generated instruction.
-         */
-        private byte generateInstructionByte() {
-            int sizeField = generateImmSizeField();
-            return (byte)((mOpcode << 3) | (sizeField << 1) | mRegister);
-        }
-
-        /**
-         * Write {@code value} at offset {@code writingOffset} into {@code bytecode}.
-         * {@code immSize} bytes are written. {@code value} is truncated to
-         * {@code immSize} bytes. {@code value} is treated simply as a
-         * 32-bit value, so unsigned values should be zero extended and the truncation
-         * should simply throw away their zero-ed upper bits, and signed values should
-         * be sign extended and the truncation should simply throw away their signed
-         * upper bits.
-         */
-        private static int writeValue(int value, byte[] bytecode, int writingOffset, int immSize) {
-            for (int i = immSize - 1; i >= 0; i--) {
-                bytecode[writingOffset++] = (byte)((value >> (i * 8)) & 255);
-            }
-            return writingOffset;
-        }
-
-        /**
-         * Generate bytecode for this instruction at offset {@link Instruction#offset}.
-         */
-        void generate(byte[] bytecode) throws IllegalInstructionException {
-            if (mOpcode == Opcodes.LABEL.value) {
-                return;
-            }
-            int writingOffset = offset;
-            bytecode[writingOffset++] = generateInstructionByte();
-            int indeterminateSize = calculateRequiredIndeterminateSize();
-            int startOffset = 0;
-            if (mOpcode == Opcodes.EXT.value) {
-                // For extend opcode, always write the actual opcode first.
-                writingOffset = mIntImms.get(startOffset++).writeValue(bytecode, writingOffset,
-                        indeterminateSize);
-            }
-            if (mTargetLabel != null) {
-                writingOffset = writeValue(calculateTargetLabelOffset(), bytecode, writingOffset,
-                        indeterminateSize);
-            }
-            for (int i = startOffset; i < mIntImms.size(); ++i) {
-                writingOffset = mIntImms.get(i).writeValue(bytecode, writingOffset,
-                        indeterminateSize);
-            }
-            if (mBytesImm != null) {
-                System.arraycopy(mBytesImm, 0, bytecode, writingOffset, mBytesImm.length);
-                writingOffset += mBytesImm.length;
-            }
-            if ((writingOffset - offset) != size()) {
-                throw new IllegalStateException("wrote " + (writingOffset - offset) +
-                        " but should have written " + size());
-            }
-        }
-
-        /**
-         * Calculates the maximum indeterminate size of all IMMs in this instruction.
-         * <p>
-         * This method finds the largest size needed to encode any indeterminate-sized IMMs in
-         * the instruction. This size will be stored in the immLen field.
-         */
-        private int calculateRequiredIndeterminateSize() {
-            int maxSize = mTargetLabelSize;
-            for (IntImmediate imm : mIntImms) {
-                maxSize = Math.max(maxSize, imm.calculateIndeterminateSize());
-            }
-            return maxSize;
-        }
-
-        private int calculateTargetLabelOffset() throws IllegalInstructionException {
-            Instruction targetLabelInstruction;
-            if (mTargetLabel == DROP_LABEL) {
-                targetLabelInstruction = mDropLabel;
-            } else if (mTargetLabel == PASS_LABEL) {
-                targetLabelInstruction = mPassLabel;
-            } else {
-                targetLabelInstruction = mLabels.get(mTargetLabel);
-            }
-            if (targetLabelInstruction == null) {
-                throw new IllegalInstructionException("label not found: " + mTargetLabel);
-            }
-            // Calculate distance from end of this instruction to instruction.offset.
-            final int targetLabelOffset = targetLabelInstruction.offset - (offset + size());
-            return targetLabelOffset;
-        }
-    }
+public class ApfV4Generator<Type extends BaseApfGenerator> extends BaseApfGenerator {
 
     /**
-     * Jump to this label to terminate the program and indicate the packet
-     * should be dropped.
-     */
-    public static final String DROP_LABEL = "__DROP__";
-
-    /**
-     * Jump to this label to terminate the program and indicate the packet
-     * should be passed to the AP.
-     */
-    public static final String PASS_LABEL = "__PASS__";
-
-    /**
-     * Number of memory slots available for access via APF stores to memory and loads from memory.
-     * The memory slots are numbered 0 to {@code MEMORY_SLOTS} - 1. This must be kept in sync with
-     * the APF interpreter.
-     */
-    public static final int MEMORY_SLOTS = 16;
-
-    /**
-     * Memory slot number that is prefilled with the IPv4 header length.
-     * Note that this memory slot may be overwritten by a program that
-     * executes stores to this memory slot. This must be kept in sync with
-     * the APF interpreter.
-     */
-    public static final int IPV4_HEADER_SIZE_MEMORY_SLOT = 13;
-
-    /**
-     * Memory slot number that is prefilled with the size of the packet being filtered in bytes.
-     * Note that this memory slot may be overwritten by a program that
-     * executes stores to this memory slot. This must be kept in sync with the APF interpreter.
-     */
-    public static final int PACKET_SIZE_MEMORY_SLOT = 14;
-
-    /**
-     * Memory slot number that is prefilled with the age of the filter in seconds. The age of the
-     * filter is the time since the filter was installed until now.
-     * Note that this memory slot may be overwritten by a program that
-     * executes stores to this memory slot. This must be kept in sync with the APF interpreter.
-     */
-    public static final int FILTER_AGE_MEMORY_SLOT = 15;
-
-    /**
-     * First memory slot containing prefilled values. Can be used in range comparisons to determine
-     * if memory slot index is within prefilled slots.
-     */
-    public static final int FIRST_PREFILLED_MEMORY_SLOT = IPV4_HEADER_SIZE_MEMORY_SLOT;
-
-    /**
-     * Last memory slot containing prefilled values. Can be used in range comparisons to determine
-     * if memory slot index is within prefilled slots.
-     */
-    public static final int LAST_PREFILLED_MEMORY_SLOT = FILTER_AGE_MEMORY_SLOT;
-
-    // This version number syncs up with APF_VERSION in hardware/google/apf/apf_interpreter.h
-    public static final int MIN_APF_VERSION = 2;
-    public static final int MIN_APF_VERSION_IN_DEV = 5;
-    public static final int APF_VERSION_4 = 4;
-
-
-    private final ArrayList<Instruction> mInstructions = new ArrayList<Instruction>();
-    private final HashMap<String, Instruction> mLabels = new HashMap<String, Instruction>();
-    private final Instruction mDropLabel = new Instruction(Opcodes.LABEL);
-    private final Instruction mPassLabel = new Instruction(Opcodes.LABEL);
-    private final int mVersion;
-    private boolean mGenerated;
-
-    /**
-     * Creates an ApfGenerator instance which is able to emit instructions for the specified
+     * Creates an ApfV4Generator instance which is able to emit instructions for the specified
      * {@code version} of the APF interpreter. Throws {@code IllegalInstructionException} if
      * the requested version is unsupported.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public ApfV4Generator(int version) throws IllegalInstructionException {
-        mVersion = version;
+        super(version);
         requireApfVersion(MIN_APF_VERSION);
     }
 
-    /**
-     * Returns true if the ApfGenerator supports the specified {@code version}, otherwise false.
-     */
-    public static boolean supportsVersion(int version) {
-        return version >= MIN_APF_VERSION;
-    }
-
-    private void requireApfVersion(int minimumVersion) throws IllegalInstructionException {
-        if (mVersion < minimumVersion) {
-            throw new IllegalInstructionException("Requires APF >= " + minimumVersion);
-        }
-    }
-
-    private ApfV4Generator append(Instruction instruction) {
+    Type append(Instruction instruction) {
         if (mGenerated) {
             throw new IllegalStateException("Program already generated");
         }
         mInstructions.add(instruction);
-        return this;
+        return (Type) this;
     }
 
     /**
@@ -682,14 +67,14 @@
      * </pre>
      * In this case "next_filter" may not have any generated code associated with it.
      */
-    public ApfV4Generator defineLabel(String name) throws IllegalInstructionException {
+    public Type defineLabel(String name) throws IllegalInstructionException {
         return append(new Instruction(Opcodes.LABEL).setLabel(name));
     }
 
     /**
      * Add an unconditional jump instruction to the end of the program.
      */
-    public ApfV4Generator addJump(String target) {
+    public Type addJump(String target) {
         return append(new Instruction(Opcodes.JMP).setTargetLabel(target));
     }
 
@@ -697,24 +82,24 @@
      * Add an instruction to the end of the program to load the byte at offset {@code offset}
      * bytes from the beginning of the packet into {@code register}.
      */
-    public ApfV4Generator addLoad8(Register r, int ofs) {
-        return append(new Instruction(Opcodes.LDB, r).addUnsigned(ofs));
+    public Type addLoad8(Register r, int ofs) {
+        return append(new Instruction(Opcodes.LDB, r).addPacketOffset(ofs));
     }
 
     /**
      * Add an instruction to the end of the program to load 16-bits at offset {@code offset}
      * bytes from the beginning of the packet into {@code register}.
      */
-    public ApfV4Generator addLoad16(Register r, int ofs) {
-        return append(new Instruction(Opcodes.LDH, r).addUnsigned(ofs));
+    public Type addLoad16(Register r, int ofs) {
+        return append(new Instruction(Opcodes.LDH, r).addPacketOffset(ofs));
     }
 
     /**
      * Add an instruction to the end of the program to load 32-bits at offset {@code offset}
      * bytes from the beginning of the packet into {@code register}.
      */
-    public ApfV4Generator addLoad32(Register r, int ofs) {
-        return append(new Instruction(Opcodes.LDW, r).addUnsigned(ofs));
+    public Type addLoad32(Register r, int ofs) {
+        return append(new Instruction(Opcodes.LDW, r).addPacketOffset(ofs));
     }
 
     /**
@@ -722,8 +107,8 @@
      * {@code register}. The offset of the loaded byte from the beginning of the packet is
      * the sum of {@code offset} and the value in register R1.
      */
-    public ApfV4Generator addLoad8Indexed(Register r, int ofs) {
-        return append(new Instruction(Opcodes.LDBX, r).addUnsigned(ofs));
+    public Type addLoad8Indexed(Register r, int ofs) {
+        return append(new Instruction(Opcodes.LDBX, r).addPacketOffset(ofs));
     }
 
     /**
@@ -731,8 +116,8 @@
      * {@code register}. The offset of the loaded 16-bits from the beginning of the packet is
      * the sum of {@code offset} and the value in register R1.
      */
-    public ApfV4Generator addLoad16Indexed(Register r, int ofs) {
-        return append(new Instruction(Opcodes.LDHX, r).addUnsigned(ofs));
+    public Type addLoad16Indexed(Register r, int ofs) {
+        return append(new Instruction(Opcodes.LDHX, r).addPacketOffset(ofs));
     }
 
     /**
@@ -740,42 +125,42 @@
      * {@code register}. The offset of the loaded 32-bits from the beginning of the packet is
      * the sum of {@code offset} and the value in register R1.
      */
-    public ApfV4Generator addLoad32Indexed(Register r, int ofs) {
-        return append(new Instruction(Opcodes.LDWX, r).addUnsigned(ofs));
+    public Type addLoad32Indexed(Register r, int ofs) {
+        return append(new Instruction(Opcodes.LDWX, r).addPacketOffset(ofs));
     }
 
     /**
      * Add an instruction to the end of the program to add {@code value} to register R0.
      */
-    public ApfV4Generator addAdd(int val) {
+    public Type addAdd(int val) {
         return append(new Instruction(Opcodes.ADD).addTwosCompUnsigned(val));
     }
 
     /**
      * Add an instruction to the end of the program to multiply register R0 by {@code value}.
      */
-    public ApfV4Generator addMul(int val) {
+    public Type addMul(long val) {
         return append(new Instruction(Opcodes.MUL).addUnsigned(val));
     }
 
     /**
      * Add an instruction to the end of the program to divide register R0 by {@code value}.
      */
-    public ApfV4Generator addDiv(int val) {
+    public Type addDiv(long val) {
         return append(new Instruction(Opcodes.DIV).addUnsigned(val));
     }
 
     /**
      * Add an instruction to the end of the program to logically and register R0 with {@code value}.
      */
-    public ApfV4Generator addAnd(int val) {
+    public Type addAnd(int val) {
         return append(new Instruction(Opcodes.AND).addTwosCompUnsigned(val));
     }
 
     /**
      * Add an instruction to the end of the program to logically or register R0 with {@code value}.
      */
-    public ApfV4Generator addOr(int val) {
+    public Type addOr(int val) {
         return append(new Instruction(Opcodes.OR).addTwosCompUnsigned(val));
     }
 
@@ -783,7 +168,7 @@
      * Add an instruction to the end of the program to shift left register R0 by {@code value} bits.
      */
     // TODO: consider whether should change the argument type to byte
-    public ApfV4Generator addLeftShift(int val) {
+    public Type addLeftShift(int val) {
         return append(new Instruction(Opcodes.SH).addSigned(val));
     }
 
@@ -792,28 +177,28 @@
      * bits.
      */
     // TODO: consider whether should change the argument type to byte
-    public ApfV4Generator addRightShift(int val) {
+    public Type addRightShift(int val) {
         return append(new Instruction(Opcodes.SH).addSigned(-val));
     }
 
     /**
      * Add an instruction to the end of the program to add register R1 to register R0.
      */
-    public ApfV4Generator addAddR1() {
+    public Type addAddR1() {
         return append(new Instruction(Opcodes.ADD, R1));
     }
 
     /**
      * Add an instruction to the end of the program to multiply register R0 by register R1.
      */
-    public ApfV4Generator addMulR1() {
+    public Type addMulR1() {
         return append(new Instruction(Opcodes.MUL, R1));
     }
 
     /**
      * Add an instruction to the end of the program to divide register R0 by register R1.
      */
-    public ApfV4Generator addDivR1() {
+    public Type addDivR1() {
         return append(new Instruction(Opcodes.DIV, R1));
     }
 
@@ -821,7 +206,7 @@
      * Add an instruction to the end of the program to logically and register R0 with register R1
      * and store the result back into register R0.
      */
-    public ApfV4Generator addAndR1() {
+    public Type addAndR1() {
         return append(new Instruction(Opcodes.AND, R1));
     }
 
@@ -829,7 +214,7 @@
      * Add an instruction to the end of the program to logically or register R0 with register R1
      * and store the result back into register R0.
      */
-    public ApfV4Generator addOrR1() {
+    public Type addOrR1() {
         return append(new Instruction(Opcodes.OR, R1));
     }
 
@@ -837,14 +222,14 @@
      * Add an instruction to the end of the program to shift register R0 left by the value in
      * register R1.
      */
-    public ApfV4Generator addLeftShiftR1() {
+    public Type addLeftShiftR1() {
         return append(new Instruction(Opcodes.SH, R1));
     }
 
     /**
      * Add an instruction to the end of the program to move {@code value} into {@code register}.
      */
-    public ApfV4Generator addLoadImmediate(Register register, int value) {
+    public Type addLoadImmediate(Register register, int value) {
         return append(new Instruction(Opcodes.LI, register).addSigned(value));
     }
 
@@ -852,7 +237,7 @@
      * Add an instruction to the end of the program to jump to {@code target} if register R0's
      * value equals {@code value}.
      */
-    public ApfV4Generator addJumpIfR0Equals(int val, String tgt) {
+    public Type addJumpIfR0Equals(int val, String tgt) {
         return append(new Instruction(Opcodes.JEQ).addTwosCompUnsigned(val).setTargetLabel(tgt));
     }
 
@@ -860,7 +245,7 @@
      * Add an instruction to the end of the program to jump to {@code target} if register R0's
      * value does not equal {@code value}.
      */
-    public ApfV4Generator addJumpIfR0NotEquals(int val, String tgt) {
+    public Type addJumpIfR0NotEquals(int val, String tgt) {
         return append(new Instruction(Opcodes.JNE).addTwosCompUnsigned(val).setTargetLabel(tgt));
     }
 
@@ -868,7 +253,7 @@
      * Add an instruction to the end of the program to jump to {@code target} if register R0's
      * value is greater than {@code value}.
      */
-    public ApfV4Generator addJumpIfR0GreaterThan(int val, String tgt) {
+    public Type addJumpIfR0GreaterThan(long val, String tgt) {
         return append(new Instruction(Opcodes.JGT).addUnsigned(val).setTargetLabel(tgt));
     }
 
@@ -876,7 +261,7 @@
      * Add an instruction to the end of the program to jump to {@code target} if register R0's
      * value is less than {@code value}.
      */
-    public ApfV4Generator addJumpIfR0LessThan(int val, String tgt) {
+    public Type addJumpIfR0LessThan(long val, String tgt) {
         return append(new Instruction(Opcodes.JLT).addUnsigned(val).setTargetLabel(tgt));
     }
 
@@ -884,14 +269,14 @@
      * Add an instruction to the end of the program to jump to {@code target} if register R0's
      * value has any bits set that are also set in {@code value}.
      */
-    public ApfV4Generator addJumpIfR0AnyBitsSet(int val, String tgt) {
+    public Type addJumpIfR0AnyBitsSet(int val, String tgt) {
         return append(new Instruction(Opcodes.JSET).addTwosCompUnsigned(val).setTargetLabel(tgt));
     }
     /**
      * Add an instruction to the end of the program to jump to {@code target} if register R0's
      * value equals register R1's value.
      */
-    public ApfV4Generator addJumpIfR0EqualsR1(String tgt) {
+    public Type addJumpIfR0EqualsR1(String tgt) {
         return append(new Instruction(Opcodes.JEQ, R1).setTargetLabel(tgt));
     }
 
@@ -899,7 +284,7 @@
      * Add an instruction to the end of the program to jump to {@code target} if register R0's
      * value does not equal register R1's value.
      */
-    public ApfV4Generator addJumpIfR0NotEqualsR1(String tgt) {
+    public Type addJumpIfR0NotEqualsR1(String tgt) {
         return append(new Instruction(Opcodes.JNE, R1).setTargetLabel(tgt));
     }
 
@@ -907,7 +292,7 @@
      * Add an instruction to the end of the program to jump to {@code target} if register R0's
      * value is greater than register R1's value.
      */
-    public ApfV4Generator addJumpIfR0GreaterThanR1(String tgt) {
+    public Type addJumpIfR0GreaterThanR1(String tgt) {
         return append(new Instruction(Opcodes.JGT, R1).setTargetLabel(tgt));
     }
 
@@ -915,7 +300,7 @@
      * Add an instruction to the end of the program to jump to {@code target} if register R0's
      * value is less than register R1's value.
      */
-    public ApfV4Generator addJumpIfR0LessThanR1(String target) {
+    public Type addJumpIfR0LessThanR1(String target) {
         return append(new Instruction(Opcodes.JLT, R1).setTargetLabel(target));
     }
 
@@ -923,7 +308,7 @@
      * Add an instruction to the end of the program to jump to {@code target} if register R0's
      * value has any bits set that are also set in R1's value.
      */
-    public ApfV4Generator addJumpIfR0AnyBitsSetR1(String tgt) {
+    public Type addJumpIfR0AnyBitsSetR1(String tgt) {
         return append(new Instruction(Opcodes.JSET, R1).setTargetLabel(tgt));
     }
 
@@ -932,7 +317,7 @@
      * packet at an offset specified by {@code register} don't match {@code bytes}
      * R=0 means check for not equal
      */
-    public ApfV4Generator addJumpIfBytesAtR0NotEqual(byte[] bytes, String tgt) {
+    public Type addJumpIfBytesAtR0NotEqual(byte[] bytes, String tgt) {
         return append(new Instruction(Opcodes.JNEBS).addUnsigned(
                 bytes.length).setTargetLabel(tgt).setBytesImm(bytes));
     }
@@ -942,7 +327,7 @@
      * packet at an offset specified by {@code register} match {@code bytes}
      * R=1 means check for equal.
      */
-    public ApfV4Generator addJumpIfBytesAtR0Equal(byte[] bytes, String tgt)
+    public Type addJumpIfBytesAtR0Equal(byte[] bytes, String tgt)
             throws IllegalInstructionException {
         requireApfVersion(MIN_APF_VERSION_IN_DEV);
         return append(new Instruction(Opcodes.JNEBS, R1).addUnsigned(
@@ -953,16 +338,16 @@
      * Add an instruction to the end of the program to load memory slot {@code slot} into
      * {@code register}.
      */
-    public ApfV4Generator addLoadFromMemory(Register r, int slot)
+    public Type addLoadFromMemory(Register r, int slot)
             throws IllegalInstructionException {
-        return append(new Instruction(ExtendedOpcodes.LDM, slot, r));
+        return append(new BaseApfGenerator.Instruction(ExtendedOpcodes.LDM, slot, r));
     }
 
     /**
      * Add an instruction to the end of the program to store {@code register} into memory slot
      * {@code slot}.
      */
-    public ApfV4Generator addStoreToMemory(Register r, int slot)
+    public Type addStoreToMemory(Register r, int slot)
             throws IllegalInstructionException {
         return append(new Instruction(ExtendedOpcodes.STM, slot, r));
     }
@@ -970,21 +355,21 @@
     /**
      * Add an instruction to the end of the program to logically not {@code register}.
      */
-    public ApfV4Generator addNot(Register r) {
+    public Type addNot(Register r) {
         return append(new Instruction(ExtendedOpcodes.NOT, r));
     }
 
     /**
      * Add an instruction to the end of the program to negate {@code register}.
      */
-    public ApfV4Generator addNeg(Register r) {
+    public Type addNeg(Register r) {
         return append(new Instruction(ExtendedOpcodes.NEG, r));
     }
 
     /**
      * Add an instruction to swap the values in register R0 and register R1.
      */
-    public ApfV4Generator addSwap() {
+    public Type addSwap() {
         return append(new Instruction(ExtendedOpcodes.SWAP));
     }
 
@@ -992,337 +377,16 @@
      * Add an instruction to the end of the program to move the value into
      * {@code register} from the other register.
      */
-    public ApfV4Generator addMove(Register r) {
+    public Type addMove(Register r) {
         return append(new Instruction(ExtendedOpcodes.MOVE, r));
     }
 
     /**
      * Add an instruction to the end of the program to let the program immediately return PASS.
      */
-    public ApfV4Generator addPass() {
-        // PASS requires using R0 because it shares opcode with DROP
-        return append(new Instruction(Opcodes.PASSDROP));
-    }
-
-    /**
-     * Add an instruction to the end of the program to increment the counter value and
-     * immediately return PASS.
-     */
-    public ApfV4Generator addCountAndPass(int cnt) throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        checkRange("CounterNumber", cnt /* value */, 1 /* lowerBound */,
-                1000 /* upperBound */);
-        // PASS requires using R0 because it shares opcode with DROP
-        return append(new Instruction(Opcodes.PASSDROP).addUnsigned(cnt));
-    }
-
-    /**
-     * Add an instruction to the end of the program to let the program immediately return DROP.
-     */
-    public ApfV4Generator addDrop() throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        // DROP requires using R1 because it shares opcode with PASS
-        return append(new Instruction(Opcodes.PASSDROP, R1));
-    }
-
-    /**
-     * Add an instruction to the end of the program to increment the counter value and
-     * immediately return DROP.
-     */
-    public ApfV4Generator addCountAndDrop(int cnt) throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        checkRange("CounterNumber", cnt /* value */, 1 /* lowerBound */,
-                1000 /* upperBound */);
-        // DROP requires using R1 because it shares opcode with PASS
-        return append(new Instruction(Opcodes.PASSDROP, R1).addUnsigned(cnt));
-    }
-
-    /**
-     * Add an instruction to the end of the program to call the apf_allocate_buffer() function.
-     * Buffer length to be allocated is stored in register 0.
-     */
-    public ApfV4Generator addAllocateR0() throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        return append(new Instruction(ExtendedOpcodes.ALLOCATE));
-    }
-
-    /**
-     * Add an instruction to the end of the program to call the apf_allocate_buffer() function.
-     *
-     * @param size the buffer length to be allocated.
-     */
-    public ApfV4Generator addAllocate(int size) throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        // R1 means the extra be16 immediate is present
-        return append(new Instruction(ExtendedOpcodes.ALLOCATE, R1).addU16(size));
-    }
-
-    /**
-     * Add an instruction to the beginning of the program to reserve the data region.
-     * @param data the actual data byte
-     */
-    public ApfV4Generator addData(byte[] data) throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        if (!mInstructions.isEmpty()) {
-            throw new IllegalInstructionException("data instruction has to come first");
-        }
-        return append(new Instruction(Opcodes.JMP, R1).addUnsigned(data.length).setBytesImm(data));
-    }
-
-    /**
-     * Add an instruction to the end of the program to transmit the allocated buffer.
-     */
-    public ApfV4Generator addTransmit() throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        // TRANSMIT requires using R0 because it shares opcode with DISCARD
-        return append(new Instruction(ExtendedOpcodes.TRANSMITDISCARD));
-    }
-
-    /**
-     * Add an instruction to the end of the program to discard the allocated buffer.
-     */
-    public ApfV4Generator addDiscard() throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        // DISCARD requires using R1 because it shares opcode with TRANSMIT
-        return append(new Instruction(ExtendedOpcodes.TRANSMITDISCARD, R1));
-    }
-
-    /**
-     * Add an instruction to the end of the program to write 1 byte value to output buffer.
-     */
-    public ApfV4Generator addWriteU8(int val) throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        return append(new Instruction(Opcodes.WRITE).overrideLenField(1).addU8(val));
-    }
-
-    /**
-     * Add an instruction to the end of the program to write 2 bytes value to output buffer.
-     */
-    public ApfV4Generator addWriteU16(int val) throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        return append(new Instruction(Opcodes.WRITE).overrideLenField(2).addU16(val));
-    }
-
-    /**
-     * Add an instruction to the end of the program to write 4 bytes value to output buffer.
-     */
-    public ApfV4Generator addWriteU32(long val) throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        return append(new Instruction(Opcodes.WRITE).overrideLenField(4).addU32(val));
-    }
-
-    /**
-     * Add an instruction to the end of the program to write 1 byte value from register to output
-     * buffer.
-     */
-    public ApfV4Generator addWriteU8(Register reg) throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        return append(new Instruction(ExtendedOpcodes.EWRITE1, reg));
-    }
-
-    /**
-     * Add an instruction to the end of the program to write 2 byte value from register to output
-     * buffer.
-     */
-    public ApfV4Generator addWriteU16(Register reg) throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        return append(new Instruction(ExtendedOpcodes.EWRITE2, reg));
-    }
-
-    /**
-     * Add an instruction to the end of the program to write 4 byte value from register to output
-     * buffer.
-     */
-    public ApfV4Generator addWriteU32(Register reg) throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        return append(new Instruction(ExtendedOpcodes.EWRITE4, reg));
-    }
-
-    /**
-     * Add an instruction to the end of the program to copy data from APF program/data region to
-     * output buffer and auto-increment the output buffer pointer.
-     *
-     * @param src the offset inside the APF program/data region for where to start copy.
-     * @param len the length of bytes needed to be copied, only <= 255 bytes can be copied at
-     *               one time.
-     * @return the ApfGenerator object
-     */
-    public ApfV4Generator addDataCopy(int src, int len)
-            throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        return append(new Instruction(Opcodes.PKTDATACOPY, R1).addUnsigned(src).addU8(len));
-    }
-
-    /**
-     * Add an instruction to the end of the program to copy data from input packet to output
-     * buffer and auto-increment the output buffer pointer.
-     *
-     * @param src the offset inside the input packet for where to start copy.
-     * @param len the length of bytes needed to be copied, only <= 255 bytes can be copied at
-     *               one time.
-     * @return the ApfGenerator object
-     */
-    public ApfV4Generator addPacketCopy(int src, int len)
-            throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        return append(new Instruction(Opcodes.PKTDATACOPY, R0).addUnsigned(src).addU8(len));
-    }
-
-    /**
-     * Add an instruction to the end of the program to copy data from APF program/data region to
-     * output buffer and auto-increment the output buffer pointer.
-     * Source offset is stored in R0.
-     *
-     * @param len the number of bytes to be copied, only <= 255 bytes can be copied at once.
-     * @return the ApfGenerator object
-     */
-    public ApfV4Generator addDataCopyFromR0(int len) throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        return append(new Instruction(ExtendedOpcodes.EDATACOPY).addU8(len));
-    }
-
-    /**
-     * Add an instruction to the end of the program to copy data from input packet to output
-     * buffer and auto-increment the output buffer pointer.
-     * Source offset is stored in R0.
-     *
-     * @param len the number of bytes to be copied, only <= 255 bytes can be copied at once.
-     * @return the ApfGenerator object
-     */
-    public ApfV4Generator addPacketCopyFromR0(int len) throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        return append(new Instruction(ExtendedOpcodes.EPKTCOPY).addU8(len));
-    }
-
-    /**
-     * Add an instruction to the end of the program to copy data from APF program/data region to
-     * output buffer and auto-increment the output buffer pointer.
-     * Source offset is stored in R0.
-     * Copy length is stored in R1.
-     *
-     * @return the ApfGenerator object
-     */
-    public ApfV4Generator addDataCopyFromR0LenR1() throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        return append(new Instruction(ExtendedOpcodes.EDATACOPY, R1));
-    }
-
-    /**
-     * Add an instruction to the end of the program to copy data from input packet to output
-     * buffer and auto-increment the output buffer pointer.
-     * Source offset is stored in R0.
-     * Copy length is stored in R1.
-     *
-     * @return the ApfGenerator object
-     */
-    public ApfV4Generator addPacketCopyFromR0LenR1() throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        return append(new Instruction(ExtendedOpcodes.EPKTCOPY, R1));
-    }
-
-    /**
-     * Check if the byte is valid dns character: A-Z,0-9,-,_
-     */
-    private static boolean isValidDnsCharacter(byte c) {
-        return (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_';
-    }
-
-    private static void validateNames(@NonNull byte[] names) {
-        final int len = names.length;
-        if (len < 4) {
-            throw new IllegalArgumentException("qnames must have at least length 4");
-        }
-        final String errorMessage = "qname: " + HexDump.toHexString(names)
-                + "is not null-terminated list of TLV-encoded names";
-        int i = 0;
-        while (i < len - 1) {
-            int label_len = names[i++];
-            if (label_len < 1 || label_len > 63) {
-                throw new IllegalArgumentException(
-                        "label len: " + label_len + " must be between 1 and 63");
-            }
-            if (i + label_len >= len - 1) {
-                throw new IllegalArgumentException(errorMessage);
-            }
-            while (label_len-- > 0) {
-                if (!isValidDnsCharacter(names[i++])) {
-                    throw new IllegalArgumentException("qname: " + HexDump.toHexString(names)
-                            + " contains invalid character");
-                }
-            }
-            if (names[i] == 0) {
-                i++; // skip null terminator.
-            }
-        }
-        if (names[len - 1] != 0) {
-            throw new IllegalArgumentException(errorMessage);
-        }
-    }
-
-    /**
-     * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
-     * payload's DNS questions do NOT contain the QNAMEs specified in {@code qnames} and qtype
-     * equals {@code qtype}. Examines the payload starting at the offset in R0.
-     * R = 0 means check for "does not contain".
-     */
-    public ApfV4Generator addJumpIfPktAtR0DoesNotContainDnsQ(@NonNull byte[] qnames, int qtype,
-            @NonNull String tgt) throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        validateNames(qnames);
-        return append(new Instruction(ExtendedOpcodes.JDNSQMATCH).setTargetLabel(tgt).addU8(
-                qtype).setBytesImm(qnames));
-    }
-
-    /**
-     * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
-     * payload's DNS questions contain the QNAMEs specified in {@code qnames} and qtype
-     * equals {@code qtype}. Examines the payload starting at the offset in R0.
-     * R = 1 means check for "contain".
-     */
-    public ApfV4Generator addJumpIfPktAtR0ContainDnsQ(@NonNull byte[] qnames, int qtype,
-            @NonNull String tgt) throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        validateNames(qnames);
-        return append(new Instruction(ExtendedOpcodes.JDNSQMATCH, R1).setTargetLabel(tgt).addU8(
-                qtype).setBytesImm(qnames));
-    }
-
-    /**
-     * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
-     * payload's DNS answers/authority/additional records do NOT contain the NAMEs
-     * specified in {@code Names}. Examines the payload starting at the offset in R0.
-     * R = 0 means check for "does not contain".
-     */
-    public ApfV4Generator addJumpIfPktAtR0DoesNotContainDnsA(@NonNull byte[] names,
-            @NonNull String tgt) throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        validateNames(names);
-        return append(new Instruction(ExtendedOpcodes.JDNSAMATCH).setTargetLabel(tgt).setBytesImm(
-                names));
-    }
-
-    /**
-     * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
-     * payload's DNS answers/authority/additional records contain the NAMEs
-     * specified in {@code Names}. Examines the payload starting at the offset in R0.
-     * R = 1 means check for "contain".
-     */
-    public ApfV4Generator addJumpIfPktAtR0ContainDnsA(@NonNull byte[] names,
-            @NonNull String tgt) throws IllegalInstructionException {
-        requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        validateNames(names);
-        return append(new Instruction(ExtendedOpcodes.JDNSAMATCH, R1).setTargetLabel(
-                tgt).setBytesImm(names));
-    }
-
-    private static void checkRange(@NonNull String variableName, long value, long lowerBound,
-            long upperBound) {
-        if (value >= lowerBound && value <= upperBound) {
-            return;
-        }
-        throw new IllegalArgumentException(
-                String.format("%s: %d, must be in range [%d, %d]", variableName, value, lowerBound,
-                        upperBound));
+    public Type addPass() {
+        // PASS requires using Rbit0 because it shares opcode with DROP
+        return append(new Instruction(Opcodes.PASSDROP, Rbit0));
     }
 
     /**
@@ -1331,7 +395,7 @@
      * @{code offset} to the other register.
      * Requires APF v4 or greater.
      */
-    public ApfV4Generator addLoadData(Register dst, int ofs)
+    public Type addLoadData(Register dst, int ofs)
             throws IllegalInstructionException {
         requireApfVersion(APF_VERSION_4);
         return append(new Instruction(Opcodes.LDDW, dst).addSigned(ofs));
@@ -1343,92 +407,11 @@
      * @{code offset} to the other register.
      * Requires APF v4 or greater.
      */
-    public ApfV4Generator addStoreData(Register src, int ofs)
+    public Type addStoreData(Register src, int ofs)
             throws IllegalInstructionException {
         requireApfVersion(APF_VERSION_4);
         return append(new Instruction(Opcodes.STDW, src).addSigned(ofs));
     }
 
-    /**
-     * Updates instruction offset fields using latest instruction sizes.
-     * @return current program length in bytes.
-     */
-    private int updateInstructionOffsets() {
-        int offset = 0;
-        for (Instruction instruction : mInstructions) {
-            instruction.offset = offset;
-            offset += instruction.size();
-        }
-        return offset;
-    }
-
-    /**
-     * Calculate the size of the imm.
-     */
-    private static int calculateImmSize(int imm, boolean signed) {
-        if (imm == 0) {
-            return 0;
-        }
-        if (signed && (imm >= -128 && imm <= 127) || !signed && (imm >= 0 && imm <= 255)) {
-            return 1;
-        }
-        if (signed && (imm >= -32768 && imm <= 32767) || !signed && (imm >= 0 && imm <= 65535)) {
-            return 2;
-        }
-        return 4;
-    }
-
-    /**
-     * Returns an overestimate of the size of the generated program. {@link #generate} may return
-     * a program that is smaller.
-     */
-    public int programLengthOverEstimate() {
-        return updateInstructionOffsets();
-    }
-
-    /**
-     * Generate the bytecode for the APF program.
-     * @return the bytecode.
-     * @throws IllegalStateException if a label is referenced but not defined.
-     */
-    public byte[] generate() throws IllegalInstructionException {
-        // Enforce that we can only generate once because we cannot unshrink instructions and
-        // PASS/DROP labels may move further away requiring unshrinking if we add further
-        // instructions.
-        if (mGenerated) {
-            throw new IllegalStateException("Can only generate() once!");
-        }
-        mGenerated = true;
-        int total_size;
-        boolean shrunk;
-        // Shrink the immediate value fields of instructions.
-        // As we shrink the instructions some branch offset
-        // fields may shrink also, thereby shrinking the
-        // instructions further. Loop until we've reached the
-        // minimum size. Rarely will this loop more than a few times.
-        // Limit iterations to avoid O(n^2) behavior.
-        int iterations_remaining = 10;
-        do {
-            total_size = updateInstructionOffsets();
-            // Update drop and pass label offsets.
-            mDropLabel.offset = total_size + 1;
-            mPassLabel.offset = total_size;
-            // Limit run-time in aberant circumstances.
-            if (iterations_remaining-- == 0) break;
-            // Attempt to shrink instructions.
-            shrunk = false;
-            for (Instruction instruction : mInstructions) {
-                if (instruction.shrink()) {
-                    shrunk = true;
-                }
-            }
-        } while (shrunk);
-        // Generate bytecode for instructions.
-        byte[] bytecode = new byte[total_size];
-        for (Instruction instruction : mInstructions) {
-            instruction.generate(bytecode);
-        }
-        return bytecode;
-    }
 }
 
diff --git a/src/android/net/apf/ApfV6Generator.java b/src/android/net/apf/ApfV6Generator.java
new file mode 100644
index 0000000..40f5778
--- /dev/null
+++ b/src/android/net/apf/ApfV6Generator.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2024 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.apf;
+
+import static android.net.apf.BaseApfGenerator.Rbit.Rbit0;
+import static android.net.apf.BaseApfGenerator.Rbit.Rbit1;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.HexDump;
+
+/**
+ * APFv6 assembler/generator. A tool for generating an APFv6 program.
+ *
+ * @hide
+ */
+public class ApfV6Generator extends ApfV4Generator<ApfV6Generator> {
+
+    /**
+     * Creates an ApfV6Generator instance which is able to emit instructions for the specified
+     * {@code version} of the APF interpreter. Throws {@code IllegalInstructionException} if
+     * the requested version is unsupported.
+     *
+     */
+    public ApfV6Generator() throws IllegalInstructionException {
+        super(MIN_APF_VERSION_IN_DEV);
+    }
+
+    /**
+     * Add an instruction to the end of the program to increment the counter value and
+     * immediately return PASS.
+     */
+    public ApfV6Generator addCountAndPass(int cnt) {
+        checkRange("CounterNumber", cnt /* value */, 1 /* lowerBound */,
+                1000 /* upperBound */);
+        // PASS requires using Rbit0 because it shares opcode with DROP
+        return append(new Instruction(Opcodes.PASSDROP, Rbit0).addUnsigned(cnt));
+    }
+
+    /**
+     * Add an instruction to the end of the program to let the program immediately return DROP.
+     */
+    public ApfV6Generator addDrop() {
+        // DROP requires using Rbit1 because it shares opcode with PASS
+        return append(new Instruction(Opcodes.PASSDROP, Rbit1));
+    }
+
+    /**
+     * Add an instruction to the end of the program to increment the counter value and
+     * immediately return DROP.
+     */
+    public ApfV6Generator addCountAndDrop(int cnt) {
+        checkRange("CounterNumber", cnt /* value */, 1 /* lowerBound */,
+                1000 /* upperBound */);
+        // DROP requires using Rbit1 because it shares opcode with PASS
+        return append(new Instruction(Opcodes.PASSDROP, Rbit1).addUnsigned(cnt));
+    }
+
+    /**
+     * Add an instruction to the end of the program to call the apf_allocate_buffer() function.
+     * Buffer length to be allocated is stored in register 0.
+     */
+    public ApfV6Generator addAllocateR0() {
+        return append(new Instruction(ExtendedOpcodes.ALLOCATE));
+    }
+
+    /**
+     * Add an instruction to the end of the program to call the apf_allocate_buffer() function.
+     *
+     * @param size the buffer length to be allocated.
+     */
+    public ApfV6Generator addAllocate(int size) {
+        // Rbit1 means the extra be16 immediate is present
+        return append(new Instruction(ExtendedOpcodes.ALLOCATE, Rbit1).addU16(size));
+    }
+
+    /**
+     * Add an instruction to the beginning of the program to reserve the data region.
+     * @param data the actual data byte
+     */
+    public ApfV6Generator addData(byte[] data) throws IllegalInstructionException {
+        if (!mInstructions.isEmpty()) {
+            throw new IllegalInstructionException("data instruction has to come first");
+        }
+        return append(new Instruction(Opcodes.JMP, Rbit1).addUnsigned(data.length)
+                .setBytesImm(data));
+    }
+
+    /**
+     * Add an instruction to the end of the program to transmit the allocated buffer without
+     * checksum.
+     */
+    public ApfV6Generator addTransmitWithoutChecksum() {
+        return addTransmit(-1 /* ipOfs */);
+    }
+
+    /**
+     * Add an instruction to the end of the program to transmit the allocated buffer.
+     */
+    public ApfV6Generator addTransmit(int ipOfs) {
+        if (ipOfs >= 255) {
+            throw new IllegalArgumentException("IP offset of " + ipOfs + " must be < 255");
+        }
+        if (ipOfs == -1) ipOfs = 255;
+        return append(new Instruction(ExtendedOpcodes.TRANSMIT, Rbit0).addU8(ipOfs).addU8(255));
+    }
+
+    /**
+     * Add an instruction to the end of the program to transmit the allocated buffer.
+     */
+    public ApfV6Generator addTransmitL4(int ipOfs, int csumOfs, int csumStart, int partialCsum,
+                                        boolean isUdp) {
+        if (ipOfs >= 255) {
+            throw new IllegalArgumentException("IP offset of " + ipOfs + " must be < 255");
+        }
+        if (ipOfs == -1) ipOfs = 255;
+        if (csumOfs >= 255) {
+            throw new IllegalArgumentException("L4 checksum requires csum offset of "
+                                               + csumOfs + " < 255");
+        }
+        return append(new Instruction(ExtendedOpcodes.TRANSMIT, isUdp ? Rbit1 : Rbit0)
+                .addU8(ipOfs).addU8(csumOfs).addU8(csumStart).addU16(partialCsum));
+    }
+
+    /**
+     * Add an instruction to the end of the program to write 1 byte value to output buffer.
+     */
+    public ApfV6Generator addWriteU8(int val) {
+        return append(new Instruction(Opcodes.WRITE).overrideLenField(1).addU8(val));
+    }
+
+    /**
+     * Add an instruction to the end of the program to write 2 bytes value to output buffer.
+     */
+    public ApfV6Generator addWriteU16(int val) {
+        return append(new Instruction(Opcodes.WRITE).overrideLenField(2).addU16(val));
+    }
+
+    /**
+     * Add an instruction to the end of the program to write 4 bytes value to output buffer.
+     */
+    public ApfV6Generator addWriteU32(long val) {
+        return append(new Instruction(Opcodes.WRITE).overrideLenField(4).addU32(val));
+    }
+
+    /**
+     * Add an instruction to the end of the program to write 1 byte value from register to output
+     * buffer.
+     */
+    public ApfV6Generator addWriteU8(Register reg) {
+        return append(new Instruction(ExtendedOpcodes.EWRITE1, reg));
+    }
+
+    /**
+     * Add an instruction to the end of the program to write 2 byte value from register to output
+     * buffer.
+     */
+    public ApfV6Generator addWriteU16(Register reg) {
+        return append(new Instruction(ExtendedOpcodes.EWRITE2, reg));
+    }
+
+    /**
+     * Add an instruction to the end of the program to write 4 byte value from register to output
+     * buffer.
+     */
+    public ApfV6Generator addWriteU32(Register reg) {
+        return append(new Instruction(ExtendedOpcodes.EWRITE4, reg));
+    }
+
+    /**
+     * Add an instruction to the end of the program to copy data from APF program/data region to
+     * output buffer and auto-increment the output buffer pointer.
+     *
+     * @param src the offset inside the APF program/data region for where to start copy.
+     * @param len the length of bytes needed to be copied, only <= 255 bytes can be copied at
+     *               one time.
+     * @return the ApfV6Generator object
+     */
+    public ApfV6Generator addDataCopy(int src, int len) {
+        return append(new Instruction(Opcodes.PKTDATACOPY, Rbit1).addDataOffset(src).addU8(len));
+    }
+
+    /**
+     * Add an instruction to the end of the program to copy data from input packet to output
+     * buffer and auto-increment the output buffer pointer.
+     *
+     * @param src the offset inside the input packet for where to start copy.
+     * @param len the length of bytes needed to be copied, only <= 255 bytes can be copied at
+     *               one time.
+     * @return the ApfV6Generator object
+     */
+    public ApfV6Generator addPacketCopy(int src, int len) {
+        return append(new Instruction(Opcodes.PKTDATACOPY, Rbit0).addPacketOffset(src).addU8(len));
+    }
+
+    /**
+     * Add an instruction to the end of the program to copy data from APF program/data region to
+     * output buffer and auto-increment the output buffer pointer.
+     * Source offset is stored in R0.
+     *
+     * @param len the number of bytes to be copied, only <= 255 bytes can be copied at once.
+     * @return the ApfV6Generator object
+     */
+    public ApfV6Generator addDataCopyFromR0(int len) {
+        return append(new Instruction(ExtendedOpcodes.EPKTDATACOPYIMM, Rbit1).addU8(len));
+    }
+
+    /**
+     * Add an instruction to the end of the program to copy data from input packet to output
+     * buffer and auto-increment the output buffer pointer.
+     * Source offset is stored in R0.
+     *
+     * @param len the number of bytes to be copied, only <= 255 bytes can be copied at once.
+     * @return the ApfV6Generator object
+     */
+    public ApfV6Generator addPacketCopyFromR0(int len) {
+        return append(new Instruction(ExtendedOpcodes.EPKTDATACOPYIMM, Rbit0).addU8(len));
+    }
+
+    /**
+     * Add an instruction to the end of the program to copy data from APF program/data region to
+     * output buffer and auto-increment the output buffer pointer.
+     * Source offset is stored in R0.
+     * Copy length is stored in R1.
+     *
+     * @return the ApfV6Generator object
+     */
+    public ApfV6Generator addDataCopyFromR0LenR1() {
+        return append(new Instruction(ExtendedOpcodes.EPKTDATACOPYR1, Rbit1));
+    }
+
+    /**
+     * Add an instruction to the end of the program to copy data from input packet to output
+     * buffer and auto-increment the output buffer pointer.
+     * Source offset is stored in R0.
+     * Copy length is stored in R1.
+     *
+     * @return the ApfV6Generator object
+     */
+    public ApfV6Generator addPacketCopyFromR0LenR1() {
+        return append(new Instruction(ExtendedOpcodes.EPKTDATACOPYR1, Rbit0));
+    }
+
+    /**
+     * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
+     * payload's DNS questions do NOT contain the QNAMEs specified in {@code qnames} and qtype
+     * equals {@code qtype}. Examines the payload starting at the offset in R0.
+     * R = 0 means check for "does not contain".
+     * Drops packets if packets are corrupted.
+     */
+    public ApfV6Generator addJumpIfPktAtR0DoesNotContainDnsQ(@NonNull byte[] qnames, int qtype,
+                                                             @NonNull String tgt) {
+        validateNames(qnames);
+        return append(new Instruction(ExtendedOpcodes.JDNSQMATCH, Rbit0).setTargetLabel(tgt).addU8(
+                qtype).setBytesImm(qnames));
+    }
+
+    /**
+     * Same as {@link #addJumpIfPktAtR0DoesNotContainDnsQ} except passes packets if packets are
+     * corrupted.
+     */
+    public ApfV6Generator addJumpIfPktAtR0DoesNotContainDnsQSafe(@NonNull byte[] qnames, int qtype,
+            @NonNull String tgt) {
+        validateNames(qnames);
+        return append(new Instruction(ExtendedOpcodes.JDNSQMATCHSAFE, Rbit0).setTargetLabel(
+                tgt).addU8(qtype).setBytesImm(qnames));
+    }
+
+    /**
+     * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
+     * payload's DNS questions contain the QNAMEs specified in {@code qnames} and qtype
+     * equals {@code qtype}. Examines the payload starting at the offset in R0.
+     * R = 1 means check for "contain".
+     * Drops packets if packets are corrupted.
+     */
+    public ApfV6Generator addJumpIfPktAtR0ContainDnsQ(@NonNull byte[] qnames, int qtype,
+                                                      @NonNull String tgt) {
+        validateNames(qnames);
+        return append(new Instruction(ExtendedOpcodes.JDNSQMATCH, Rbit1).setTargetLabel(tgt).addU8(
+                qtype).setBytesImm(qnames));
+    }
+
+    /**
+     * Same as {@link #addJumpIfPktAtR0ContainDnsQ} except passes packets if packets are
+     * corrupted.
+     */
+    public ApfV6Generator addJumpIfPktAtR0ContainDnsQSafe(@NonNull byte[] qnames, int qtype,
+            @NonNull String tgt) {
+        validateNames(qnames);
+        return append(new Instruction(ExtendedOpcodes.JDNSQMATCHSAFE, Rbit1).setTargetLabel(
+                tgt).addU8(qtype).setBytesImm(qnames));
+    }
+
+    /**
+     * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
+     * payload's DNS answers/authority/additional records do NOT contain the NAMEs
+     * specified in {@code Names}. Examines the payload starting at the offset in R0.
+     * R = 0 means check for "does not contain".
+     * Drops packets if packets are corrupted.
+     */
+    public ApfV6Generator addJumpIfPktAtR0DoesNotContainDnsA(@NonNull byte[] names,
+                                                             @NonNull String tgt) {
+        validateNames(names);
+        return append(new Instruction(ExtendedOpcodes.JDNSAMATCH, Rbit0).setTargetLabel(tgt)
+                        .setBytesImm(names));
+    }
+
+    /**
+     * Same as {@link #addJumpIfPktAtR0DoesNotContainDnsA} except passes packets if packets are
+     * corrupted.
+     */
+    public ApfV6Generator addJumpIfPktAtR0DoesNotContainDnsASafe(@NonNull byte[] names,
+            @NonNull String tgt) {
+        validateNames(names);
+        return append(new Instruction(ExtendedOpcodes.JDNSAMATCHSAFE, Rbit0).setTargetLabel(tgt)
+                .setBytesImm(names));
+    }
+
+    /**
+     * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
+     * payload's DNS answers/authority/additional records contain the NAMEs
+     * specified in {@code Names}. Examines the payload starting at the offset in R0.
+     * R = 1 means check for "contain".
+     * Drops packets if packets are corrupted.
+     */
+    public ApfV6Generator addJumpIfPktAtR0ContainDnsA(@NonNull byte[] names,
+                                                      @NonNull String tgt) {
+        validateNames(names);
+        return append(new Instruction(ExtendedOpcodes.JDNSAMATCH, Rbit1).setTargetLabel(
+                tgt).setBytesImm(names));
+    }
+
+    /**
+     * Same as {@link #addJumpIfPktAtR0ContainDnsA} except passes packets if packets are
+     * corrupted.
+     */
+    public ApfV6Generator addJumpIfPktAtR0ContainDnsASafe(@NonNull byte[] names,
+            @NonNull String tgt) {
+        validateNames(names);
+        return append(new Instruction(ExtendedOpcodes.JDNSAMATCHSAFE, Rbit1).setTargetLabel(
+                tgt).setBytesImm(names));
+    }
+
+    /**
+     * Check if the byte is valid dns character: A-Z,0-9,-,_
+     */
+    private static boolean isValidDnsCharacter(byte c) {
+        return (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '%';
+    }
+
+    private static void validateNames(@NonNull byte[] names) {
+        final int len = names.length;
+        if (len < 4) {
+            throw new IllegalArgumentException("qnames must have at least length 4");
+        }
+        final String errorMessage = "qname: " + HexDump.toHexString(names)
+                + "is not null-terminated list of TLV-encoded names";
+        int i = 0;
+        while (i < len - 1) {
+            int label_len = names[i++];
+            // byte == 0xff means it is a '*' wildcard
+            if (label_len == -1) continue;
+            if (label_len < 1 || label_len > 63) {
+                throw new IllegalArgumentException(
+                        "label len: " + label_len + " must be between 1 and 63");
+            }
+            if (i + label_len >= len - 1) {
+                throw new IllegalArgumentException(errorMessage);
+            }
+            while (label_len-- > 0) {
+                if (!isValidDnsCharacter(names[i++])) {
+                    throw new IllegalArgumentException("qname: " + HexDump.toHexString(names)
+                            + " contains invalid character");
+                }
+            }
+            if (names[i] == 0) {
+                i++; // skip null terminator.
+            }
+        }
+        if (names[len - 1] != 0) {
+            throw new IllegalArgumentException(errorMessage);
+        }
+    }
+}
diff --git a/src/android/net/apf/BaseApfGenerator.java b/src/android/net/apf/BaseApfGenerator.java
new file mode 100644
index 0000000..75ef639
--- /dev/null
+++ b/src/android/net/apf/BaseApfGenerator.java
@@ -0,0 +1,778 @@
+/*
+ * Copyright (C) 2024 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.apf;
+
+import static android.net.apf.BaseApfGenerator.Rbit.Rbit0;
+import static android.net.apf.BaseApfGenerator.Rbit.Rbit1;
+import static android.net.apf.BaseApfGenerator.Register.R0;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * The base class for APF assembler/generator.
+ *
+ * @hide
+ */
+public abstract class BaseApfGenerator {
+
+    public BaseApfGenerator(int mVersion) {
+        this.mVersion = mVersion;
+    }
+
+    /**
+     * This exception is thrown when an attempt is made to generate an illegal instruction.
+     */
+    public static class IllegalInstructionException extends Exception {
+        IllegalInstructionException(String msg) {
+            super(msg);
+        }
+    }
+    enum Opcodes {
+        LABEL(-1),
+        // Unconditionally pass (if R=0) or drop (if R=1) packet.
+        // An optional unsigned immediate value can be provided to encode the counter number.
+        // If the value is non-zero, the instruction increments the counter.
+        // The counter is located (-4 * counter number) bytes from the end of the data region.
+        // It is a U32 big-endian value and is always incremented by 1.
+        // This is more or less equivalent to: lddw R0, -N4; add R0,1; stdw R0, -N4; {pass,drop}
+        // e.g. "pass", "pass 1", "drop", "drop 1"
+        PASSDROP(0),
+        LDB(1),    // Load 1 byte from immediate offset, e.g. "ldb R0, [5]"
+        LDH(2),    // Load 2 bytes from immediate offset, e.g. "ldh R0, [5]"
+        LDW(3),    // Load 4 bytes from immediate offset, e.g. "ldw R0, [5]"
+        LDBX(4),   // Load 1 byte from immediate offset plus register, e.g. "ldbx R0, [5]R0"
+        LDHX(5),   // Load 2 byte from immediate offset plus register, e.g. "ldhx R0, [5]R0"
+        LDWX(6),   // Load 4 byte from immediate offset plus register, e.g. "ldwx R0, [5]R0"
+        ADD(7),    // Add, e.g. "add R0,5"
+        MUL(8),    // Multiply, e.g. "mul R0,5"
+        DIV(9),    // Divide, e.g. "div R0,5"
+        AND(10),   // And, e.g. "and R0,5"
+        OR(11),    // Or, e.g. "or R0,5"
+        SH(12),    // Left shift, e.g. "sh R0, 5" or "sh R0, -5" (shifts right)
+        LI(13),    // Load immediate, e.g. "li R0,5" (immediate encoded as signed value)
+        // Jump, e.g. "jmp label"
+        // In APFv6, we use JMP(R=1) to encode the DATA instruction. DATA is executed as a jump.
+        // It tells how many bytes of the program regions are used to store the data and followed
+        // by the actual data bytes.
+        // "e.g. data 5, abcde"
+        JMP(14),
+        JEQ(15),   // Compare equal and branch, e.g. "jeq R0,5,label"
+        JNE(16),   // Compare not equal and branch, e.g. "jne R0,5,label"
+        JGT(17),   // Compare greater than and branch, e.g. "jgt R0,5,label"
+        JLT(18),   // Compare less than and branch, e.g. "jlt R0,5,label"
+        JSET(19),  // Compare any bits set and branch, e.g. "jset R0,5,label"
+        JNEBS(20), // Compare not equal byte sequence, e.g. "jnebs R0,5,label,0x1122334455"
+        EXT(21),   // Followed by immediate indicating ExtendedOpcodes.
+        LDDW(22),  // Load 4 bytes from data memory address (register + immediate): "lddw R0, [5]R1"
+        STDW(23),  // Store 4 bytes to data memory address (register + immediate): "stdw R0, [5]R1"
+        // Write 1, 2 or 4 bytes immediate to the output buffer and auto-increment the pointer to
+        // write. e.g. "write 5"
+        WRITE(24),
+        // Copy bytes from input packet/APF program/data region to output buffer and
+        // auto-increment the output buffer pointer.
+        // Register bit is used to specify the source of data copy.
+        // R=0 means copy from packet.
+        // R=1 means copy from APF program/data region.
+        // The copy length is stored in (u8)imm2.
+        // e.g. "pktcopy 5, 5" "datacopy 5, 5"
+        PKTDATACOPY(25);
+
+        final int value;
+
+        Opcodes(int value) {
+            this.value = value;
+        }
+    }
+    // Extended opcodes. Primary opcode is Opcodes.EXT. ExtendedOpcodes are encoded in the immediate
+    // field.
+    enum ExtendedOpcodes {
+        LDM(0),   // Load from memory, e.g. "ldm R0,5"
+        STM(16),  // Store to memory, e.g. "stm R0,5"
+        NOT(32),  // Not, e.g. "not R0"
+        NEG(33),  // Negate, e.g. "neg R0"
+        SWAP(34), // Swap, e.g. "swap R0,R1"
+        MOVE(35),  // Move, e.g. "move R0,R1"
+        // Allocate writable output buffer.
+        // R=0, use register R0 to store the length. R=1, encode the length in the u16 int imm2.
+        // "e.g. allocate R0"
+        // "e.g. allocate 123"
+        ALLOCATE(36),
+        // Transmit and deallocate the buffer (transmission can be delayed until the program
+        // terminates).  Length of buffer is the output buffer pointer (0 means discard).
+        // R=1 iff udp style L4 checksum
+        // u8 imm2 - ip header offset from start of buffer (255 for non-ip packets)
+        // u8 imm3 - offset from start of buffer to store L4 checksum (255 for no L4 checksum)
+        // u8 imm4 - offset from start of buffer to begin L4 checksum calc (present iff imm3 != 255)
+        // u16 imm5 - partial checksum value to include in L4 checksum (present iff imm3 != 255)
+        // "e.g. transmit"
+        TRANSMIT(37),
+        // Write 1, 2 or 4 byte value from register to the output buffer and auto-increment the
+        // output buffer pointer.
+        // e.g. "ewrite1 r0"
+        EWRITE1(38),
+        EWRITE2(39),
+        EWRITE4(40),
+        // Copy bytes from input packet/APF program/data region to output buffer and
+        // auto-increment the output buffer pointer.
+        // Register bit is used to specify the source of data copy.
+        // R=0 means copy from packet.
+        // R=1 means copy from APF program/data region.
+        // The source offset is stored in R0, copy length is stored in u8 imm2 or R1.
+        // e.g. "epktcopy r0, 16", "edatacopy r0, 16", "epktcopy r0, r1", "edatacopy r0, r1"
+        EPKTDATACOPYIMM(41),
+        EPKTDATACOPYR1(42),
+        // Jumps if the UDP payload content (starting at R0) does [not] match one
+        // of the specified QNAMEs in question records, applying case insensitivity.
+        // SAFE version PASSES corrupt packets, while the other one DROPS.
+        // R=0/1 meaning 'does not match'/'matches'
+        // R0: Offset to UDP payload content
+        // imm1: Extended opcode
+        // imm2: Jump label offset
+        // imm3(u8): Question type (PTR/SRV/TXT/A/AAAA)
+        // imm4(bytes): null terminated list of null terminated LV-encoded QNAMEs
+        // e.g.: "jdnsqeq R0,label,0xc,\002aa\005local\0\0",
+        //       "jdnsqne R0,label,0xc,\002aa\005local\0\0"
+        JDNSQMATCH(43),
+        JDNSQMATCHSAFE(45),
+        // Jumps if the UDP payload content (starting at R0) does [not] match one
+        // of the specified NAMEs in answers/authority/additional records, applying
+        // case insensitivity.
+        // SAFE version PASSES corrupt packets, while the other one DROPS.
+        // R=0/1 meaning 'does not match'/'matches'
+        // R0: Offset to UDP payload content
+        // imm1: Extended opcode
+        // imm2: Jump label offset
+        // imm3(bytes): null terminated list of null terminated LV-encoded NAMEs
+        // e.g.: "jdnsaeq R0,label,0xc,\002aa\005local\0\0",
+        //       "jdnsane R0,label,0xc,\002aa\005local\0\0"
+
+        JDNSAMATCH(44),
+        JDNSAMATCHSAFE(46);
+
+        final int value;
+
+        ExtendedOpcodes(int value) {
+            this.value = value;
+        }
+    }
+    public enum Register {
+        R0,
+        R1;
+    }
+
+    public enum Rbit {
+        Rbit0(0),
+        Rbit1(1);
+
+        final int value;
+
+        Rbit(int value) {
+            this.value = value;
+        }
+    }
+
+    private enum IntImmediateType {
+        INDETERMINATE_SIZE_SIGNED,
+        INDETERMINATE_SIZE_UNSIGNED,
+        SIGNED_8,
+        UNSIGNED_8,
+        SIGNED_BE16,
+        UNSIGNED_BE16,
+        SIGNED_BE32,
+        UNSIGNED_BE32;
+    }
+
+    private static class IntImmediate {
+        public final IntImmediateType mImmediateType;
+        public final int mValue;
+
+        IntImmediate(int value, IntImmediateType type) {
+            mImmediateType = type;
+            mValue = value;
+        }
+
+        private int calculateIndeterminateSize() {
+            switch (mImmediateType) {
+                case INDETERMINATE_SIZE_SIGNED:
+                    return calculateImmSize(mValue, true /* signed */);
+                case INDETERMINATE_SIZE_UNSIGNED:
+                    return calculateImmSize(mValue, false /* signed */);
+                default:
+                    // For IMM with determinate size, return 0 to allow Math.max() calculation in
+                    // caller function.
+                    return 0;
+            }
+        }
+
+        private int getEncodingSize(int immFieldSize) {
+            switch (mImmediateType) {
+                case SIGNED_8:
+                case UNSIGNED_8:
+                    return 1;
+                case SIGNED_BE16:
+                case UNSIGNED_BE16:
+                    return 2;
+                case SIGNED_BE32:
+                case UNSIGNED_BE32:
+                    return 4;
+                case INDETERMINATE_SIZE_SIGNED:
+                case INDETERMINATE_SIZE_UNSIGNED: {
+                    int minSizeRequired = calculateIndeterminateSize();
+                    if (minSizeRequired > immFieldSize) {
+                        throw new IllegalStateException(
+                                String.format("immFieldSize: %d is too small to encode value %d",
+                                        immFieldSize, mValue));
+                    }
+                    return immFieldSize;
+                }
+            }
+            throw new IllegalStateException("UnhandledInvalid IntImmediateType: " + mImmediateType);
+        }
+
+        private int writeValue(byte[] bytecode, Integer writingOffset, int immFieldSize) {
+            return Instruction.writeValue(mValue, bytecode, writingOffset,
+                    getEncodingSize(immFieldSize));
+        }
+
+        public static IntImmediate newSigned(int imm) {
+            return new IntImmediate(imm, IntImmediateType.INDETERMINATE_SIZE_SIGNED);
+        }
+
+        public static IntImmediate newUnsigned(long imm) {
+            // upperBound is 2^32 - 1
+            checkRange("Unsigned IMM", imm, 0 /* lowerBound */,
+                    4294967295L /* upperBound */);
+            return new IntImmediate((int) imm, IntImmediateType.INDETERMINATE_SIZE_UNSIGNED);
+        }
+
+        public static IntImmediate newTwosComplementUnsigned(long imm) {
+            checkRange("Unsigned TwosComplement IMM", imm, Integer.MIN_VALUE,
+                    4294967295L /* upperBound */);
+            return new IntImmediate((int) imm, IntImmediateType.INDETERMINATE_SIZE_UNSIGNED);
+        }
+
+        public static IntImmediate newTwosComplementSigned(long imm) {
+            checkRange("Signed TwosComplement IMM", imm, Integer.MIN_VALUE,
+                    4294967295L /* upperBound */);
+            return new IntImmediate((int) imm, IntImmediateType.INDETERMINATE_SIZE_SIGNED);
+        }
+
+        public static IntImmediate newS8(byte imm) {
+            checkRange("S8 IMM", imm, Byte.MIN_VALUE, Byte.MAX_VALUE);
+            return new IntImmediate(imm, IntImmediateType.SIGNED_8);
+        }
+
+        public static IntImmediate newU8(int imm) {
+            checkRange("U8 IMM", imm, 0, 255);
+            return new IntImmediate(imm, IntImmediateType.UNSIGNED_8);
+        }
+
+        public static IntImmediate newS16(short imm) {
+            return new IntImmediate(imm, IntImmediateType.SIGNED_BE16);
+        }
+
+        public static IntImmediate newU16(int imm) {
+            checkRange("U16 IMM", imm, 0, 65535);
+            return new IntImmediate(imm, IntImmediateType.UNSIGNED_BE16);
+        }
+
+        public static IntImmediate newS32(int imm) {
+            return new IntImmediate(imm, IntImmediateType.SIGNED_BE32);
+        }
+
+        public static IntImmediate newU32(long imm) {
+            // upperBound is 2^32 - 1
+            checkRange("U32 IMM", imm, 0 /* lowerBound */,
+                    4294967295L /* upperBound */);
+            return new IntImmediate((int) imm, IntImmediateType.UNSIGNED_BE32);
+        }
+
+        @Override
+        public String toString() {
+            return "IntImmediate{" + "mImmediateType=" + mImmediateType + ", mValue=" + mValue
+                    + '}';
+        }
+    }
+
+    class Instruction {
+        private final Opcodes mOpcode;
+        private final Rbit mRbit;
+        public final List<IntImmediate> mIntImms = new ArrayList<>();
+        // When mOpcode is a jump:
+        private int mTargetLabelSize;
+        private int mLenFieldOverride = -1;
+        private String mTargetLabel;
+        // When mOpcode == Opcodes.LABEL:
+        private String mLabel;
+        private byte[] mBytesImm;
+        // Offset in bytes from the beginning of this program.
+        // Set by {@link BaseApfGenerator#generate}.
+        int offset;
+
+        Instruction(Opcodes opcode, Rbit rbit) {
+            mOpcode = opcode;
+            mRbit = rbit;
+        }
+
+        Instruction(Opcodes opcode, Register register) {
+            this(opcode, register == R0 ? Rbit0 : Rbit1);
+        }
+
+        Instruction(ExtendedOpcodes extendedOpcodes, Rbit rbit) {
+            this(Opcodes.EXT, rbit);
+            addUnsigned(extendedOpcodes.value);
+        }
+
+        Instruction(ExtendedOpcodes extendedOpcodes, Register register) {
+            this(Opcodes.EXT, register);
+            addUnsigned(extendedOpcodes.value);
+        }
+
+        Instruction(ExtendedOpcodes extendedOpcodes, int slot, Register register)
+                throws IllegalInstructionException {
+            this(Opcodes.EXT, register);
+            if (slot < 0 || slot >= MEMORY_SLOTS) {
+                throw new IllegalInstructionException("illegal memory slot number: " + slot);
+            }
+            addUnsigned(extendedOpcodes.value + slot);
+        }
+
+        Instruction(Opcodes opcode) {
+            this(opcode, R0);
+        }
+
+        Instruction(ExtendedOpcodes extendedOpcodes) {
+            this(extendedOpcodes, R0);
+        }
+
+        Instruction addSigned(int imm) {
+            mIntImms.add(IntImmediate.newSigned(imm));
+            return this;
+        }
+
+        Instruction addUnsigned(long imm) {
+            mIntImms.add(IntImmediate.newUnsigned(imm));
+            return this;
+        }
+
+        // in practice, 'int' always enough for packet offset
+        Instruction addPacketOffset(int imm) {
+            return addUnsigned(imm);
+        }
+
+        // in practice, 'int' always enough for data offset
+        Instruction addDataOffset(int imm) {
+            return addUnsigned(imm);
+        }
+
+        Instruction addTwosCompSigned(int imm) {
+            mIntImms.add(IntImmediate.newTwosComplementSigned(imm));
+            return this;
+        }
+
+
+        Instruction addTwosCompUnsigned(int imm) {
+            mIntImms.add(IntImmediate.newTwosComplementUnsigned(imm));
+            return this;
+        }
+
+        Instruction addS8(byte imm) {
+            mIntImms.add(IntImmediate.newS8(imm));
+            return this;
+        }
+
+        Instruction addU8(int imm) {
+            mIntImms.add(IntImmediate.newU8(imm));
+            return this;
+        }
+
+        Instruction addS16(short imm) {
+            mIntImms.add(IntImmediate.newS16(imm));
+            return this;
+        }
+
+        Instruction addU16(int imm) {
+            mIntImms.add(IntImmediate.newU16(imm));
+            return this;
+        }
+
+        Instruction addS32(int imm) {
+            mIntImms.add(IntImmediate.newS32(imm));
+            return this;
+        }
+
+        Instruction addU32(long imm) {
+            mIntImms.add(IntImmediate.newU32(imm));
+            return this;
+        }
+
+        Instruction setLabel(String label) throws IllegalInstructionException {
+            if (mLabels.containsKey(label)) {
+                throw new IllegalInstructionException("duplicate label " + label);
+            }
+            if (mOpcode != Opcodes.LABEL) {
+                throw new IllegalStateException("adding label to non-label instruction");
+            }
+            mLabel = label;
+            mLabels.put(label, this);
+            return this;
+        }
+
+        Instruction setTargetLabel(String label) {
+            mTargetLabel = label;
+            mTargetLabelSize = 4; // May shrink later on in generate().
+            return this;
+        }
+
+        Instruction overrideLenField(int size) {
+            mLenFieldOverride = size;
+            return this;
+        }
+
+        Instruction setBytesImm(byte[] bytes) {
+            mBytesImm = bytes;
+            return this;
+        }
+
+        /**
+         * @return size of instruction in bytes.
+         */
+        int size() {
+            if (mOpcode == Opcodes.LABEL) {
+                return 0;
+            }
+            int size = 1;
+            int indeterminateSize = calculateRequiredIndeterminateSize();
+            for (IntImmediate imm : mIntImms) {
+                size += imm.getEncodingSize(indeterminateSize);
+            }
+            if (mTargetLabel != null) {
+                size += indeterminateSize;
+            }
+            if (mBytesImm != null) {
+                size += mBytesImm.length;
+            }
+            return size;
+        }
+
+        /**
+         * Resize immediate value field so that it's only as big as required to
+         * contain the offset of the jump destination.
+         * @return {@code true} if shrunk.
+         */
+        boolean shrink() throws IllegalInstructionException {
+            if (mTargetLabel == null) {
+                return false;
+            }
+            int oldTargetLabelSize = mTargetLabelSize;
+            mTargetLabelSize = calculateImmSize(calculateTargetLabelOffset(), false);
+            if (mTargetLabelSize > oldTargetLabelSize) {
+                throw new IllegalStateException("instruction grew");
+            }
+            return mTargetLabelSize < oldTargetLabelSize;
+        }
+
+        /**
+         * Assemble value for instruction size field.
+         */
+        private int generateImmSizeField() {
+            // If we already know the size the length field, just use it
+            switch (mLenFieldOverride) {
+                case -1:
+                    break;
+                case 1:
+                    return 1;
+                case 2:
+                    return 2;
+                case 4:
+                    return 3;
+                default:
+                    throw new IllegalStateException(
+                            "mLenFieldOverride has invalid value: " + mLenFieldOverride);
+            }
+            // Otherwise, calculate
+            int immSize = calculateRequiredIndeterminateSize();
+            // Encode size field to fit in 2 bits: 0->0, 1->1, 2->2, 3->4.
+            return immSize == 4 ? 3 : immSize;
+        }
+
+        /**
+         * Assemble first byte of generated instruction.
+         */
+        private byte generateInstructionByte() {
+            int sizeField = generateImmSizeField();
+            return (byte) ((mOpcode.value << 3) | (sizeField << 1) | (byte) mRbit.value);
+        }
+
+        /**
+         * Write {@code value} at offset {@code writingOffset} into {@code bytecode}.
+         * {@code immSize} bytes are written. {@code value} is truncated to
+         * {@code immSize} bytes. {@code value} is treated simply as a
+         * 32-bit value, so unsigned values should be zero extended and the truncation
+         * should simply throw away their zero-ed upper bits, and signed values should
+         * be sign extended and the truncation should simply throw away their signed
+         * upper bits.
+         */
+        private static int writeValue(int value, byte[] bytecode, int writingOffset, int immSize) {
+            for (int i = immSize - 1; i >= 0; i--) {
+                bytecode[writingOffset++] = (byte) ((value >> (i * 8)) & 255);
+            }
+            return writingOffset;
+        }
+
+        /**
+         * Generate bytecode for this instruction at offset {@link Instruction#offset}.
+         */
+        void generate(byte[] bytecode) throws IllegalInstructionException {
+            if (mOpcode == Opcodes.LABEL) {
+                return;
+            }
+            int writingOffset = offset;
+            bytecode[writingOffset++] = generateInstructionByte();
+            int indeterminateSize = calculateRequiredIndeterminateSize();
+            int startOffset = 0;
+            if (mOpcode == Opcodes.EXT) {
+                // For extend opcode, always write the actual opcode first.
+                writingOffset = mIntImms.get(startOffset++).writeValue(bytecode, writingOffset,
+                        indeterminateSize);
+            }
+            if (mTargetLabel != null) {
+                writingOffset = writeValue(calculateTargetLabelOffset(), bytecode, writingOffset,
+                        indeterminateSize);
+            }
+            for (int i = startOffset; i < mIntImms.size(); ++i) {
+                writingOffset = mIntImms.get(i).writeValue(bytecode, writingOffset,
+                        indeterminateSize);
+            }
+            if (mBytesImm != null) {
+                System.arraycopy(mBytesImm, 0, bytecode, writingOffset, mBytesImm.length);
+                writingOffset += mBytesImm.length;
+            }
+            if ((writingOffset - offset) != size()) {
+                throw new IllegalStateException("wrote " + (writingOffset - offset)
+                        + " but should have written " + size());
+            }
+        }
+
+        /**
+         * Calculates the maximum indeterminate size of all IMMs in this instruction.
+         * <p>
+         * This method finds the largest size needed to encode any indeterminate-sized IMMs in
+         * the instruction. This size will be stored in the immLen field.
+         */
+        private int calculateRequiredIndeterminateSize() {
+            int maxSize = mTargetLabelSize;
+            for (IntImmediate imm : mIntImms) {
+                maxSize = Math.max(maxSize, imm.calculateIndeterminateSize());
+            }
+            return maxSize;
+        }
+
+        private int calculateTargetLabelOffset() throws IllegalInstructionException {
+            Instruction targetLabelInstruction;
+            if (mTargetLabel == DROP_LABEL) {
+                targetLabelInstruction = mDropLabel;
+            } else if (mTargetLabel == PASS_LABEL) {
+                targetLabelInstruction = mPassLabel;
+            } else {
+                targetLabelInstruction = mLabels.get(mTargetLabel);
+            }
+            if (targetLabelInstruction == null) {
+                throw new IllegalInstructionException("label not found: " + mTargetLabel);
+            }
+            // Calculate distance from end of this instruction to instruction.offset.
+            final int targetLabelOffset = targetLabelInstruction.offset - (offset + size());
+            return targetLabelOffset;
+        }
+    }
+
+    /**
+     * Updates instruction offset fields using latest instruction sizes.
+     * @return current program length in bytes.
+     */
+    private int updateInstructionOffsets() {
+        int offset = 0;
+        for (Instruction instruction : mInstructions) {
+            instruction.offset = offset;
+            offset += instruction.size();
+        }
+        return offset;
+    }
+
+    /**
+     * Calculate the size of the imm.
+     */
+    private static int calculateImmSize(int imm, boolean signed) {
+        if (imm == 0) {
+            return 0;
+        }
+        if (signed && (imm >= -128 && imm <= 127) || !signed && (imm >= 0 && imm <= 255)) {
+            return 1;
+        }
+        if (signed && (imm >= -32768 && imm <= 32767) || !signed && (imm >= 0 && imm <= 65535)) {
+            return 2;
+        }
+        return 4;
+    }
+
+    static void checkRange(@NonNull String variableName, long value, long lowerBound,
+                           long upperBound) {
+        if (value >= lowerBound && value <= upperBound) {
+            return;
+        }
+        throw new IllegalArgumentException(
+                String.format("%s: %d, must be in range [%d, %d]", variableName, value, lowerBound,
+                        upperBound));
+    }
+
+    /**
+     * Returns an overestimate of the size of the generated program. {@link #generate} may return
+     * a program that is smaller.
+     */
+    public int programLengthOverEstimate() {
+        return updateInstructionOffsets();
+    }
+
+    /**
+     * Generate the bytecode for the APF program.
+     * @return the bytecode.
+     * @throws IllegalStateException if a label is referenced but not defined.
+     */
+    public byte[] generate() throws IllegalInstructionException {
+        // Enforce that we can only generate once because we cannot unshrink instructions and
+        // PASS/DROP labels may move further away requiring unshrinking if we add further
+        // instructions.
+        if (mGenerated) {
+            throw new IllegalStateException("Can only generate() once!");
+        }
+        mGenerated = true;
+        int total_size;
+        boolean shrunk;
+        // Shrink the immediate value fields of instructions.
+        // As we shrink the instructions some branch offset
+        // fields may shrink also, thereby shrinking the
+        // instructions further. Loop until we've reached the
+        // minimum size. Rarely will this loop more than a few times.
+        // Limit iterations to avoid O(n^2) behavior.
+        int iterations_remaining = 10;
+        do {
+            total_size = updateInstructionOffsets();
+            // Update drop and pass label offsets.
+            mDropLabel.offset = total_size + 1;
+            mPassLabel.offset = total_size;
+            // Limit run-time in aberant circumstances.
+            if (iterations_remaining-- == 0) break;
+            // Attempt to shrink instructions.
+            shrunk = false;
+            for (Instruction instruction : mInstructions) {
+                if (instruction.shrink()) {
+                    shrunk = true;
+                }
+            }
+        } while (shrunk);
+        // Generate bytecode for instructions.
+        byte[] bytecode = new byte[total_size];
+        for (Instruction instruction : mInstructions) {
+            instruction.generate(bytecode);
+        }
+        return bytecode;
+    }
+
+    /**
+     * Returns true if the BaseApfGenerator supports the specified {@code version}, otherwise false.
+     */
+    public static boolean supportsVersion(int version) {
+        return version >= MIN_APF_VERSION;
+    }
+
+    void requireApfVersion(int minimumVersion) throws IllegalInstructionException {
+        if (mVersion < minimumVersion) {
+            throw new IllegalInstructionException("Requires APF >= " + minimumVersion);
+        }
+    }
+
+    /**
+     * Jump to this label to terminate the program and indicate the packet
+     * should be dropped.
+     */
+    public static final String DROP_LABEL = "__DROP__";
+
+    /**
+     * Jump to this label to terminate the program and indicate the packet
+     * should be passed to the AP.
+     */
+    public static final String PASS_LABEL = "__PASS__";
+
+    /**
+     * Number of memory slots available for access via APF stores to memory and loads from memory.
+     * The memory slots are numbered 0 to {@code MEMORY_SLOTS} - 1. This must be kept in sync with
+     * the APF interpreter.
+     */
+    public static final int MEMORY_SLOTS = 16;
+
+    /**
+     * Memory slot number that is prefilled with the IPv4 header length.
+     * Note that this memory slot may be overwritten by a program that
+     * executes stores to this memory slot. This must be kept in sync with
+     * the APF interpreter.
+     */
+    public static final int IPV4_HEADER_SIZE_MEMORY_SLOT = 13;
+
+    /**
+     * Memory slot number that is prefilled with the size of the packet being filtered in bytes.
+     * Note that this memory slot may be overwritten by a program that
+     * executes stores to this memory slot. This must be kept in sync with the APF interpreter.
+     */
+    public static final int PACKET_SIZE_MEMORY_SLOT = 14;
+
+    /**
+     * Memory slot number that is prefilled with the age of the filter in seconds. The age of the
+     * filter is the time since the filter was installed until now.
+     * Note that this memory slot may be overwritten by a program that
+     * executes stores to this memory slot. This must be kept in sync with the APF interpreter.
+     */
+    public static final int FILTER_AGE_MEMORY_SLOT = 15;
+
+    /**
+     * First memory slot containing prefilled values. Can be used in range comparisons to determine
+     * if memory slot index is within prefilled slots.
+     */
+    public static final int FIRST_PREFILLED_MEMORY_SLOT = IPV4_HEADER_SIZE_MEMORY_SLOT;
+
+    /**
+     * Last memory slot containing prefilled values. Can be used in range comparisons to determine
+     * if memory slot index is within prefilled slots.
+     */
+    public static final int LAST_PREFILLED_MEMORY_SLOT = FILTER_AGE_MEMORY_SLOT;
+
+    // This version number syncs up with APF_VERSION in hardware/google/apf/apf_interpreter.h
+    public static final int MIN_APF_VERSION = 2;
+    public static final int MIN_APF_VERSION_IN_DEV = 5;
+    public static final int APF_VERSION_4 = 4;
+
+
+    final ArrayList<Instruction> mInstructions = new ArrayList<Instruction>();
+    private final HashMap<String, Instruction> mLabels = new HashMap<String, Instruction>();
+    private final Instruction mDropLabel = new Instruction(Opcodes.LABEL);
+    private final Instruction mPassLabel = new Instruction(Opcodes.LABEL);
+    private final int mVersion;
+    public boolean mGenerated;
+}
diff --git a/src/android/net/apf/DnsUtils.java b/src/android/net/apf/DnsUtils.java
index f0f3039..4fa02be 100644
--- a/src/android/net/apf/DnsUtils.java
+++ b/src/android/net/apf/DnsUtils.java
@@ -16,8 +16,8 @@
 
 package android.net.apf;
 
-import static android.net.apf.ApfV4Generator.Register.R0;
-import static android.net.apf.ApfV4Generator.Register.R1;
+import static android.net.apf.BaseApfGenerator.Register.R0;
+import static android.net.apf.BaseApfGenerator.Register.R1;
 
 import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
 import static com.android.net.module.util.NetworkStackConstants.UDP_HEADER_LEN;
diff --git a/src/android/net/apf/JumpTable.java b/src/android/net/apf/JumpTable.java
index 9b01225..016b01b 100644
--- a/src/android/net/apf/JumpTable.java
+++ b/src/android/net/apf/JumpTable.java
@@ -16,7 +16,7 @@
 
 package android.net.apf;
 
-import static android.net.apf.ApfV4Generator.Register.R0;
+import static android.net.apf.BaseApfGenerator.Register.R0;
 
 import androidx.annotation.NonNull;
 
diff --git a/src/android/net/apf/LegacyApfFilter.java b/src/android/net/apf/LegacyApfFilter.java
index d81f8b4..20a9e33 100644
--- a/src/android/net/apf/LegacyApfFilter.java
+++ b/src/android/net/apf/LegacyApfFilter.java
@@ -16,6 +16,8 @@
 
 package android.net.apf;
 
+import static android.net.apf.BaseApfGenerator.Register.R0;
+import static android.net.apf.BaseApfGenerator.Register.R1;
 import static android.net.util.SocketUtils.makePacketSocketAddress;
 import static android.system.OsConstants.AF_PACKET;
 import static android.system.OsConstants.ARPHRD_ETHER;
@@ -43,8 +45,7 @@
 import android.net.NattKeepalivePacketDataParcelable;
 import android.net.TcpKeepalivePacketDataParcelable;
 import android.net.apf.ApfCounterTracker.Counter;
-import android.net.apf.ApfV4Generator.IllegalInstructionException;
-import android.net.apf.ApfV4Generator.Register;
+import android.net.apf.BaseApfGenerator.IllegalInstructionException;
 import android.net.ip.IpClient.IpClientCallbacksWrapper;
 import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.ApfStats;
@@ -126,7 +127,7 @@
      */
     private void maybeSetupCounter(ApfV4Generator gen, Counter c) {
         if (mApfCapabilities.hasDataAccess()) {
-            gen.addLoadImmediate(Register.R1, c.offset());
+            gen.addLoadImmediate(R1, c.offset());
         }
     }
 
@@ -972,15 +973,15 @@
         long generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException {
             String nextFilterLabel = "Ra" + getUniqueNumberLocked();
             // Skip if packet is not the right size
-            gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
+            gen.addLoadFromMemory(R0, gen.PACKET_SIZE_MEMORY_SLOT);
             gen.addJumpIfR0NotEquals(mPacket.capacity(), nextFilterLabel);
             // Skip filter if expired
-            gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
+            gen.addLoadFromMemory(R0, gen.FILTER_AGE_MEMORY_SLOT);
             gen.addJumpIfR0GreaterThan(filterLifetime(), nextFilterLabel);
             for (PacketSection section : mPacketSections) {
                 // Generate code to match the packet bytes.
                 if (section.type == PacketSection.Type.MATCH) {
-                    gen.addLoadImmediate(Register.R0, section.start);
+                    gen.addLoadImmediate(R0, section.start);
                     gen.addJumpIfBytesAtR0NotEqual(
                             Arrays.copyOfRange(mPacket.array(), section.start,
                                     section.start + section.length),
@@ -991,8 +992,8 @@
                 // The packet is accepted if any non-ignored lifetime is lower than filterLifetime.
                 if (isRelevantLifetime(section)) {
                     switch (section.length) {
-                        case 4: gen.addLoad32(Register.R0, section.start); break;
-                        case 2: gen.addLoad16(Register.R0, section.start); break;
+                        case 4: gen.addLoad32(R0, section.start); break;
+                        case 2: gen.addLoad16(R0, section.start); break;
                         default:
                             throw new IllegalStateException(
                                     "bogus lifetime size " + section.length);
@@ -1064,21 +1065,21 @@
         void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException {
             final String nextFilterLabel = "natt_keepalive_filter" + getUniqueNumberLocked();
 
-            gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
+            gen.addLoadImmediate(R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
             gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel);
 
             // A NAT-T keepalive packet contains 1 byte payload with the value 0xff
             // Check payload length is 1
-            gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+            gen.addLoadFromMemory(R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
             gen.addAdd(UDP_HEADER_LEN);
             gen.addSwap();
-            gen.addLoad16(Register.R0, IPV4_TOTAL_LENGTH_OFFSET);
-            gen.addNeg(Register.R1);
+            gen.addLoad16(R0, IPV4_TOTAL_LENGTH_OFFSET);
+            gen.addNeg(R1);
             gen.addAddR1();
             gen.addJumpIfR0NotEquals(1, nextFilterLabel);
 
             // Check that the ports match
-            gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+            gen.addLoadFromMemory(R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
             gen.addAdd(ETH_HEADER_LEN);
             gen.addJumpIfBytesAtR0NotEqual(mPortFingerprint, nextFilterLabel);
 
@@ -1180,28 +1181,28 @@
         void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException {
             final String nextFilterLabel = "keepalive_ack" + getUniqueNumberLocked();
 
-            gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
+            gen.addLoadImmediate(R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
             gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel);
 
             // Skip to the next filter if it's not zero-sized :
             // TCP_HEADER_SIZE + IPV4_HEADER_SIZE - ipv4_total_length == 0
             // Load the IP header size into R1
-            gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+            gen.addLoadFromMemory(R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
             // Load the TCP header size into R0 (it's indexed by R1)
-            gen.addLoad8Indexed(Register.R0, ETH_HEADER_LEN + TCP_HEADER_SIZE_OFFSET);
+            gen.addLoad8Indexed(R0, ETH_HEADER_LEN + TCP_HEADER_SIZE_OFFSET);
             // Size offset is in the top nibble, but it must be multiplied by 4, and the two
             // top bits of the low nibble are guaranteed to be zeroes. Right-shift R0 by 2.
             gen.addRightShift(2);
             // R0 += R1 -> R0 contains TCP + IP headers length
             gen.addAddR1();
             // Load IPv4 total length
-            gen.addLoad16(Register.R1, IPV4_TOTAL_LENGTH_OFFSET);
-            gen.addNeg(Register.R0);
+            gen.addLoad16(R1, IPV4_TOTAL_LENGTH_OFFSET);
+            gen.addNeg(R0);
             gen.addAddR1();
             gen.addJumpIfR0NotEquals(0, nextFilterLabel);
             // Add IPv4 header length
-            gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
-            gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN);
+            gen.addLoadFromMemory(R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+            gen.addLoadImmediate(R0, ETH_HEADER_LEN);
             gen.addAddR1();
             gen.addJumpIfBytesAtR0NotEqual(mPortSeqAckFingerprint, nextFilterLabel);
 
@@ -1315,23 +1316,23 @@
         final String checkTargetIPv4 = "checkTargetIPv4";
 
         // Pass if not ARP IPv4.
-        gen.addLoadImmediate(Register.R0, ARP_HEADER_OFFSET);
+        gen.addLoadImmediate(R0, ARP_HEADER_OFFSET);
         maybeSetupCounter(gen, Counter.PASSED_ARP_NON_IPV4);
         gen.addJumpIfBytesAtR0NotEqual(ARP_IPV4_HEADER, mCountAndPassLabel);
 
         // Pass if unknown ARP opcode.
-        gen.addLoad16(Register.R0, ARP_OPCODE_OFFSET);
+        gen.addLoad16(R0, ARP_OPCODE_OFFSET);
         gen.addJumpIfR0Equals(ARP_OPCODE_REQUEST, checkTargetIPv4); // Skip to unicast check
         maybeSetupCounter(gen, Counter.PASSED_ARP_UNKNOWN);
         gen.addJumpIfR0NotEquals(ARP_OPCODE_REPLY, mCountAndPassLabel);
 
         // Drop if ARP reply source IP is 0.0.0.0
-        gen.addLoad32(Register.R0, ARP_SOURCE_IP_ADDRESS_OFFSET);
+        gen.addLoad32(R0, ARP_SOURCE_IP_ADDRESS_OFFSET);
         maybeSetupCounter(gen, Counter.DROPPED_ARP_REPLY_SPA_NO_HOST);
         gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel);
 
         // Pass if unicast reply.
-        gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
+        gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
         maybeSetupCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY);
         gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
 
@@ -1339,13 +1340,13 @@
         gen.defineLabel(checkTargetIPv4);
         if (mIPv4Address == null) {
             // When there is no IPv4 address, drop GARP replies (b/29404209).
-            gen.addLoad32(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
+            gen.addLoad32(R0, ARP_TARGET_IP_ADDRESS_OFFSET);
             maybeSetupCounter(gen, Counter.DROPPED_GARP_REPLY);
             gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel);
         } else {
             // When there is an IPv4 address, drop unicast/broadcast requests
             // and broadcast replies with a different target IPv4 address.
-            gen.addLoadImmediate(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
+            gen.addLoadImmediate(R0, ARP_TARGET_IP_ADDRESS_OFFSET);
             maybeSetupCounter(gen, Counter.DROPPED_ARP_OTHER_HOST);
             gen.addJumpIfBytesAtR0NotEqual(mIPv4Address, mCountAndDropLabel);
         }
@@ -1382,17 +1383,17 @@
 
             // Pass DHCP addressed to us.
             // Check it's UDP.
-            gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET);
+            gen.addLoad8(R0, IPV4_PROTOCOL_OFFSET);
             gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipDhcpv4Filter);
             // Check it's not a fragment. This matches the BPF filter installed by the DHCP client.
-            gen.addLoad16(Register.R0, IPV4_FRAGMENT_OFFSET_OFFSET);
+            gen.addLoad16(R0, IPV4_FRAGMENT_OFFSET_OFFSET);
             gen.addJumpIfR0AnyBitsSet(IPV4_FRAGMENT_OFFSET_MASK, skipDhcpv4Filter);
             // Check it's addressed to DHCP client port.
-            gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
-            gen.addLoad16Indexed(Register.R0, UDP_DESTINATION_PORT_OFFSET);
+            gen.addLoadFromMemory(R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+            gen.addLoad16Indexed(R0, UDP_DESTINATION_PORT_OFFSET);
             gen.addJumpIfR0NotEquals(DHCP_CLIENT_PORT, skipDhcpv4Filter);
             // Check it's DHCP to our MAC address.
-            gen.addLoadImmediate(Register.R0, DHCP_CLIENT_MAC_OFFSET);
+            gen.addLoadImmediate(R0, DHCP_CLIENT_MAC_OFFSET);
             // NOTE: Relies on R1 containing IPv4 header offset.
             gen.addAddR1();
             gen.addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipDhcpv4Filter);
@@ -1403,14 +1404,14 @@
             gen.defineLabel(skipDhcpv4Filter);
 
             // If IPv4 destination address is in multicast range, drop.
-            gen.addLoad8(Register.R0, IPV4_DEST_ADDR_OFFSET);
+            gen.addLoad8(R0, IPV4_DEST_ADDR_OFFSET);
             gen.addAnd(0xf0);
             maybeSetupCounter(gen, Counter.DROPPED_IPV4_MULTICAST);
             gen.addJumpIfR0Equals(0xe0, mCountAndDropLabel);
 
             // If IPv4 broadcast packet, drop regardless of L2 (b/30231088).
             maybeSetupCounter(gen, Counter.DROPPED_IPV4_BROADCAST_ADDR);
-            gen.addLoad32(Register.R0, IPV4_DEST_ADDR_OFFSET);
+            gen.addLoad32(R0, IPV4_DEST_ADDR_OFFSET);
             gen.addJumpIfR0Equals(IPV4_BROADCAST_ADDRESS, mCountAndDropLabel);
             if (mIPv4Address != null && mIPv4PrefixLength < 31) {
                 maybeSetupCounter(gen, Counter.DROPPED_IPV4_BROADCAST_NET);
@@ -1428,7 +1429,7 @@
             // If L2 broadcast packet, drop.
             // TODO: can we invert this condition to fall through to the common pass case below?
             maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST);
-            gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
+            gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
             gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
             maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST);
             gen.addJump(mCountAndDropLabel);
@@ -1452,7 +1453,7 @@
         if (!haveKeepaliveResponses) return;
 
         // If not the right proto, skip keepalive filters
-        gen.addLoad8(Register.R0, offset);
+        gen.addLoad8(R0, offset);
         gen.addJumpIfR0NotEquals(proto, label);
 
         // Drop Keepalive responses
@@ -1501,7 +1502,7 @@
         // if keepalive ack
         //   drop
 
-        gen.addLoad8(Register.R0, IPV6_NEXT_HEADER_OFFSET);
+        gen.addLoad8(R0, IPV6_NEXT_HEADER_OFFSET);
 
         // MLD packets set the router-alert hop-by-hop option.
         // TODO: be smarter about not blindly passing every packet with HBH options.
@@ -1520,7 +1521,7 @@
 
                 // ICMPv6 but not ECHO? -> Skip the multicast filter.
                 // (ICMPv6 ECHO requests will go through the multicast filter below).
-                gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET);
+                gen.addLoad8(R0, ICMP6_TYPE_OFFSET);
                 gen.addJumpIfR0NotEquals(ICMPV6_ECHO_REQUEST_TYPE, skipIPv6MulticastFilterLabel);
             } else {
                 gen.addJumpIfR0Equals(IPPROTO_ICMPV6, skipIPv6MulticastFilterLabel);
@@ -1529,7 +1530,7 @@
             // Drop all other packets sent to ff00::/8 (multicast prefix).
             gen.defineLabel(dropAllIPv6MulticastsLabel);
             maybeSetupCounter(gen, Counter.DROPPED_IPV6_NON_ICMP_MULTICAST);
-            gen.addLoad8(Register.R0, IPV6_DEST_ADDR_OFFSET);
+            gen.addLoad8(R0, IPV6_DEST_ADDR_OFFSET);
             gen.addJumpIfR0Equals(0xff, mCountAndDropLabel);
             // If any keepalive filter matches, drop
             generateV6KeepaliveFilters(gen);
@@ -1548,7 +1549,7 @@
 
         // Add unsolicited multicast neighbor announcements filter
         String skipUnsolicitedMulticastNALabel = "skipUnsolicitedMulticastNA";
-        gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET);
+        gen.addLoad8(R0, ICMP6_TYPE_OFFSET);
         // Drop all router solicitations (b/32833400)
         maybeSetupCounter(gen, Counter.DROPPED_IPV6_ROUTER_SOLICITATION);
         gen.addJumpIfR0Equals(ICMPV6_ROUTER_SOLICITATION, mCountAndDropLabel);
@@ -1558,7 +1559,7 @@
         // This is a way to cover ff02::1 and ff02::2 with a single JNEBS.
         // TODO: Drop only if they don't contain the address of on-link neighbours.
         final byte[] unsolicitedNaDropPrefix = Arrays.copyOf(IPV6_ALL_NODES_ADDRESS, 15);
-        gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET);
+        gen.addLoadImmediate(R0, IPV6_DEST_ADDR_OFFSET);
         gen.addJumpIfBytesAtR0NotEqual(unsolicitedNaDropPrefix, skipUnsolicitedMulticastNALabel);
 
         maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA);
@@ -1615,19 +1616,19 @@
         //   3. it is a UDP packet with port 5353
 
         // Check it's L2 mDNS multicast address.
-        gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
+        gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
         gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V4_MAC_ADDRESS,
                 skipMdnsv4Filter);
 
         // Checks it's IPv4.
-        gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
+        gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET);
         gen.addJumpIfR0NotEquals(ETH_P_IP, skipMdnsFilter);
 
         // Checks it's UDP.
-        gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET);
+        gen.addLoad8(R0, IPV4_PROTOCOL_OFFSET);
         gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipMdnsFilter);
         // Set R1 to IPv4 header.
-        gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+        gen.addLoadFromMemory(R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
         gen.addJump(checkMdnsUdpPort);
 
         gen.defineLabel(skipMdnsv4Filter);
@@ -1637,28 +1638,28 @@
         gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V6_MAC_ADDRESS, skipMdnsFilter);
 
         // Checks it's IPv6.
-        gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
+        gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET);
         gen.addJumpIfR0NotEquals(ETH_P_IPV6, skipMdnsFilter);
 
         // Checks it's UDP.
-        gen.addLoad8(Register.R0, IPV6_NEXT_HEADER_OFFSET);
+        gen.addLoad8(R0, IPV6_NEXT_HEADER_OFFSET);
         gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipMdnsFilter);
 
         // Set R1 to IPv6 header.
-        gen.addLoadImmediate(Register.R1, IPV6_HEADER_LEN);
+        gen.addLoadImmediate(R1, IPV6_HEADER_LEN);
 
         // Checks it's mDNS UDP port
         gen.defineLabel(checkMdnsUdpPort);
-        gen.addLoad16Indexed(Register.R0, UDP_DESTINATION_PORT_OFFSET);
+        gen.addLoad16Indexed(R0, UDP_DESTINATION_PORT_OFFSET);
         gen.addJumpIfR0NotEquals(MDNS_PORT, skipMdnsFilter);
 
-        gen.addLoad16Indexed(Register.R0, MDNS_QDCOUNT_OFFSET);
+        gen.addLoad16Indexed(R0, MDNS_QDCOUNT_OFFSET);
         // If QDCOUNT != 1, pass the packet
         gen.addJumpIfR0NotEquals(1, mDnsAcceptPacket);
 
         // If QDCOUNT == 1, matches the QNAME with allowlist.
         // Load offset for the first QNAME.
-        gen.addLoadImmediate(Register.R0, MDNS_QNAME_OFFSET);
+        gen.addLoadImmediate(R0, MDNS_QNAME_OFFSET);
         gen.addAddR1();
 
         // Check first QNAME against allowlist
@@ -1718,9 +1719,18 @@
         if (mApfCapabilities.hasDataAccess()) {
             // Increment TOTAL_PACKETS
             maybeSetupCounter(gen, Counter.TOTAL_PACKETS);
-            gen.addLoadData(Register.R0, 0);  // load counter
+            gen.addLoadData(R0, 0);  // load counter
             gen.addAdd(1);
-            gen.addStoreData(Register.R0, 0);  // write-back counter
+            gen.addStoreData(R0, 0);  // write-back counter
+
+            maybeSetupCounter(gen, Counter.FILTER_AGE_SECONDS);
+            gen.addLoadFromMemory(R0, 15);  // m[15] is filter age in seconds
+            gen.addStoreData(R0, 0);  // store 'counter'
+
+            // requires a new enough APFv5+ interpreter, otherwise will be 0
+            maybeSetupCounter(gen, Counter.FILTER_AGE_16384THS);
+            gen.addLoadFromMemory(R0, 9);  // m[9] is filter age in 16384ths
+            gen.addStoreData(R0, 0);  // store 'counter'
         }
 
         // Here's a basic summary of what the initial program does:
@@ -1739,7 +1749,7 @@
         //   pass
         // insert IPv6 filter to drop, pass, or fall off the end for ICMPv6 packets
 
-        gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
+        gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET);
 
         if (mDrop802_3Frames) {
             // drop 802.3 frames (ethtype < 0x0600)
@@ -1761,7 +1771,7 @@
 
         // Add mDNS filter:
         generateMdnsFilterLocked(gen);
-        gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
+        gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET);
 
         // Add IPv4 filters:
         String skipIPv4FiltersLabel = "skipIPv4Filters";
@@ -1777,7 +1787,7 @@
         gen.addJumpIfR0Equals(ETH_P_IPV6, ipv6FilterLabel);
 
         // Drop non-IP non-ARP broadcasts, pass the rest
-        gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
+        gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
         maybeSetupCounter(gen, Counter.PASSED_NON_IP_UNICAST);
         gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
         maybeSetupCounter(gen, Counter.DROPPED_ETH_BROADCAST);
@@ -1809,16 +1819,16 @@
         // pointed to by R1, then jumps to the pass label. This saves a few bytes over inserting
         // the entire sequence inline for every counter.
         gen.defineLabel(mCountAndPassLabel);
-        gen.addLoadData(Register.R0, 0);   // R0 = *(R1 + 0)
+        gen.addLoadData(R0, 0);   // R0 = *(R1 + 0)
         gen.addAdd(1);                     // R0++
-        gen.addStoreData(Register.R0, 0);  // *(R1 + 0) = R0
+        gen.addStoreData(R0, 0);  // *(R1 + 0) = R0
         gen.addJump(gen.PASS_LABEL);
 
         // Same as above for the count & drop trampoline.
         gen.defineLabel(mCountAndDropLabel);
-        gen.addLoadData(Register.R0, 0);   // R0 = *(R1 + 0)
+        gen.addLoadData(R0, 0);   // R0 = *(R1 + 0)
         gen.addAdd(1);                     // R0++
-        gen.addStoreData(Register.R0, 0);  // *(R1 + 0) = R0
+        gen.addStoreData(R0, 0);  // *(R1 + 0) = R0
         gen.addJump(gen.DROP_LABEL);
     }
 
diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java
index 4e6a222..e415698 100644
--- a/src/android/net/dhcp/DhcpClient.java
+++ b/src/android/net/dhcp/DhcpClient.java
@@ -64,6 +64,7 @@
 import android.net.MacAddress;
 import android.net.NetworkStackIpMemoryStore;
 import android.net.TrafficStats;
+import android.net.ip.IIpClient;
 import android.net.ip.IpClient;
 import android.net.ipmemorystore.NetworkAttributes;
 import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener;
@@ -424,7 +425,7 @@
          * Get the configuration from RRO to check whether or not to send hostname option in
          * DHCPDISCOVER/DHCPREQUEST message.
          */
-        public boolean getSendHostnameOption(final Context context) {
+        public boolean getSendHostnameOverlaySetting(final Context context) {
             return context.getResources().getBoolean(R.bool.config_dhcp_client_hostname);
         }
 
@@ -533,11 +534,19 @@
         mRebindAlarm = makeWakeupMessage("REBIND", CMD_REBIND_DHCP);
         mExpiryAlarm = makeWakeupMessage("EXPIRY", CMD_EXPIRE_DHCP);
 
-        // Transliterate hostname read from system settings if RRO option is enabled.
-        final boolean sendHostname = deps.getSendHostnameOption(context);
-        mHostname = sendHostname ? new HostnameTransliterator().transliterate(
-                deps.getDeviceName(mContext)) : null;
-        mMetrics.setHostnameTransinfo(sendHostname, mHostname != null);
+        mHostname = new HostnameTransliterator().transliterate(deps.getDeviceName(mContext));
+        mMetrics.setHostnameTransinfo(deps.getSendHostnameOverlaySetting(context),
+                mHostname != null);
+    }
+
+    @Nullable
+    private String maybeGetHostnameForSending() {
+        boolean sendHostname = mDependencies.getSendHostnameOverlaySetting(mContext);
+        if (mConfiguration != null
+                && mConfiguration.hostnameSetting != IIpClient.HOSTNAME_SETTING_UNSET) {
+            sendHostname = mConfiguration.hostnameSetting == IIpClient.HOSTNAME_SETTING_SEND;
+        }
+        return sendHostname ? mHostname : null;
     }
 
     public void registerForPreDhcpNotification() {
@@ -557,7 +566,7 @@
      * check whether or not to support DHCP Rapid Commit option.
      */
     public boolean isDhcpRapidCommitEnabled() {
-        return mDependencies.isFeatureEnabled(mContext, DHCP_RAPID_COMMIT_VERSION);
+        return mDependencies.isFeatureNotChickenedOut(mContext, DHCP_RAPID_COMMIT_VERSION);
     }
 
     /**
@@ -758,7 +767,7 @@
         final boolean requestRapidCommit = isDhcpRapidCommitEnabled() && (getSecs() <= 4);
         final ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
                 DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr,
-                DO_UNICAST, getRequestedParams(), requestRapidCommit, mHostname,
+                DO_UNICAST, getRequestedParams(), requestRapidCommit, maybeGetHostnameForSending(),
                 mConfiguration.options);
         mMetrics.incrementCountForDiscover();
         return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST);
@@ -773,7 +782,7 @@
 
         final ByteBuffer packet = DhcpPacket.buildRequestPacket(
                 encap, mTransactionId, getSecs(), clientAddress, DO_UNICAST, mHwAddr,
-                requestedAddress, serverAddress, getRequestedParams(), mHostname,
+                requestedAddress, serverAddress, getRequestedParams(), maybeGetHostnameForSending(),
                 mConfiguration.options);
         String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null;
         String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() +
@@ -996,14 +1005,17 @@
         @NonNull
         public final List<DhcpOption> options;
         public final boolean isWifiManagedProfile;
+        public final int hostnameSetting;
 
         public Configuration(@Nullable final String l2Key, final boolean isPreconnectionEnabled,
                 @NonNull final List<DhcpOption> options,
-                final boolean isWifiManagedProfile) {
+                final boolean isWifiManagedProfile,
+                final int hostnameSetting) {
             this.l2Key = l2Key;
             this.isPreconnectionEnabled = isPreconnectionEnabled;
             this.options = options;
             this.isWifiManagedProfile = isWifiManagedProfile;
+            this.hostnameSetting = hostnameSetting;
         }
     }
 
@@ -1400,7 +1412,8 @@
             final Layer2PacketParcelable l2Packet = new Layer2PacketParcelable();
             final ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
                     DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr,
-                    DO_UNICAST, getRequestedParams(), true /* rapid commit */, mHostname,
+                    DO_UNICAST, getRequestedParams(), true /* rapid commit */,
+                    maybeGetHostnameForSending(),
                     mConfiguration.options);
 
             l2Packet.dstMacAddress = MacAddress.fromBytes(DhcpPacket.ETHER_BROADCAST);
diff --git a/src/android/net/dhcp/DhcpServer.java b/src/android/net/dhcp/DhcpServer.java
index 041417d..276fee1 100644
--- a/src/android/net/dhcp/DhcpServer.java
+++ b/src/android/net/dhcp/DhcpServer.java
@@ -195,6 +195,14 @@
          * @param name Specific experimental flag name.
          */
         boolean isFeatureEnabled(@NonNull Context context, @NonNull String name);
+
+        /**
+         * Check whether one specific experimental feature for connectivity namespace is not
+         * disabled.
+         * @param context The global context information about an app environment.
+         * @param name Specific experimental flag name.
+         */
+        boolean isFeatureNotChickenedOut(@NonNull Context context, @NonNull String name);
     }
 
     private class DependenciesImpl implements Dependencies {
@@ -234,6 +242,11 @@
         public boolean isFeatureEnabled(@NonNull Context context, @NonNull String name) {
             return DeviceConfigUtils.isNetworkStackFeatureEnabled(context, name);
         }
+
+        @Override
+        public boolean isFeatureNotChickenedOut(final Context context, final String name) {
+            return DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(context, name);
+        }
     }
 
     private static class MalformedPacketException extends Exception {
@@ -262,7 +275,8 @@
         mDeps = deps;
         mClock = deps.makeClock();
         mLeaseRepo = deps.makeLeaseRepository(mServingParams, mLog, mClock);
-        mDhcpRapidCommitEnabled = deps.isFeatureEnabled(context, DHCP_RAPID_COMMIT_VERSION);
+        mDhcpRapidCommitEnabled =
+                deps.isFeatureNotChickenedOut(context, DHCP_RAPID_COMMIT_VERSION);
 
         // CHECKSTYLE:OFF IndentationCheck
         addState(mStoppedState);
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index 56dbb6f..c1d18ae 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -51,6 +51,7 @@
 import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION;
 import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION;
 import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION;
+import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_POPULATE_LINK_ADDRESS_LIFETIME_VERSION;
 import static com.android.networkstack.util.NetworkStackUtils.createInet6AddressFromEui64;
 import static com.android.networkstack.util.NetworkStackUtils.macAddressToEui64;
 import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission;
@@ -705,6 +706,7 @@
     private final boolean mEnableIpClientIgnoreLowRaLifetime;
     private final boolean mApfShouldHandleLightDoze;
     private final boolean mEnableApfPollingCounters;
+    private final boolean mPopulateLinkAddressLifetime;
 
     private InterfaceParams mInterfaceParams;
 
@@ -936,17 +938,21 @@
         mApfCounterPollingIntervalMs = mDependencies.getDeviceConfigPropertyInt(
                 CONFIG_APF_COUNTER_POLLING_INTERVAL_SECS,
                 DEFAULT_APF_COUNTER_POLLING_INTERVAL_SECS) * DateUtils.SECOND_IN_MILLIS;
-        mUseNewApfFilter = mDependencies.isFeatureEnabled(context, APF_NEW_RA_FILTER_VERSION);
+        mUseNewApfFilter = SdkLevel.isAtLeastV() || mDependencies.isFeatureEnabled(context,
+                APF_NEW_RA_FILTER_VERSION);
         mEnableApfPollingCounters = mDependencies.isFeatureEnabled(context,
                 APF_POLLING_COUNTERS_VERSION);
-        mEnableIpClientIgnoreLowRaLifetime = mDependencies.isFeatureEnabled(context,
-                IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION);
+        mEnableIpClientIgnoreLowRaLifetime =
+                SdkLevel.isAtLeastV() || mDependencies.isFeatureEnabled(context,
+                        IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION);
         // Light doze mode status checking API is only available at T or later releases.
         mApfShouldHandleLightDoze = SdkLevel.isAtLeastT() && mDependencies.isFeatureNotChickenedOut(
                 mContext, APF_HANDLE_LIGHT_DOZE_FORCE_DISABLE);
+        mPopulateLinkAddressLifetime = mDependencies.isFeatureEnabled(context,
+                IPCLIENT_POPULATE_LINK_ADDRESS_LIFETIME_VERSION);
 
         IpClientLinkObserver.Configuration config = new IpClientLinkObserver.Configuration(
-                mMinRdnssLifetimeSec);
+                mMinRdnssLifetimeSec, mPopulateLinkAddressLifetime);
 
         mLinkObserver = new IpClientLinkObserver(
                 mContext, getHandler(),
@@ -2259,13 +2265,6 @@
                 setIpv6Sysctl(DAD_TRANSMITS, 0 /* dad_transmits */);
             }
         }
-        // Check the feature flag first before reading IPv6 sysctl, which can prevent from
-        // triggering a potential kernel bug about the sysctl.
-        // TODO: add unit test to check if the setIpv6Sysctl() is called or not.
-        if (mEnableIpClientIgnoreLowRaLifetime && mUseNewApfFilter
-                && mDependencies.hasIpv6Sysctl(mInterfaceName, ACCEPT_RA_MIN_LFT)) {
-            setIpv6Sysctl(ACCEPT_RA_MIN_LFT, mAcceptRaMinLft);
-        }
         return mInterfaceCtrl.setIPv6PrivacyExtensions(true)
                 && mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode)
                 && mInterfaceCtrl.enableIPv6();
@@ -2450,7 +2449,17 @@
         }
 
         apfConfig.minRdnssLifetimeSec = mMinRdnssLifetimeSec;
-        apfConfig.acceptRaMinLft = mAcceptRaMinLft;
+        // Check the feature flag first before reading IPv6 sysctl, which can prevent from
+        // triggering a potential kernel bug about the sysctl.
+        // TODO: add unit test to check if the setIpv6Sysctl() is called or not.
+        if (mEnableIpClientIgnoreLowRaLifetime && mUseNewApfFilter
+                && mDependencies.hasIpv6Sysctl(mInterfaceName, ACCEPT_RA_MIN_LFT)) {
+            setIpv6Sysctl(ACCEPT_RA_MIN_LFT, mAcceptRaMinLft);
+            final Integer acceptRaMinLft = getIpv6Sysctl(ACCEPT_RA_MIN_LFT);
+            apfConfig.acceptRaMinLft = acceptRaMinLft == null ? 0 : acceptRaMinLft;
+        } else {
+            apfConfig.acceptRaMinLft = 0;
+        }
         apfConfig.shouldHandleLightDoze = mApfShouldHandleLightDoze;
         apfConfig.minMetricsSessionDurationMs = mApfCounterPollingIntervalMs;
         return mDependencies.maybeCreateApfFilter(mContext, apfConfig, mInterfaceParams,
@@ -2665,7 +2674,8 @@
             isManagedWifiProfile = true;
         }
         mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP, new DhcpClient.Configuration(mL2Key,
-                isUsingPreconnection(), options, isManagedWifiProfile));
+                isUsingPreconnection(), options, isManagedWifiProfile,
+                mConfiguration.mHostnameSetting));
     }
 
     private boolean hasPermission(String permissionName) {
@@ -3258,19 +3268,25 @@
                     break;
 
                 case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
-                    final int leaseDuration = msg.arg1;
                     final LinkAddress ipAddress = (LinkAddress) msg.obj;
-                    // For IPv4 link addresses, there is no concept of preferred/valid lifetimes.
-                    // Populate the ifa_cacheinfo attribute in the netlink message with the DHCP
-                    // lease duration, which is used by the kernel to maintain the validity of the
-                    // IP addresses.
-                    if (NetlinkUtils.sendRtmNewAddressRequest(mInterfaceParams.index,
-                            ipAddress.getAddress(),
-                            (short) ipAddress.getPrefixLength(),
-                            0 /* flags */,
-                            (byte) RT_SCOPE_UNIVERSE /* scope */,
-                            leaseDuration /* preferred */,
-                            leaseDuration /* valid */)) {
+                    final boolean success;
+                    if (mPopulateLinkAddressLifetime) {
+                        // For IPv4 link addresses, there is no concept of preferred/valid
+                        // lifetimes. Populate the ifa_cacheinfo attribute in the netlink
+                        // message with the DHCP lease duration, which is used by the kernel
+                        // to maintain the validity of the IP addresses.
+                        final int leaseDuration = msg.arg1;
+                        success = NetlinkUtils.sendRtmNewAddressRequest(mInterfaceParams.index,
+                                ipAddress.getAddress(),
+                                (short) ipAddress.getPrefixLength(),
+                                0 /* flags */,
+                                (byte) RT_SCOPE_UNIVERSE /* scope */,
+                                leaseDuration /* preferred */,
+                                leaseDuration /* valid */);
+                    } else {
+                        success = mInterfaceCtrl.setIPv4Address(ipAddress);
+                    }
+                    if (success) {
                         // Although it's impossible to happen that DHCP client becomes null in
                         // RunningState and then NPE is thrown when it attempts to send a message
                         // on an null object, sometimes it's found during stress tests. If this
diff --git a/src/android/net/ip/IpClientLinkObserver.java b/src/android/net/ip/IpClientLinkObserver.java
index 22ee101..738a50b 100644
--- a/src/android/net/ip/IpClientLinkObserver.java
+++ b/src/android/net/ip/IpClientLinkObserver.java
@@ -143,9 +143,11 @@
     /** Configuration parameters for IpClientLinkObserver. */
     public static class Configuration {
         public final int minRdnssLifetime;
+        public final boolean populateLinkAddressLifetime;
 
-        public Configuration(int minRdnssLifetime) {
+        public Configuration(int minRdnssLifetime, boolean populateLinkAddressLifetime) {
             this.minRdnssLifetime = minRdnssLifetime;
+            this.populateLinkAddressLifetime = populateLinkAddressLifetime;
         }
     }
 
@@ -160,7 +162,7 @@
     private final Handler mHandler;
     private final IpClient.Dependencies mDependencies;
     private final String mClatInterfaceName;
-    private final MyNetlinkMonitor mNetlinkMonitor;
+    private final IpClientNetlinkMonitor mNetlinkMonitor;
     private final boolean mNetlinkEventParsingEnabled;
 
     private boolean mClatInterfaceExists;
@@ -194,7 +196,7 @@
         mDependencies = deps;
         mNetlinkEventParsingEnabled = deps.isFeatureNotChickenedOut(context,
                 IPCLIENT_PARSE_NETLINK_EVENTS_FORCE_DISABLE);
-        mNetlinkMonitor = new MyNetlinkMonitor(h, log, mTag);
+        mNetlinkMonitor = new IpClientNetlinkMonitor(h, log, mTag);
         mHandler.post(() -> {
             if (!mNetlinkMonitor.start()) {
                 Log.wtf(mTag, "Fail to start NetlinkMonitor.");
@@ -427,10 +429,10 @@
      * Simple NetlinkMonitor. Listen for netlink events from kernel.
      * All methods except the constructor must be called on the handler thread.
      */
-    private class MyNetlinkMonitor extends NetlinkMonitor {
+    private class IpClientNetlinkMonitor extends NetlinkMonitor {
         private final Handler mHandler;
 
-        MyNetlinkMonitor(Handler h, SharedLog log, String tag) {
+        IpClientNetlinkMonitor(Handler h, SharedLog log, String tag) {
             super(h, log, tag, OsConstants.NETLINK_ROUTE,
                     !mNetlinkEventParsingEnabled
                         ? NetlinkConstants.RTMGRP_ND_USEROPT
@@ -634,9 +636,13 @@
 
         // The preferred/valid in ifa_cacheinfo expressed in units of seconds, convert
         // it to milliseconds for deprecationTime or expirationTime used in LinkAddress.
-        private static long getDeprecationOrExpirationTime(
-                @Nullable final StructIfacacheInfo cacheInfo, long now, boolean deprecationTime) {
-            if (cacheInfo == null) return LinkAddress.LIFETIME_UNKNOWN;
+        // If the experiment flag is not enabled, LinkAddress.LIFETIME_UNKNOWN is retuend,
+        // the same as before.
+        private long getDeprecationOrExpirationTime(@Nullable final StructIfacacheInfo cacheInfo,
+                long now, boolean deprecationTime) {
+            if (!mConfig.populateLinkAddressLifetime || (cacheInfo == null)) {
+                return LinkAddress.LIFETIME_UNKNOWN;
+            }
             final long lifetime = deprecationTime ? cacheInfo.preferred : cacheInfo.valid;
             return (lifetime == Integer.toUnsignedLong(INFINITE_LEASE))
                     ? LinkAddress.LIFETIME_PERMANENT
diff --git a/src/android/net/ip/IpReachabilityMonitor.java b/src/android/net/ip/IpReachabilityMonitor.java
index d2b1064..daf9e51 100644
--- a/src/android/net/ip/IpReachabilityMonitor.java
+++ b/src/android/net/ip/IpReachabilityMonitor.java
@@ -531,7 +531,12 @@
                 isNudFailureDueToRoam(), lostProvisioning);
 
         if (lostProvisioning) {
-            final String logMsg = "FAILURE: LOST_PROVISIONING, " + event;
+            final boolean isOrganicNudFailureAndToBeIgnored =
+                    ((type == NudEventType.NUD_ORGANIC_FAILED_CRITICAL)
+                            && mIgnoreOrganicNudFailure);
+            final String logMsg = "FAILURE: LOST_PROVISIONING, " + event
+                    + ", NUD event type: " + type.name()
+                    + (isOrganicNudFailureAndToBeIgnored ? ", to be ignored" : "");
             Log.w(TAG, logMsg);
             // TODO: remove |ip| when the callback signature no longer has
             // an InetAddress argument.
@@ -539,7 +544,7 @@
             // are not from kernel organic or the NUD failure event type is
             // NUD_ORGANIC_FAILED_CRITICAL but the experiment flag is not
             // enabled. Regardless, the event metrics are still recoreded.
-            if (type != NudEventType.NUD_ORGANIC_FAILED_CRITICAL || !mIgnoreOrganicNudFailure) {
+            if (!isOrganicNudFailureAndToBeIgnored) {
                 mCallback.notifyLost(ip, logMsg, type);
             }
         }
diff --git a/src/com/android/networkstack/ipmemorystore/IpMemoryStoreService.java b/src/com/android/networkstack/ipmemorystore/IpMemoryStoreService.java
index aa7d698..76ed56c 100644
--- a/src/com/android/networkstack/ipmemorystore/IpMemoryStoreService.java
+++ b/src/com/android/networkstack/ipmemorystore/IpMemoryStoreService.java
@@ -424,6 +424,12 @@
             @Nullable final IOnStatusAndCountListener listener) {
         mExecutor.execute(() -> {
             try {
+                if (null == mDb) {
+                    if (null != listener) {
+                        listener.onComplete(makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), 0);
+                    }
+                    return;
+                }
                 final StatusAndCount res = IpMemoryStoreDatabase.delete(mDb, l2Key, needWipe);
                 if (null != listener) listener.onComplete(makeStatus(res.status), res.count);
             } catch (final RemoteException e) {
@@ -450,6 +456,12 @@
             @Nullable final IOnStatusAndCountListener listener) {
         mExecutor.execute(() -> {
             try {
+                if (null == mDb) {
+                    if (null != listener) {
+                        listener.onComplete(makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), 0);
+                    }
+                    return;
+                }
                 final StatusAndCount res =
                         IpMemoryStoreDatabase.deleteCluster(mDb, cluster, needWipe);
                 if (null != listener) listener.onComplete(makeStatus(res.status), res.count);
@@ -464,7 +476,12 @@
      */
     @Override
     public void factoryReset() {
-        mExecutor.execute(() -> IpMemoryStoreDatabase.wipeDataUponNetworkReset(mDb));
+        mExecutor.execute(() -> {
+            if (null == mDb) {
+                return;
+            }
+            IpMemoryStoreDatabase.wipeDataUponNetworkReset(mDb);
+        });
     }
 
     /** Get db size threshold. */
@@ -474,6 +491,9 @@
     }
 
     private long getDbSize() {
+        if (null == mDb) {
+            return 0;
+        }
         final File dbFile = new File(mDb.getPath());
         try {
             return dbFile.length();
diff --git a/src/com/android/networkstack/netlink/TcpSocketTracker.java b/src/com/android/networkstack/netlink/TcpSocketTracker.java
index 658fe8a..0d77dca 100644
--- a/src/com/android/networkstack/netlink/TcpSocketTracker.java
+++ b/src/com/android/networkstack/netlink/TcpSocketTracker.java
@@ -792,7 +792,9 @@
          * to deal with flag values changing at runtime.
          */
         public boolean shouldIgnoreTcpInfoForBlockedUids() {
-            return SdkLevel.isAtLeastT() && DeviceConfigUtils.isFeatureSupported(
+            // Note b/326143935 - can trigger crash due to kernel bug / missing
+            // feature on some T devices.
+            return SdkLevel.isAtLeastU() && DeviceConfigUtils.isFeatureSupported(
                     mContext, FEATURE_IS_UID_NETWORKING_BLOCKED)
                     && DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(mContext,
                     IGNORE_TCP_INFO_FOR_BLOCKED_UIDS);
diff --git a/src/com/android/networkstack/util/NetworkStackUtils.java b/src/com/android/networkstack/util/NetworkStackUtils.java
index c2de03c..0cb31fe 100755
--- a/src/com/android/networkstack/util/NetworkStackUtils.java
+++ b/src/com/android/networkstack/util/NetworkStackUtils.java
@@ -268,6 +268,14 @@
     public static final String NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION =
             "networkmonitor_async_privdns_resolution";
 
+    /**
+     * Experiment flag to populate the IP link address lifetime such as deprecationTime and
+     * expirationtTime.
+     */
+    public static final String IPCLIENT_POPULATE_LINK_ADDRESS_LIFETIME_VERSION =
+            "ipclient_populate_link_address_lifetime_version";
+
+
     /**** BEGIN Feature Kill Switch Flags ****/
 
     /**
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java
index 78a47ea..c62fb90 100755
--- a/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -2092,6 +2092,7 @@
             mThread = new Thread(() -> sendMessage(obtainMessage(CMD_PROBE_COMPLETE, token, 0,
                     isCaptivePortal(deps, httpsUrls, httpUrls, fallbackUrl))));
             mThread.start();
+            mDependencies.onThreadCreated(mThread);
         }
 
         @Override
@@ -3205,6 +3206,7 @@
                     ? new HttpsProbe(properties, proxy, url, captivePortalApiUrl)
                     : new HttpProbe(properties, proxy, url, captivePortalApiUrl);
             mResult = CaptivePortalProbeResult.failed(probeType);
+            mDependencies.onThreadCreated(this);
         }
 
         private volatile CaptivePortalProbeResult mResult;
@@ -3382,6 +3384,7 @@
         // Fixed pool to prevent configuring too many urls to exhaust system resource.
         final ExecutorService executor = Executors.newFixedThreadPool(
                 Math.min(num, MAX_PROBE_THREAD_POOL_SIZE));
+        mDependencies.onExecutorServiceCreated(executor);
         final CompletionService<CaptivePortalProbeResult> ecs =
                 new ExecutorCompletionService<CaptivePortalProbeResult>(executor);
         final Uri capportApiUrl = getCaptivePortalApiUrl(mLinkProperties);
@@ -3726,6 +3729,24 @@
             DataStallStatsUtils.write(stats, result);
         }
 
+        /**
+         * Callback to be called when a probing thread instance is created.
+         *
+         * This method is designed for overriding in test classes to collect
+         * created threads and waits for the termination.
+         */
+        public void onThreadCreated(@NonNull Thread thread) {
+        }
+
+        /**
+         * Callback to be called when a ExecutorService instance is created.
+         *
+         * This method is designed for overriding in test classes to collect
+         * created threads and waits for the termination.
+         */
+        public void onExecutorServiceCreated(@NonNull ExecutorService ecs) {
+        }
+
         public static final Dependencies DEFAULT = new Dependencies();
     }
 
diff --git a/tests/hostlib/Android.bp b/tests/hostlib/Android.bp
index 189a88c..2ebcb3e 100644
--- a/tests/hostlib/Android.bp
+++ b/tests/hostlib/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index ec058d7..65a94f3 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -68,9 +69,6 @@
     static_libs: [
         "NetworkStackApiStableLib",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // Network stack integration tests.
@@ -87,9 +85,6 @@
     jarjar_rules: ":NetworkStackJarJarRules",
     host_required: ["net-tests-utils-host-common"],
     test_config_template: "AndroidTestTemplate_Integration.xml",
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // Network stack next integration tests.
@@ -113,9 +108,6 @@
     jarjar_rules: ":NetworkStackJarJarRules",
     host_required: ["net-tests-utils-host-common"],
     test_config_template: "AndroidTestTemplate_Integration.xml",
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // Network stack integration root tests.
@@ -142,9 +134,6 @@
     jarjar_rules: ":NetworkStackJarJarRules",
     host_required: ["net-tests-utils-host-common"],
     test_config_template: "AndroidTestTemplate_Integration.xml",
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // Special version of the network stack tests that includes all tests necessary for code coverage
@@ -172,7 +161,4 @@
     compile_multilib: "both",
     manifest: "AndroidManifest_coverage.xml",
     jarjar_rules: ":NetworkStackJarJarRules",
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
diff --git a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
index 72a6b4d..06b0ca2 100644
--- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
@@ -169,6 +169,7 @@
 import android.net.shared.Layer2Information;
 import android.net.shared.ProvisioningConfiguration;
 import android.net.shared.ProvisioningConfiguration.ScanResultInfo;
+import android.net.util.HostnameTransliterator;
 import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -178,6 +179,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.provider.Settings;
 import android.stats.connectivity.NudEventType;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -306,10 +308,10 @@
     protected static final long TEST_TIMEOUT_MS = 2_000L;
     private static final long TEST_WAIT_ENOBUFS_TIMEOUT_MS = 30_000L;
     private static final long TEST_WAIT_RENEW_REBIND_RETRANSMIT_MS = 15_000L;
-    // To prevent the flakiness about deprecationTime and expirationTime check, +/- 2s tolerance
+    // To prevent the flakiness about deprecationTime and expirationTime check, +/- 4s tolerance
     // should be enough between the timestamp when the IP provisioning completes successfully and
     // when IpClientLinkObserver sees the RTM_NEWADDR netlink events.
-    private static final long TEST_LIFETIME_TOLERANCE_MS = 2_000L;
+    private static final long TEST_LIFETIME_TOLERANCE_MS = 4_000L;
 
     @Rule
     public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
@@ -598,13 +600,13 @@
                 }
 
                 @Override
-                public boolean getSendHostnameOption(final Context context) {
+                public boolean getSendHostnameOverlaySetting(final Context context) {
                     return mIsHostnameConfigurationEnabled;
                 }
 
                 @Override
                 public String getDeviceName(final Context context) {
-                    return mIsHostnameConfigurationEnabled ? mHostname : null;
+                    return mHostname;
                 }
             };
         }
@@ -746,6 +748,10 @@
         setFeatureEnabled(NetworkStackUtils.IPCLIENT_DHCPV6_PREFIX_DELEGATION_VERSION,
                 true /* isDhcp6PrefixDelegationEnabled */);
 
+        // Enable populating the IP Link Address lifetime.
+        setFeatureEnabled(NetworkStackUtils.IPCLIENT_POPULATE_LINK_ADDRESS_LIFETIME_VERSION,
+                true /* enabled */);
+
         setUpTapInterface();
         // It turns out that Router Solicitation will also be sent out even after the tap interface
         // is brought up, however, we want to wait for RS which is sent due to IPv6 stack is enabled
@@ -1255,14 +1261,14 @@
         assertIpMemoryNeverStoreNetworkAttributes(TEST_L2KEY, TEST_TIMEOUT_MS);
     }
 
-    private void assertHostname(final boolean isHostnameConfigurationEnabled,
+    private void assertHostname(final boolean expectSendHostname,
             final String hostname, final String hostnameAfterTransliteration,
             final List<DhcpPacket> packetList) throws Exception {
         for (DhcpPacket packet : packetList) {
-            if (!isHostnameConfigurationEnabled || hostname == null) {
+            if (!expectSendHostname || hostname == null) {
                 assertNoHostname(packet.getHostname());
             } else {
-                assertEquals(packet.getHostname(), hostnameAfterTransliteration);
+                assertEquals(hostnameAfterTransliteration, packet.getHostname());
             }
         }
     }
@@ -5849,4 +5855,90 @@
             }
         }
     }
+
+    private void doDhcpHostnameSettingTest(int hostnameSetting,
+            boolean isHostnameConfigurationEnabled, boolean expectSendHostname) throws Exception {
+        final ProvisioningConfiguration cfg = new ProvisioningConfiguration.Builder()
+                .withoutIPv6()
+                .withHostnameSetting(hostnameSetting)
+                .build();
+        final String expectedHostname;
+        final String expectedHostnameAfterTransliteration;
+        if (mDependencies != null) {
+            mDependencies.setHostnameConfiguration(isHostnameConfigurationEnabled,
+                    TEST_HOST_NAME);
+            expectedHostname = TEST_HOST_NAME;
+            expectedHostnameAfterTransliteration = TEST_HOST_NAME_TRANSLITERATION;
+        } else {
+            expectedHostname = Settings.Global.getString(
+                    InstrumentationRegistry.getInstrumentation().getContext().getContentResolver(),
+                    Settings.Global.DEVICE_NAME);
+            expectedHostnameAfterTransliteration = new HostnameTransliterator()
+                    .transliterate(expectedHostname);
+        }
+        startIpClientProvisioning(cfg);
+
+        // perform DHCP handshake and capture the packets sent from client such as
+        // DHCPDISCOVER and DHCPREQUEST.
+        final List<DhcpPacket> sentPackets = handleDhcpPackets(true /* isSuccessLease */,
+                DhcpPacket.INFINITE_LEASE,
+                false /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU,
+                null /* captivePortalApiUrl */, null /* ipv6OnlyWaitTime */,
+                null /* domainName */, null /* domainSearchList */);
+
+        // check if the DHCP packet sent from the client takes a hostname option per different
+        // configs. Do not consider the null hostname case.
+        assertHostname(expectSendHostname, expectedHostname, expectedHostnameAfterTransliteration,
+                sentPackets);
+    }
+
+    @Test
+    @SignatureRequiredTest(reason = "need to mock setHostnameConfiguration")
+    public void testHostname_hostnameSettingUnset_enableHostnameConfig() throws Exception {
+        // If hostname setting is unset but legacy hostname overlay config is enabled,
+        // we expect that the DHCP packet takes a hostname option.
+        doDhcpHostnameSettingTest(IIpClient.HOSTNAME_SETTING_UNSET,
+                true /* isHostnameConfigurationEnabled */, true /* expectSendHostname */);
+    }
+
+    @Test
+    @SignatureRequiredTest(reason = "need to mock setHostnameConfiguration")
+    public void testHostname_hostnameSettingUnset_disableHostnameConfig() throws Exception {
+        // If hostname setting is unset and legacy hostname overlay config is disabled,
+        // we expect that the DHCP packet doesn't take a hostname option.
+        doDhcpHostnameSettingTest(IIpClient.HOSTNAME_SETTING_UNSET,
+                false /* isHostnameConfigurationEnabled */, false /* expectSendHostname */);
+    }
+
+    @Test
+    public void testHostname_hostnameSettingSend_enableHostnameConfig() throws Exception {
+        // If hostname setting is set and legacy hostname overlay config is enabled,
+        // we expect that the DHCP packet takes a hostname option.
+        doDhcpHostnameSettingTest(IIpClient.HOSTNAME_SETTING_SEND,
+                true /* isHostnameConfigurationEnabled */, true /* expectSendHostname */);
+    }
+
+    @Test
+    public void testHostname_hostnameSettingSend_disableHostnameConfig() throws Exception {
+        // If hostname setting is set and legacy hostname overlay config is disabled,
+        // we still expect that the DHCP packet takes a hostname option.
+        doDhcpHostnameSettingTest(IIpClient.HOSTNAME_SETTING_SEND,
+                false /* isHostnameConfigurationEnabled */, true /* expectSendHostname */);
+    }
+
+    @Test
+    public void testHostname_hostnameSettingNotSend_enableHostnameConfig() throws Exception {
+        // If hostname setting is not send and even if legacy hostname overlay config is
+        // enabled, we expect that the DHCP packet doesn't take a hostname option.
+        doDhcpHostnameSettingTest(IIpClient.HOSTNAME_SETTING_DO_NOT_SEND,
+                true /* isHostnameConfigurationEnabled */, false /* expectSendHostname */);
+    }
+
+    @Test
+    public void testHostname_hostnameSettingNotSend_disableHostnameConfig() throws Exception {
+        // If hostname setting is not send and even if legacy hostname overlay config is
+        // disabled, we expect that the DHCP packet doesn't take a hostname option.
+        doDhcpHostnameSettingTest(IIpClient.HOSTNAME_SETTING_DO_NOT_SEND,
+                false /* isHostnameConfigurationEnabled */, false /* expectSendHostname */);
+    }
 }
diff --git a/tests/integration/lint-baseline.xml b/tests/integration/lint-baseline.xml
deleted file mode 100644
index edc9dac..0000000
--- a/tests/integration/lint-baseline.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.LinkProperties#getDhcpServerAddress`">
-        <location
-            file="packages/modules/NetworkStack/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java"
-            line="1574"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.LinkProperties#getNat64Prefix`">
-        <location
-            file="packages/modules/NetworkStack/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java"
-            line="2014"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.LinkProperties#getNat64Prefix`">
-        <location
-            file="packages/modules/NetworkStack/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java"
-            line="2020"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.LinkProperties#getNat64Prefix`">
-        <location
-            file="packages/modules/NetworkStack/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java"
-            line="2050"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.LinkProperties#getNat64Prefix`">
-        <location
-            file="packages/modules/NetworkStack/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java"
-            line="2051"/>
-    </issue>
-
-</issues>
\ No newline at end of file
diff --git a/tests/integration/signature/android/net/NetworkStatsIntegrationTest.kt b/tests/integration/signature/android/net/NetworkStatsIntegrationTest.kt
index 2e52e3f..6c56add 100644
--- a/tests/integration/signature/android/net/NetworkStatsIntegrationTest.kt
+++ b/tests/integration/signature/android/net/NetworkStatsIntegrationTest.kt
@@ -28,15 +28,12 @@
 import android.net.NetworkStatsIntegrationTest.Direction.UPLOAD
 import android.net.NetworkTemplate.MATCH_TEST
 import android.os.Build
-import android.os.ParcelFileDescriptor.AutoCloseInputStream
 import android.os.Process
-import android.util.Log
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.DevSdkIgnoreRunner
 import com.android.testutils.PacketBridge
 import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
-import com.android.testutils.SkipPresubmit
 import com.android.testutils.TestDnsServer
 import com.android.testutils.TestHttpServer
 import com.android.testutils.TestableNetworkCallback
@@ -44,7 +41,6 @@
 import fi.iki.elonen.NanoHTTPD
 import java.io.BufferedInputStream
 import java.io.BufferedOutputStream
-import java.io.BufferedReader
 import java.net.HttpURLConnection
 import java.net.HttpURLConnection.HTTP_OK
 import java.net.InetSocketAddress
@@ -52,7 +48,6 @@
 import java.nio.charset.Charset
 import kotlin.math.ceil
 import kotlin.test.assertEquals
-import kotlin.test.assertNotNull
 import kotlin.test.assertTrue
 import org.junit.After
 import org.junit.Assume.assumeTrue
@@ -82,7 +77,9 @@
     private val TEST_DOWNLOAD_SIZE = 10000L
     private val TEST_UPLOAD_SIZE = 20000L
     private val HTTP_SERVER_NAME = "test.com"
-    private val DNS_SERVER_PORT = 53
+    private val HTTP_SERVER_PORT = 8080 // Use port > 1024 to avoid restrictions on system ports
+    private val DNS_INTERNAL_SERVER_PORT = 53
+    private val DNS_EXTERNAL_SERVER_PORT = 1053
     private val TCP_ACK_SIZE = 72
 
     // Packet overheads that are not part of the actual data transmission, these
@@ -99,13 +96,21 @@
     private val inst = InstrumentationRegistry.getInstrumentation()
     private val context = inst.getContext()
     private val packetBridge = runAsShell(MANAGE_TEST_NETWORKS) {
-        PacketBridge(context, listOf(LOCAL_V6ADDR), REMOTE_V6ADDR.address)
+        PacketBridge(
+            context,
+            listOf(LOCAL_V6ADDR),
+            REMOTE_V6ADDR.address,
+            listOf(
+                Pair(DNS_INTERNAL_SERVER_PORT, DNS_EXTERNAL_SERVER_PORT)
+            )
+        )
     }
     private val cm = context.getSystemService(ConnectivityManager::class.java)!!
 
     // Set up DNS server for testing server and DNS64.
     private val fakeDns = TestDnsServer(
-        packetBridge.externalNetwork, InetSocketAddress(LOCAL_V6ADDR.address, DNS_SERVER_PORT)
+        packetBridge.externalNetwork,
+        InetSocketAddress(LOCAL_V6ADDR.address, DNS_EXTERNAL_SERVER_PORT)
     ).apply {
         start()
         setAnswer(
@@ -116,7 +121,10 @@
     }
 
     // Start up test http server.
-    private val httpServer = TestHttpServer(LOCAL_V6ADDR.address.hostAddress).apply {
+    private val httpServer = TestHttpServer(
+        LOCAL_V6ADDR.address.hostAddress,
+        HTTP_SERVER_PORT
+    ).apply {
         start()
     }
 
@@ -182,7 +190,6 @@
      * While the packets are being forwarded to the external interface, the servers will see
      * the packets originated from the mocked v6 address, and destined to a local v6 address.
      */
-    @SkipPresubmit(reason = "Out of SLO flakiness")
     @Test
     fun test464XlatTcpStats() {
         // Wait for 464Xlat to be ready.
@@ -384,7 +391,6 @@
         val taggedUid = getUidDetail(iface, TEST_TAG)
         val trafficStatsIface = getTrafficStatsIface(iface)
         val trafficStatsUid = getTrafficStatsUid(Process.myUid())
-        val xtBpfStats = getXtBpfStatsInternal()
 
         private fun getUidDetail(iface: String, tag: Int): BareStats {
             return getNetworkStatsThat(iface, tag) { nsm, template ->
@@ -451,40 +457,6 @@
             TrafficStats.getUidTxBytes(uid),
             TrafficStats.getUidTxPackets(uid)
         )
-
-        private fun getXtBpfStatsInternal(): BareStats {
-            // The following pattern matches ip(6)tables-save -c output like below:
-            // [119:37802] -A bw_raw_PREROUTING -m bpf --object-pinned
-            //      /sys/fs/bpf/netd_shared/prog_netd_skfilter_ingress_xtbpf
-            // [141:26439] -A bw_mangle_POSTROUTING -m bpf --object-pinned
-            //      /sys/fs/bpf/netd_shared/prog_netd_skfilter_egress_xtbpf
-            val ingressRegex = Regex("""\[(?<rxPackets>\d+):(?<rxBytes>\d+)\]""" +
-                    """.*prog_netd_skfilter_ingress_xtbpf""")
-            val egressRegex = Regex("""\[(?<txPackets>\d+):(?<txBytes>\d+)\]""" +
-                    """.*prog_netd_skfilter_egress_xtbpf""")
-            val (v4Stats, v6Stats) = listOf("iptables-save -c", "ip6tables-save -c").map {
-                val output = runShellCommand(it)
-                val rxMatches = ingressRegex.find(output)
-                val txMatches = egressRegex.find(output)
-                assertNotNull(rxMatches)
-                assertNotNull(txMatches)
-
-                BareStats(
-                        rxBytes = rxMatches.groups["rxBytes"]!!.value.toLong(),
-                        rxPackets = rxMatches.groups["rxPackets"]!!.value.toLong(),
-                        txBytes = txMatches.groups["txBytes"]!!.value.toLong(),
-                        txPackets = txMatches.groups["txPackets"]!!.value.toLong()
-                )
-            }
-            return v4Stats.plus(v6Stats)
-        }
-
-        private fun runShellCommand(cmd: String): String {
-            return InstrumentationRegistry.getInstrumentation().getUiAutomation()
-                    .executeShellCommand(cmd).use { pfd ->
-                        AutoCloseInputStream(pfd).bufferedReader().use(BufferedReader::readText)
-                    }
-        }
     }
 
     private fun assertAllStatsIncreases(
@@ -513,15 +485,6 @@
         lower: BareStats,
         upper: BareStats
     ) {
-        // XtBpf iptables hook counted traffic on all interfaces. Thus, this might see traffic
-        // on other interfaces as well. Also, other thread/process could reload the relevant
-        // iptables table. Thus, instead of asserting the readings, print logs when it is
-        // unexpected to provide more debug information when failing other items.
-        if (!checkInRange(before.xtBpfStats, after.xtBpfStats,
-                        lower + lower.reverse(), upper + upper.reverse())) {
-            Log.d(TAG, "Unexpected xtbpf stats: ${after.xtBpfStats} - ${before.xtBpfStats} " +
-                    "is not within range [$lower, $upper]")
-        }
         assertInRange(
             "Unexpected iface traffic stats",
             after.iface,
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index ea29714..88d995f 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -69,9 +70,6 @@
     static_libs: ["NetworkStackApiCurrentLib"],
     compile_multilib: "both", // Workaround for b/147785146 for mainline-presubmit
     jarjar_rules: ":NetworkStackJarJarRules",
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // Library containing the unit tests. This is used by the coverage test target to pull in the
@@ -84,7 +82,7 @@
     static_libs: ["NetworkStackApiStableLib"],
     lint: {
         test: true,
-        baseline_filename: "lint-baseline.xml",
+
     },
     visibility: [
         "//packages/modules/NetworkStack/tests/integration",
@@ -107,9 +105,6 @@
     static_libs: ["NetworkStackApiStableLib"],
     compile_multilib: "both",
     jarjar_rules: ":NetworkStackJarJarRules",
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // Additional dependencies of libnetworkstackutilsjni that are not provided by the system when
diff --git a/tests/unit/jni/Android.bp b/tests/unit/jni/Android.bp
index 50576cc..428dacc 100644
--- a/tests/unit/jni/Android.bp
+++ b/tests/unit/jni/Android.bp
@@ -15,13 +15,14 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 cc_library_shared {
     name: "libnetworkstacktestsjni",
     srcs: [
-        "**/*.cpp"
+        "**/*.cpp",
     ],
     cflags: [
         "-Wall",
diff --git a/tests/unit/jni/apf_jni.cpp b/tests/unit/jni/apf_jni.cpp
index 39dd2c2..1889a89 100644
--- a/tests/unit/jni/apf_jni.cpp
+++ b/tests/unit/jni/apf_jni.cpp
@@ -32,12 +32,12 @@
 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
 #define LOG_TAG "NetworkStackUtils-JNI"
 
-static int run_apf_interpreter(int apf_version, uint8_t* program,
+static int run_apf_interpreter(int apf_version, uint32_t* program,
                                uint32_t program_len, uint32_t ram_len,
                                const uint8_t* packet, uint32_t packet_len,
                                uint32_t filter_age) {
   if (apf_version == 4) {
-    return accept_packet(program, program_len, ram_len, packet, packet_len,
+    return accept_packet((uint8_t*)program, program_len, ram_len, packet, packet_len,
                          filter_age);
   } else {
     return apf_run(nullptr, program, program_len, ram_len, packet, packet_len,
@@ -55,23 +55,30 @@
     uint32_t packet_len = (uint32_t)packet.size();
     uint32_t program_len = env->GetArrayLength(jprogram);
     uint32_t data_len = jdata ? env->GetArrayLength(jdata) : 0;
-    std::vector<uint8_t> buf(program_len + data_len, 0);
+    // we need to guarantee room for APFv6's 5 u32 counters (20 bytes)
+    // and we need to make sure ram_len is a multiple of 4 bytes,
+    // so that the counters (which are indexed from the back are aligned.
+    uint32_t ram_len = program_len + data_len;
+    if (apf_version > 4) {
+        ram_len += 3; ram_len &= ~3;
+        if (data_len < 20) ram_len += 20;
+    }
+    std::vector<uint32_t> buf((ram_len + 3) / 4, 0);
+    jbyte* jbuf = reinterpret_cast<jbyte*>(buf.data());
 
-    env->GetByteArrayRegion(jprogram, 0, program_len, reinterpret_cast<jbyte*>(buf.data()));
+    env->GetByteArrayRegion(jprogram, 0, program_len, jbuf);
     if (jdata) {
         // Merge program and data into a single buffer.
-        env->GetByteArrayRegion(jdata, 0, data_len,
-                                reinterpret_cast<jbyte*>(buf.data() + program_len));
+        env->GetByteArrayRegion(jdata, 0, data_len, jbuf + ram_len - data_len);
     }
 
     jint result = run_apf_interpreter(
-        apf_version, buf.data(), program_len, program_len + data_len,
+        apf_version, buf.data(), program_len, ram_len,
         reinterpret_cast<const uint8_t *>(packet.get()), packet_len,
         filter_age);
 
     if (jdata) {
-        env->SetByteArrayRegion(jdata, 0, data_len,
-                                reinterpret_cast<jbyte*>(buf.data() + program_len));
+        env->SetByteArrayRegion(jdata, 0, data_len, jbuf + ram_len - data_len);
     }
 
     return result;
@@ -141,7 +148,13 @@
     jstring jpcap_filename, jbyteArray japf_program) {
     ScopedUtfChars filter(env, jfilter);
     ScopedUtfChars pcap_filename(env, jpcap_filename);
-    ScopedByteArrayRO apf_program(env, japf_program);
+    uint32_t program_len = env->GetArrayLength(japf_program);
+    uint32_t data_len = (apf_version > 4) ? 20 : 0;
+    uint32_t ram_len = program_len + data_len;
+    if (apf_version > 4) { ram_len += 3; ram_len &= ~3; }
+    std::vector<uint32_t> apf_program((ram_len + 3) / 4, 0);
+    env->GetByteArrayRegion(japf_program, 0, program_len,
+                            reinterpret_cast<jbyte*>(apf_program.data()));
 
     // Open pcap file for BPF filtering
     ScopedFILE bpf_fp(fopen(pcap_filename.c_str(), "rb"));
@@ -183,8 +196,7 @@
         do {
             apf_packet = pcap_next(apf_pcap.get(), &apf_header);
         } while (apf_packet != NULL && !run_apf_interpreter(apf_version,
-                reinterpret_cast<uint8_t*>(const_cast<int8_t*>(apf_program.get())),
-                apf_program.size(), 0 /* data_len */,
+                apf_program.data(), program_len, ram_len,
                 apf_packet, apf_header.len, 0 /* filter_age */));
 
         // Make sure both filters matched the same packet.
@@ -208,15 +220,20 @@
     ScopedByteArrayRO apf_program(env, jprogram);
     uint32_t apf_program_len = (uint32_t)apf_program.size();
     uint32_t data_len = env->GetArrayLength(jdata);
+    uint32_t ram_len = apf_program_len + data_len;
+    if (apf_version > 4) {
+        ram_len += 3; ram_len &= ~3;
+        if (data_len < 20) ram_len += 20;
+    }
     pcap_pkthdr apf_header;
     const uint8_t* apf_packet;
     char pcap_error[PCAP_ERRBUF_SIZE];
-    std::vector<uint8_t> buf(apf_program_len + data_len, 0);
+    std::vector<uint32_t> buf((ram_len + 3) / 4, 0);
+    jbyte* jbuf = reinterpret_cast<jbyte*>(buf.data());
 
     // Merge program and data into a single buffer.
-    env->GetByteArrayRegion(jprogram, 0, apf_program_len, reinterpret_cast<jbyte*>(buf.data()));
-    env->GetByteArrayRegion(jdata, 0, data_len,
-                            reinterpret_cast<jbyte*>(buf.data() + apf_program_len));
+    env->GetByteArrayRegion(jprogram, 0, apf_program_len, jbuf);
+    env->GetByteArrayRegion(jdata, 0, data_len, jbuf + ram_len - data_len);
 
     // Open pcap file
     ScopedFILE apf_fp(fopen(pcap_filename.c_str(), "rb"));
@@ -229,19 +246,16 @@
 
     while ((apf_packet = pcap_next(apf_pcap.get(), &apf_header)) != NULL) {
         int result = run_apf_interpreter(
-            apf_version, buf.data(), apf_program_len,
-            apf_program_len + data_len, apf_packet, apf_header.len, 0);
+            apf_version, buf.data(), apf_program_len, ram_len, apf_packet, apf_header.len, 0);
 
         // Return false once packet passes the filter
         if (result) {
-            env->SetByteArrayRegion(jdata, 0, data_len,
-                                    reinterpret_cast<jbyte*>(buf.data() + apf_program_len));
+            env->SetByteArrayRegion(jdata, 0, data_len, jbuf + ram_len - data_len);
             return false;
          }
     }
 
-    env->SetByteArrayRegion(jdata, 0, data_len,
-                            reinterpret_cast<jbyte*>(buf.data() + apf_program_len));
+    env->SetByteArrayRegion(jdata, 0, data_len, jbuf + ram_len - data_len);
     return true;
 }
 
diff --git a/tests/unit/lint-baseline.xml b/tests/unit/lint-baseline.xml
deleted file mode 100644
index f8b1c29..0000000
--- a/tests/unit/lint-baseline.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.NetworkCapabilities()`">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt"
-            line="136"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `android.net.NetworkCapabilities()`">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt"
-            line="137"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `new android.net.NetworkCapabilities`">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java"
-            line="57"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `new android.net.NetworkCapabilities`">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java"
-            line="109"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `new android.net.NetworkCapabilities`">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java"
-            line="117"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `new android.net.NetworkCapabilities`">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java"
-            line="123"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 30 (current min is 29): `new android.net.NetworkCapabilities`">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java"
-            line="129"/>
-    </issue>
-
-</issues>
\ No newline at end of file
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java
index 5c6a906..29a5045 100644
--- a/tests/unit/src/android/net/apf/ApfTest.java
+++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -16,11 +16,11 @@
 
 package android.net.apf;
 
-import static android.net.apf.ApfV4Generator.APF_VERSION_4;
-import static android.net.apf.ApfV4Generator.DROP_LABEL;
-import static android.net.apf.ApfV4Generator.PASS_LABEL;
-import static android.net.apf.ApfV4Generator.Register.R0;
-import static android.net.apf.ApfV4Generator.Register.R1;
+import static android.net.apf.BaseApfGenerator.APF_VERSION_4;
+import static android.net.apf.BaseApfGenerator.DROP_LABEL;
+import static android.net.apf.BaseApfGenerator.PASS_LABEL;
+import static android.net.apf.BaseApfGenerator.Register.R0;
+import static android.net.apf.BaseApfGenerator.Register.R1;
 import static android.net.apf.ApfJniUtils.compareBpfApf;
 import static android.net.apf.ApfJniUtils.compileToBpf;
 import static android.net.apf.ApfJniUtils.dropsAllPackets;
@@ -70,7 +70,7 @@
 import android.net.apf.ApfTestUtils.MockIpClientCallback;
 import android.net.apf.ApfTestUtils.TestApfFilter;
 import android.net.apf.ApfTestUtils.TestLegacyApfFilter;
-import android.net.apf.ApfV4Generator.IllegalInstructionException;
+import android.net.apf.BaseApfGenerator.IllegalInstructionException;
 import android.net.metrics.IpConnectivityLog;
 import android.os.Build;
 import android.os.PowerManager;
@@ -263,6 +263,12 @@
         ApfV4Generator gen = new ApfV4Generator(MIN_APF_VERSION);
         assertPass(gen);
 
+        // Test pass opcode
+        gen = new ApfV4Generator(MIN_APF_VERSION);
+        gen.addPass();
+        gen.addJump(DROP_LABEL);
+        assertPass(gen);
+
         // Test jumping to pass label.
         gen = new ApfV4Generator(MIN_APF_VERSION);
         gen.addJump(PASS_LABEL);
@@ -871,9 +877,16 @@
         assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
 
         // Same program as before, but this time we're trying to load past the end of the data.
+        // 3 instructions, all normal opcodes (LI, LDDW, JMP) with 1 byte immediate = 6 byte program
+        // 32 byte data length, for a total of 38 byte ram len.
+        // APFv6 needs to round this up to be a multiple of 4, so 40.
         gen = new ApfV4Generator(APF_VERSION_4);
         gen.addLoadImmediate(R0, 20);
-        gen.addLoadData(R1, 15);  // 20 + 15 > 32
+        if (mApfVersion == 4) {
+            gen.addLoadData(R1, 15);  // R0(20)+15+U32[0..3] >= 6 prog + 32 data, so invalid
+        } else {
+            gen.addLoadData(R1, 17);  // R0(20)+17+U32[0..3] >= 6 prog + 2 pad + 32 data, so invalid
+        }
         gen.addJump(DROP_LABEL);  // Not reached.
         assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
 
@@ -2795,6 +2808,70 @@
         apfFilter.shutdown();
     }
 
+    @Test
+    public void testProcessRaWithInfiniteLifeTimeWithoutCrash() throws Exception {
+        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+        // configure accept_ra_min_lft
+        final ApfConfiguration config = getDefaultConfig();
+        config.acceptRaMinLft = 180;
+        TestApfFilter apfFilter;
+        // Template packet:
+        // Frame 1: 150 bytes on wire (1200 bits), 150 bytes captured (1200 bits)
+        // Ethernet II, Src: Netgear_23:67:2c (28:c6:8e:23:67:2c), Dst: IPv6mcast_01 (33:33:00:00:00:01)
+        // Internet Protocol Version 6, Src: fe80::2ac6:8eff:fe23:672c, Dst: ff02::1
+        // Internet Control Message Protocol v6
+        //   Type: Router Advertisement (134)
+        //   Code: 0
+        //   Checksum: 0x0acd [correct]
+        //   Checksum Status: Good
+        //   Cur hop limit: 64
+        //   Flags: 0xc0, Managed address configuration, Other configuration, Prf (Default Router Preference): Medium
+        //   Router lifetime (s): 7000
+        //   Reachable time (ms): 0
+        //   Retrans timer (ms): 0
+        //   ICMPv6 Option (Source link-layer address : 28:c6:8e:23:67:2c)
+        //     Type: Source link-layer address (1)
+        //     Length: 1 (8 bytes)
+        //     Link-layer address: Netgear_23:67:2c (28:c6:8e:23:67:2c)
+        //     Source Link-layer address: Netgear_23:67:2c (28:c6:8e:23:67:2c)
+        //   ICMPv6 Option (MTU : 1500)
+        //     Type: MTU (5)
+        //     Length: 1 (8 bytes)
+        //     Reserved
+        //     MTU: 1500
+        //   ICMPv6 Option (Prefix information : 2401:fa00:480:f000::/64)
+        //     Type: Prefix information (3)
+        //     Length: 4 (32 bytes)
+        //     Prefix Length: 64
+        //     Flag: 0xc0, On-link flag(L), Autonomous address-configuration flag(A)
+        //     Valid Lifetime: Infinity (4294967295)
+        //     Preferred Lifetime: Infinity (4294967295)
+        //     Reserved
+        //     Prefix: 2401:fa00:480:f000::
+        //   ICMPv6 Option (Recursive DNS Server 2401:fa00:480:f000::1)
+        //     Type: Recursive DNS Server (25)
+        //     Length: 3 (24 bytes)
+        //     Reserved
+        //     Lifetime: 7000
+        //     Recursive DNS Servers: 2401:fa00:480:f000::1
+        //   ICMPv6 Option (Advertisement Interval : 600000)
+        //     Type: Advertisement Interval (7)
+        //     Length: 1 (8 bytes)
+        //     Reserved
+        //     Advertisement Interval: 600000
+        final String packetStringFmt = "33330000000128C68E23672C86DD60054C6B00603AFFFE800000000000002AC68EFFFE23672CFF02000000000000000000000000000186000ACD40C01B580000000000000000010128C68E23672C05010000000005DC030440C0%s000000002401FA000480F00000000000000000001903000000001B582401FA000480F000000000000000000107010000000927C0";
+        final List<String> lifetimes = List.of("FFFFFFFF", "00000000", "00000001", "00001B58");
+        for (String lifetime : lifetimes) {
+            apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics);
+            final byte[] ra = hexStringToByteArray(
+                    String.format(packetStringFmt, lifetime + lifetime));
+            // feed the RA into APF and generate the filter, the filter shouldn't crash.
+            apfFilter.pretendPacketReceived(ra);
+            ipClientCallback.assertProgramUpdateAndGet();
+            apfFilter.shutdown();
+        }
+    }
+
     // Test for go/apf-ra-filter Case 1a.
     // Old lifetime is 0
     @Test
@@ -3821,4 +3898,183 @@
         final String referenceProgramHexString = "6bfcb03a01b8120c6b9494014a06006b907c014388a27c013e88a47c013988b87c013488cd7c012f88e17c012a88e384003f08066a0e6bdca40110000600010800060412147a1c016bd884010400021a1c6b8c7c01010000686bd4a2ef06ffffffffffff6a266bbca2ea04c0a801be6bf872e0120c84008d08000a17821e1112149c00171fffab0d2a108210446a3239a20406ea42226789c06bf472b60a1e52f06bac7ab3e06bb41a1e7e000000a6ffffffff6bb07e0000009bc0a801ff0a178230116a1aa223086b7a1f1fc0a801beaa0d3a08aa221210ab2139821501aa0d3a0ea20a041194ceca3a08a20401ff6b8072666be868a25406ffffffffffff6bb872566bf0724c7c001086dd686bd0a23b06ffffffffffff6bc8723d0a147a32007a0b3a6b980a267a2eff6be072240a366ba87a23858218886a26a2040fff02000000000000000000000000006ba472086be4b03a01b87206b03a01b87201";
         assertEquals(referenceProgramHexString, programString);
     }
+
+    @Test
+    public void testInfiniteLifetimeFullApfV4ProgramGeneration() throws IllegalInstructionException {
+        ApfV4Generator gen = new ApfV4Generator(APF_VERSION_4);
+        gen.addLoadImmediate(R1, -8);
+        gen.addLoadData(R0, 0);
+        gen.addAdd(1);
+        gen.addStoreData(R0, 0);
+        gen.addLoad16(R0, 12);
+        gen.addLoadImmediate(R1, -120);
+        gen.addJumpIfR0LessThan(0x600, "LABEL_582");
+        gen.addLoadImmediate(R1, -124);
+        gen.addJumpIfR0Equals(0x88a2, "LABEL_582");
+        gen.addJumpIfR0Equals(0x88a4, "LABEL_582");
+        gen.addJumpIfR0Equals(0x88b8, "LABEL_582");
+        gen.addJumpIfR0Equals(0x88cd, "LABEL_582");
+        gen.addJumpIfR0Equals(0x88e1, "LABEL_582");
+        gen.addJumpIfR0Equals(0x88e3, "LABEL_582");
+        gen.addJumpIfR0NotEquals(0x806, "LABEL_122");
+        gen.addLoadImmediate(R0, 14);
+        gen.addLoadImmediate(R1, -152);
+        gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("000108000604"), "LABEL_582");
+        gen.addLoad16(R0, 20);
+        gen.addJumpIfR0Equals(0x1, "LABEL_104");
+        gen.addLoadImmediate(R1, -156);
+        gen.addJumpIfR0NotEquals(0x2, "LABEL_582");
+        gen.addLoad32(R0, 28);
+        gen.addLoadImmediate(R1, -128);
+        gen.addJumpIfR0Equals(0x0, "LABEL_582");
+        gen.addLoadImmediate(R0, 0);
+        gen.addLoadImmediate(R1, -56);
+        gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_576");
+
+        gen.defineLabel("LABEL_104");
+        gen.addLoadImmediate(R0, 38);
+        gen.addLoadImmediate(R1, -80);
+        gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("c0a801ec"), "LABEL_582");
+        gen.addLoadImmediate(R1, -20);
+        gen.addJump("LABEL_576");
+
+        gen.defineLabel("LABEL_122");
+        gen.addLoad16(R0, 12);
+        gen.addJumpIfR0NotEquals(0x800, "LABEL_249");
+        gen.addLoad8(R0, 23);
+        gen.addJumpIfR0NotEquals(0x11, "LABEL_165");
+        gen.addLoad16(R0, 20);
+        gen.addJumpIfR0AnyBitsSet(0x1fff, "LABEL_165");
+        gen.addLoadFromMemory(R1, 13);
+        gen.addLoad16Indexed(R0, 16);
+        gen.addJumpIfR0NotEquals(0x44, "LABEL_165");
+        gen.addLoadImmediate(R0, 50);
+        gen.addAddR1();
+        gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("7e9046bc7008"), "LABEL_165");
+        gen.addLoadImmediate(R1, -24);
+        gen.addJump("LABEL_576");
+
+        gen.defineLabel("LABEL_165");
+        gen.addLoad8(R0, 30);
+        gen.addAnd(240);
+        gen.addLoadImmediate(R1, -96);
+        gen.addJumpIfR0Equals(0xe0, "LABEL_582");
+        gen.addLoadImmediate(R1, -88);
+        gen.addLoad32(R0, 30);
+        gen.addJumpIfR0Equals(0xffffffff, "LABEL_582");
+        gen.addLoadImmediate(R1, -92);
+        gen.addJumpIfR0Equals(0xc0a801ff, "LABEL_582");
+        gen.addLoad8(R0, 23);
+        gen.addJumpIfR0NotEquals(0x6, "LABEL_225");
+        gen.addLoad16(R0, 20);
+        gen.addJumpIfR0AnyBitsSet(0x1fff, "LABEL_225");
+        gen.addLoadFromMemory(R1, 13);
+        gen.addLoad16Indexed(R0, 16);
+        gen.addJumpIfR0NotEquals(0x7, "LABEL_225");
+        gen.addLoadImmediate(R1, -148);
+        gen.addJump("LABEL_582");
+
+        gen.defineLabel("LABEL_225");
+        gen.addLoadImmediate(R1, -36);
+        gen.addLoadImmediate(R0, 0);
+        gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_576");
+        gen.addLoadImmediate(R1, -84);
+        gen.addJump("LABEL_582");
+        gen.addLoadImmediate(R1, -28);
+        gen.addJump("LABEL_576");
+
+        gen.defineLabel("LABEL_249");
+        gen.addJumpIfR0Equals(0x86dd, "LABEL_273");
+        gen.addLoadImmediate(R0, 0);
+        gen.addLoadImmediate(R1, -60);
+        gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_576");
+        gen.addLoadImmediate(R1, -68);
+        gen.addJump("LABEL_582");
+
+        gen.defineLabel("LABEL_273");
+        gen.addLoad8(R0, 20);
+        gen.addJumpIfR0Equals(0x0, "LABEL_576");
+        gen.addJumpIfR0Equals(0x3a, "LABEL_297");
+        gen.addLoadImmediate(R1, -116);
+        gen.addLoad8(R0, 38);
+        gen.addJumpIfR0Equals(0xff, "LABEL_582");
+        gen.addLoadImmediate(R1, -44);
+        gen.addJump("LABEL_576");
+
+        gen.defineLabel("LABEL_297");
+        gen.addLoad8(R0, 54);
+        gen.addLoadImmediate(R1, -100);
+        gen.addJumpIfR0Equals(0x85, "LABEL_582");
+        gen.addJumpIfR0NotEquals(0x88, "LABEL_333");
+        gen.addLoadImmediate(R0, 38);
+        gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff0200000000000000000000000000"), "LABEL_333");
+        gen.addLoadImmediate(R1, -104);
+        gen.addJump("LABEL_582");
+
+        gen.defineLabel("LABEL_333");
+        gen.addLoadFromMemory(R0, 14);
+        gen.addJumpIfR0NotEquals(0x96, "LABEL_574");
+        gen.addLoadFromMemory(R0, 15);
+        gen.addJumpIfR0GreaterThan(0x48e, "LABEL_574");
+        gen.addLoadImmediate(R0, 0);
+        gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("7e9046bc700828c68e23672c86dd60"), "LABEL_574");
+        gen.addLoadImmediate(R0, 18);
+        gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("00603afffe800000000000002ac68efffe23672c"), "LABEL_574");
+        gen.addLoadImmediate(R0, 54);
+        gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("8600"), "LABEL_574");
+        gen.addLoadImmediate(R0, 58);
+        gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("40c0"), "LABEL_574");
+        gen.addLoad16(R0, 60);
+        gen.addJumpIfR0Equals(0x0, "LABEL_574");
+        gen.addJumpIfR0LessThan(0xb4, "LABEL_421");
+        gen.addJumpIfR0LessThan(0x91e, "LABEL_574");
+        gen.addJumpIfR0GreaterThan(0x1b58, "LABEL_574");
+
+        gen.defineLabel("LABEL_421");
+        gen.addLoadImmediate(R0, 62);
+        gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("0000000000000000010128c68e23672c05010000000005dc030440c0"), "LABEL_574");
+        gen.addLoad32(R0, 90);
+        gen.addJumpIfR0Equals(0x0, "LABEL_574");
+        gen.addJumpIfR0LessThan(0xb4, "LABEL_480");
+        gen.addJumpIfR0LessThan(0x55555555, "LABEL_574");
+        gen.addJumpIfR0GreaterThan(0xffffffffL, "LABEL_574");
+
+        gen.defineLabel("LABEL_480");
+        gen.addLoad32(R0, 94);
+        gen.addJumpIfR0LessThan(0x55555555, "LABEL_574");
+        gen.addJumpIfR0GreaterThan(0xffffffffL, "LABEL_574");
+        gen.addLoadImmediate(R0, 98);
+        gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("000000002401fa000480f000000000000000000019030000"), "LABEL_574");
+        gen.addLoad32(R0, 122);
+        gen.addJumpIfR0Equals(0x0, "LABEL_574");
+        gen.addJumpIfR0LessThan(0x78, "LABEL_547");
+        gen.addJumpIfR0LessThan(0x91e, "LABEL_574");
+        gen.addJumpIfR0GreaterThan(0x1b58, "LABEL_574");
+
+        gen.defineLabel("LABEL_547");
+        gen.addLoadImmediate(R0, 126);
+        gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("2401fa000480f00000000000000000010701"), "LABEL_574");
+        gen.addLoadImmediate(R1, -72);
+        gen.addJump("LABEL_582");
+
+        gen.defineLabel("LABEL_574");
+        gen.addLoadImmediate(R1, -40);
+
+        gen.defineLabel("LABEL_576");
+        gen.addLoadData(R0, 0);
+        gen.addAdd(1);
+        gen.addStoreData(R0, 0);
+        gen.addJump(PASS_LABEL);
+
+        gen.defineLabel("LABEL_582");
+        gen.addLoadData(R0, 0);
+        gen.addAdd(1);
+        gen.addStoreData(R0, 0);
+        gen.addJump(DROP_LABEL);
+
+        byte[] program = gen.generate();
+        final String programString = toHexString(program).toLowerCase();
+        final String referenceProgramHexString = "6bf8b03a01b8120c6b8894023706006b847c023088a27c022b88a47c022688b87c022188cd7c021c88e17c021788e384004608066a0e6dff68a40202000600010800060412147a1f016dff648401f500021a1c6b807c01ec0000686bc8a401d80006ffffffffffff6a266bb0a401d10004c0a801ec6bec7401c6120c84007808000a17821f1112149c00181fffab0d2a108211446a3239a205067e9046bc70086be874019b0a1e52f06ba07c019600e06ba81a1e7e00000189ffffffff6ba47e0000017ec0a801ff0a1782140612149c000d1fffab0d2a108206076dff6c7401656bdc68a401510006ffffffffffff6bac7401526be47401477c001386dd686bc4a401340006ffffffffffff6bbc7401350a147c012800007a0e3a6b8c0a267c012200ff6bd47401170a366b9c7c011400858218886a26a2040fff02000000000000000000000000006b9872f9aa0e82ec96aa0f8c00e5048e68a2d20f7e9046bc700828c68e23672c86dd606a12a2b91400603afffe800000000000002ac68efffe23672c6a36a2b20286006a3aa2ab0240c0123c7aa600920ab494009e091e8c00991b586a3ea2781c0000000000000000010128c68e23672c05010000000005dc030440c01a5a7a73009212b49600000067555555558e0000005effffffff1a5e9600000053555555558e0000004affffffff6a62a22d18000000002401fa000480f0000000000000000000190300001a7a7a2800920a78940020091e8c001b1b586a7ea204122401fa000480f000000000000000000107016bb872086bd8b03a01b87206b03a01b87201";
+        assertEquals(referenceProgramHexString, programString);
+    }
 }
diff --git a/tests/unit/src/android/net/apf/ApfTestUtils.java b/tests/unit/src/android/net/apf/ApfTestUtils.java
index 56d7cae..3fcb8d7 100644
--- a/tests/unit/src/android/net/apf/ApfTestUtils.java
+++ b/tests/unit/src/android/net/apf/ApfTestUtils.java
@@ -28,7 +28,7 @@
 import android.content.Context;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
-import android.net.apf.ApfV4Generator.IllegalInstructionException;
+import android.net.apf.BaseApfGenerator.IllegalInstructionException;
 import android.net.ip.IIpClientCallbacks;
 import android.net.ip.IpClient;
 import android.net.metrics.IpConnectivityLog;
diff --git a/tests/unit/src/android/net/apf/ApfV5Test.kt b/tests/unit/src/android/net/apf/ApfV5Test.kt
index 30d19b2..421ed5b 100644
--- a/tests/unit/src/android/net/apf/ApfV5Test.kt
+++ b/tests/unit/src/android/net/apf/ApfV5Test.kt
@@ -15,15 +15,28 @@
  */
 package android.net.apf
 
-import android.net.apf.ApfV4Generator.IllegalInstructionException
-import android.net.apf.ApfV4Generator.MIN_APF_VERSION
-import android.net.apf.ApfV4Generator.MIN_APF_VERSION_IN_DEV
-import android.net.apf.ApfV4Generator.Register.R0
-import android.net.apf.ApfV4Generator.Register.R1
+import android.net.apf.ApfCounterTracker.Counter
+import android.net.apf.ApfTestUtils.DROP
+import android.net.apf.ApfTestUtils.MIN_PKT_SIZE
+import android.net.apf.ApfTestUtils.PASS
+import android.net.apf.ApfTestUtils.assertDrop
+import android.net.apf.ApfTestUtils.assertPass
+import android.net.apf.ApfTestUtils.assertVerdict
+import android.net.apf.BaseApfGenerator.DROP_LABEL
+import android.net.apf.BaseApfGenerator.IllegalInstructionException
+import android.net.apf.BaseApfGenerator.MIN_APF_VERSION
+import android.net.apf.BaseApfGenerator.MIN_APF_VERSION_IN_DEV
+import android.net.apf.BaseApfGenerator.Register.R0
+import android.net.apf.BaseApfGenerator.Register.R1
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import java.lang.IllegalArgumentException
+import com.android.net.module.util.Struct
+import com.android.net.module.util.structs.EthernetHeader
+import com.android.net.module.util.structs.Ipv4Header
+import com.android.net.module.util.structs.UdpHeader
+import java.nio.ByteBuffer
 import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
 import kotlin.test.assertFailsWith
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -35,54 +48,19 @@
 @SmallTest
 class ApfV5Test {
 
-    @Test
-    fun testApfInstructionVersionCheck() {
-        var gen = ApfV4Generator(MIN_APF_VERSION)
-        assertFailsWith<IllegalInstructionException> { gen.addDrop() }
-        assertFailsWith<IllegalInstructionException> { gen.addCountAndDrop(12) }
-        assertFailsWith<IllegalInstructionException> { gen.addCountAndPass(1000) }
-        assertFailsWith<IllegalInstructionException> { gen.addTransmit() }
-        assertFailsWith<IllegalInstructionException> { gen.addDiscard() }
-        assertFailsWith<IllegalInstructionException> { gen.addAllocateR0() }
-        assertFailsWith<IllegalInstructionException> { gen.addAllocate(100) }
-        assertFailsWith<IllegalInstructionException> { gen.addData(ByteArray(3) { 0x01 }) }
-        assertFailsWith<IllegalInstructionException> { gen.addWriteU8(100) }
-        assertFailsWith<IllegalInstructionException> { gen.addWriteU16(100) }
-        assertFailsWith<IllegalInstructionException> { gen.addWriteU32(100) }
-        assertFailsWith<IllegalInstructionException> { gen.addPacketCopy(100, 100) }
-        assertFailsWith<IllegalInstructionException> { gen.addDataCopy(100, 100) }
-        assertFailsWith<IllegalInstructionException> { gen.addWriteU8(R0) }
-        assertFailsWith<IllegalInstructionException> { gen.addWriteU16(R0) }
-        assertFailsWith<IllegalInstructionException> { gen.addWriteU32(R0) }
-        assertFailsWith<IllegalInstructionException> { gen.addWriteU8(R1) }
-        assertFailsWith<IllegalInstructionException> { gen.addWriteU16(R1) }
-        assertFailsWith<IllegalInstructionException> { gen.addWriteU32(R1) }
-        assertFailsWith<IllegalInstructionException> { gen.addPacketCopyFromR0LenR1() }
-        assertFailsWith<IllegalInstructionException> { gen.addDataCopyFromR0LenR1() }
-        assertFailsWith<IllegalInstructionException> { gen.addPacketCopyFromR0(10) }
-        assertFailsWith<IllegalInstructionException> { gen.addDataCopyFromR0(10) }
-        assertFailsWith<IllegalInstructionException> {
-            gen.addJumpIfBytesAtR0Equal(byteArrayOf('A'.code.toByte()), ApfV4Generator.DROP_LABEL) }
-        assertFailsWith<IllegalInstructionException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ(
-                byteArrayOf(1, 'A'.code.toByte()), 0x0c, ApfV4Generator.DROP_LABEL) }
-        assertFailsWith<IllegalInstructionException> { gen.addJumpIfPktAtR0ContainDnsQ(
-                byteArrayOf(1, 'A'.code.toByte()), 0x0c, ApfV4Generator.DROP_LABEL) }
-        assertFailsWith<IllegalInstructionException> { gen.addJumpIfPktAtR0DoesNotContainDnsA(
-                byteArrayOf(1, 'A'.code.toByte()), ApfV4Generator.DROP_LABEL) }
-        assertFailsWith<IllegalInstructionException> { gen.addJumpIfPktAtR0ContainDnsA(
-                byteArrayOf(1, 'A'.code.toByte()), ApfV4Generator.DROP_LABEL) }
-    }
+    private val testPacket = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8,
+                                         9, 10, 11, 12, 13, 14, 15, 16)
 
     @Test
     fun testDataInstructionMustComeFirst() {
-        var gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV)
+        var gen = ApfV6Generator()
         gen.addAllocateR0()
         assertFailsWith<IllegalInstructionException> { gen.addData(ByteArray(3) { 0x01 }) }
     }
 
     @Test
     fun testApfInstructionEncodingSizeCheck() {
-        var gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV)
+        var gen = ApfV6Generator()
         assertFailsWith<IllegalArgumentException> { gen.addAllocate(65536) }
         assertFailsWith<IllegalArgumentException> { gen.addAllocate(-1) }
         assertFailsWith<IllegalArgumentException> { gen.addDataCopy(-1, 1) }
@@ -170,10 +148,25 @@
     }
 
     @Test
+    fun testValidateDnsNames() {
+        // '%' is a valid label character in mDNS subtype
+        // byte == 0xff means it is a '*' wildcard, which is a valid encoding.
+        val program = ApfV6Generator().addJumpIfPktAtR0ContainDnsQ(
+                byteArrayOf(1, '%'.code.toByte(), 0, 0),
+                1,
+                DROP_LABEL)
+                .addJumpIfPktAtR0ContainDnsA(
+                        byteArrayOf(0xff.toByte(), 1, 'B'.code.toByte(), 0, 0),
+                        DROP_LABEL
+                )
+                .generate()
+    }
+
+    @Test
     fun testApfInstructionsEncoding() {
-        var gen = ApfV4Generator(MIN_APF_VERSION)
-        gen.addPass()
-        var program = gen.generate()
+        val v4gen = ApfV4Generator<ApfV4Generator<BaseApfGenerator>>(MIN_APF_VERSION)
+        v4gen.addPass()
+        var program = v4gen.generate()
         // encoding PASS opcode: opcode=0, imm_len=0, R=0
         assertContentEquals(
                 byteArrayOf(encodeInstruction(opcode = 0, immLength = 0, register = 0)), program)
@@ -181,7 +174,7 @@
             listOf("0: pass"),
             ApfJniUtils.disassembleApf(program).map { it.trim() } )
 
-        gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV)
+        var gen = ApfV6Generator()
         gen.addDrop()
         program = gen.generate()
         // encoding DROP opcode: opcode=0, imm_len=0, R=1
@@ -191,7 +184,7 @@
             listOf("0: drop"),
             ApfJniUtils.disassembleApf(program).map { it.trim() } )
 
-        gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV)
+        gen = ApfV6Generator()
         gen.addCountAndPass(129)
         program = gen.generate()
         // encoding COUNT(PASS) opcode: opcode=0, imm_len=size_of(imm), R=0, imm=counterNumber
@@ -202,7 +195,7 @@
             listOf("0: pass         129"),
             ApfJniUtils.disassembleApf(program).map { it.trim() } )
 
-        gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV)
+        gen = ApfV6Generator()
         gen.addCountAndDrop(1000)
         program = gen.generate()
         // encoding COUNT(DROP) opcode: opcode=0, imm_len=size_of(imm), R=1, imm=counterNumber
@@ -213,7 +206,7 @@
             listOf("0: drop         1000"),
             ApfJniUtils.disassembleApf(program).map { it.trim() } )
 
-        gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV)
+        gen = ApfV6Generator()
         gen.addAllocateR0()
         gen.addAllocate(1500)
         program = gen.generate()
@@ -227,21 +220,23 @@
         assertContentEquals(listOf("0: allocate    r0", "2: allocate    1500"),
             ApfJniUtils.disassembleApf(program).map { it.trim() })
 
-        gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV)
-        gen.addTransmit()
-        gen.addDiscard()
+        gen = ApfV6Generator()
+        gen.addTransmitWithoutChecksum()
+        gen.addTransmitL4(30, 40, 50, 256, true)
         program = gen.generate()
-        // encoding TRANSMIT/DISCARD opcode: opcode=21(EXT opcode number),
-        // imm=37(TRANSMIT/DISCARD opcode number),
-        // R=0 means discard the buffer. R=1 means transmit the buffer.
+        // encoding TRANSMIT opcode: opcode=21(EXT opcode number),
+        // imm=37(TRANSMIT opcode number),
         assertContentEquals(byteArrayOf(
-                encodeInstruction(opcode = 21, immLength = 1, register = 0), 37,
-                encodeInstruction(opcode = 21, immLength = 1, register = 1), 37,
+                encodeInstruction(opcode = 21, immLength = 1, register = 0),
+                37, 255.toByte(), 255.toByte(),
+                encodeInstruction(opcode = 21, immLength = 1, register = 1), 37, 30, 40, 50, 1, 0
         ), program)
-         assertContentEquals(listOf("0: discard", "2: transmit"),
-             ApfJniUtils.disassembleApf(program).map { it.trim() })
+         assertContentEquals(listOf(
+                 "0: transmit    ip_ofs=255",
+                 "4: transmitudp ip_ofs=30, csum_ofs=40, csum_start=50, partial_csum=0x0100",
+         ), ApfJniUtils.disassembleApf(program).map { it.trim() })
 
-        gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV)
+        gen = ApfV6Generator()
         val largeByteArray = ByteArray(256) { 0x01 }
         gen.addData(largeByteArray)
         program = gen.generate()
@@ -249,10 +244,10 @@
         assertContentEquals(byteArrayOf(
                 encodeInstruction(opcode = 14, immLength = 2, register = 1), 0x01, 0x00) +
                 largeByteArray, program)
-        assertContentEquals(listOf("0: data        256," + "01".repeat(256) ),
+        assertContentEquals(listOf("0: data        256, " + "01".repeat(256) ),
             ApfJniUtils.disassembleApf(program).map { it.trim() })
 
-        gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV)
+        gen = ApfV6Generator()
         gen.addWriteU8(0x01)
         gen.addWriteU16(0x0102)
         gen.addWriteU32(0x01020304)
@@ -287,7 +282,7 @@
         ),
         ApfJniUtils.disassembleApf(program).map { it.trim() })
 
-        gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV)
+        gen = ApfV6Generator()
         gen.addWriteU8(R0)
         gen.addWriteU16(R0)
         gen.addWriteU32(R0)
@@ -311,7 +306,7 @@
                 "8: ewrite2     r1",
                 "10: ewrite4     r1"), ApfJniUtils.disassembleApf(program).map { it.trim() })
 
-        gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV)
+        gen = ApfV6Generator()
         gen.addDataCopy(0, 10)
         gen.addDataCopy(1, 5)
         gen.addPacketCopy(1000, 255)
@@ -323,36 +318,42 @@
                 0x03.toByte(), 0xe8.toByte(), 0xff.toByte(),
         ), program)
         assertContentEquals(listOf(
-                "0: dcopy       0, 10",
-                "2: dcopy       1, 5",
-                "5: pcopy       1000, 255"), ApfJniUtils.disassembleApf(program).map { it.trim() })
+                "0: datacopy    src=0, len=10",
+                "2: datacopy    src=1, len=5",
+                "5: pktcopy     src=1000, len=255"
+        ),
+        ApfJniUtils.disassembleApf(program).map { it.trim() })
 
-        gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV)
-        gen.addPacketCopyFromR0LenR1()
+        gen = ApfV6Generator()
+        gen.addDataCopyFromR0(5)
         gen.addPacketCopyFromR0(5)
         gen.addDataCopyFromR0LenR1()
-        gen.addDataCopyFromR0(5)
+        gen.addPacketCopyFromR0LenR1()
         program = gen.generate()
         assertContentEquals(byteArrayOf(
-                encodeInstruction(21, 1, 1), 41,
+                encodeInstruction(21, 1, 1), 41, 5,
                 encodeInstruction(21, 1, 0), 41, 5,
                 encodeInstruction(21, 1, 1), 42,
-                encodeInstruction(21, 1, 0), 42, 5,
+                encodeInstruction(21, 1, 0), 42,
         ), program)
-        // TODO: add back the following test case when implementing EPKTCOPY, EDATACOPY opcodes.
-//        assertContentEquals(arrayOf(
-//                "       0: dcopy [r1+0], 5",
-//                "       4: pcopy [r0+1000], 255"), ApfJniUtils.disassembleApf(program))
+        assertContentEquals(listOf(
+            "0: edatacopy    src=r0, len=5",
+            "3: epktcopy     src=r0, len=5",
+            "6: edatacopy    src=r0, len=r1",
+            "8: epktcopy     src=r0, len=r1"), ApfJniUtils.disassembleApf(program).map{ it.trim() })
 
-        gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV)
+        gen = ApfV6Generator()
         gen.addJumpIfBytesAtR0Equal(byteArrayOf('a'.code.toByte()), ApfV4Generator.DROP_LABEL)
         program = gen.generate()
         assertContentEquals(
                 byteArrayOf(encodeInstruction(opcode = 20, immLength = 1, register = 1),
                         1, 1, 'a'.code.toByte()), program)
+        assertContentEquals(listOf(
+            "0: jbseq       r0, 0x1, DROP, 61"),
+            ApfJniUtils.disassembleApf(program).map{ it.trim() })
 
         val qnames = byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0, 0)
-        gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV)
+        gen = ApfV6Generator()
         gen.addJumpIfPktAtR0DoesNotContainDnsQ(qnames, 0x0c, ApfV4Generator.DROP_LABEL)
         gen.addJumpIfPktAtR0ContainDnsQ(qnames, 0x0c, ApfV4Generator.DROP_LABEL)
         program = gen.generate()
@@ -361,8 +362,26 @@
         ) + qnames + byteArrayOf(
                 encodeInstruction(21, 1, 1), 43, 1, 0x0c.toByte(),
         ) + qnames, program)
+        assertContentEquals(listOf(
+            "0: jdnsqne     r0, DROP, 12, (1)A(1)B(0)(0)",
+            "10: jdnsqeq     r0, DROP, 12, (1)A(1)B(0)(0)"),
+            ApfJniUtils.disassembleApf(program).map{ it.trim() })
 
-        gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV)
+        gen = ApfV6Generator()
+        gen.addJumpIfPktAtR0DoesNotContainDnsQSafe(qnames, 0x0c, ApfV4Generator.DROP_LABEL)
+        gen.addJumpIfPktAtR0ContainDnsQSafe(qnames, 0x0c, ApfV4Generator.DROP_LABEL)
+        program = gen.generate()
+        assertContentEquals(byteArrayOf(
+                encodeInstruction(21, 1, 0), 45, 11, 0x0c.toByte(),
+        ) + qnames + byteArrayOf(
+                encodeInstruction(21, 1, 1), 45, 1, 0x0c.toByte(),
+        ) + qnames, program)
+        assertContentEquals(listOf(
+                "0: jdnsqnesafe r0, DROP, 12, (1)A(1)B(0)(0)",
+                "10: jdnsqeqsafe r0, DROP, 12, (1)A(1)B(0)(0)"),
+                ApfJniUtils.disassembleApf(program).map{ it.trim() })
+
+        gen = ApfV6Generator()
         gen.addJumpIfPktAtR0DoesNotContainDnsA(qnames, ApfV4Generator.DROP_LABEL)
         gen.addJumpIfPktAtR0ContainDnsA(qnames, ApfV4Generator.DROP_LABEL)
         program = gen.generate()
@@ -371,10 +390,419 @@
         ) + qnames + byteArrayOf(
                 encodeInstruction(21, 1, 1), 44, 1,
         ) + qnames, program)
+        assertContentEquals(listOf(
+            "0: jdnsane     r0, DROP, (1)A(1)B(0)(0)",
+            "9: jdnsaeq     r0, DROP, (1)A(1)B(0)(0)"),
+            ApfJniUtils.disassembleApf(program).map{ it.trim() })
+
+        gen = ApfV6Generator()
+        gen.addJumpIfPktAtR0DoesNotContainDnsASafe(qnames, ApfV4Generator.DROP_LABEL)
+        gen.addJumpIfPktAtR0ContainDnsASafe(qnames, ApfV4Generator.DROP_LABEL)
+        program = gen.generate()
+        assertContentEquals(byteArrayOf(
+                encodeInstruction(21, 1, 0), 46, 10,
+        ) + qnames + byteArrayOf(
+                encodeInstruction(21, 1, 1), 46, 1,
+        ) + qnames, program)
+        assertContentEquals(listOf(
+                "0: jdnsanesafe r0, DROP, (1)A(1)B(0)(0)",
+                "9: jdnsaeqsafe r0, DROP, (1)A(1)B(0)(0)"),
+                ApfJniUtils.disassembleApf(program).map{ it.trim() })
+    }
+
+    @Test
+    fun testWriteToTxBuffer() {
+        var program = ApfV6Generator()
+            .addAllocate(14)
+            .addWriteU8(0x01)
+            .addWriteU16(0x0203)
+            .addWriteU32(0x04050607)
+            .addLoadImmediate(R0, 1)
+            .addWriteU8(R0)
+            .addLoadImmediate(R0, 0x0203)
+            .addWriteU16(R0)
+            .addLoadImmediate(R1, 0x04050607)
+            .addWriteU32(R1)
+            .addTransmitWithoutChecksum()
+            .generate()
+        assertPass(MIN_APF_VERSION_IN_DEV, program, ByteArray(MIN_PKT_SIZE))
+        assertContentEquals(byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x02, 0x03,
+                0x04, 0x05, 0x06, 0x07), ApfJniUtils.getTransmittedPacket())
+    }
+
+    @Test
+    fun testCopyToTxBuffer() {
+        var program = ApfV6Generator()
+            .addData(byteArrayOf(33, 34, 35))
+            .addAllocate(14)
+            .addDataCopy(2 /* src */, 2 /* len */)
+            .addDataCopy(4 /* src */, 1 /* len */)
+            .addPacketCopy(0 /* src */, 1 /* len */)
+            .addPacketCopy(1 /* src */, 3 /* len */)
+            .addLoadImmediate(R0, 2) // data copy offset
+            .addDataCopyFromR0(2 /* len */)
+            .addLoadImmediate(R0, 4) // data copy offset
+            .addLoadImmediate(R1, 1) // len
+            .addDataCopyFromR0LenR1()
+            .addLoadImmediate(R0, 0) // packet copy offset
+            .addPacketCopyFromR0(1 /* len */)
+            .addLoadImmediate(R0, 1) // packet copy offset
+            .addLoadImmediate(R1, 3) // len
+            .addPacketCopyFromR0LenR1()
+            .addTransmitWithoutChecksum()
+            .generate()
+        assertPass(MIN_APF_VERSION_IN_DEV, program, testPacket)
+        assertContentEquals(byteArrayOf(33, 34, 35, 1, 2, 3, 4, 33, 34, 35, 1, 2, 3, 4),
+                ApfJniUtils.getTransmittedPacket())
+    }
+
+    @Test
+    fun testPassDrop() {
+        var program = ApfV6Generator()
+                .addDrop()
+                .addPass()
+                .generate()
+        assertDrop(MIN_APF_VERSION_IN_DEV, program, testPacket)
+
+        var dataRegion = ByteArray(Counter.totalSize()) { 0 }
+        program = ApfV6Generator()
+                .addData(byteArrayOf())
+                .addCountAndDrop(Counter.DROPPED_ETH_BROADCAST.value())
+                .generate()
+        assertVerdict(MIN_APF_VERSION_IN_DEV, DROP, program, testPacket, dataRegion)
+        var counterMap = decodeCountersIntoMap(dataRegion)
+        assertEquals(mapOf<Counter, Long>(
+                Counter.TOTAL_PACKETS to 1,
+                Counter.DROPPED_ETH_BROADCAST to 1), counterMap)
+
+        dataRegion = ByteArray(Counter.totalSize()) { 0 }
+        program = ApfV6Generator()
+                .addData(byteArrayOf())
+                .addCountAndPass(Counter.PASSED_ARP.value())
+                .generate()
+        assertVerdict(MIN_APF_VERSION_IN_DEV, PASS, program, testPacket, dataRegion)
+        counterMap = decodeCountersIntoMap(dataRegion)
+        assertEquals(mapOf<Counter, Long>(
+                Counter.TOTAL_PACKETS to 1,
+                Counter.PASSED_ARP to 1), counterMap)
+    }
+
+    @Test
+    fun testAllocateFailure() {
+        val program = ApfV6Generator()
+                .addData(byteArrayOf())
+                // allocate size: 65535 > sizeof(apf_test_buffer): 1514, trigger allocate failure.
+                .addAllocate(65535)
+                .addDrop()
+                .generate()
+        val dataRegion = ByteArray(Counter.totalSize()) { 0 }
+        assertVerdict(MIN_APF_VERSION_IN_DEV, PASS, program, testPacket, dataRegion)
+        val counterMap = decodeCountersIntoMap(dataRegion)
+        assertEquals(mapOf<Counter, Long>(
+                Counter.TOTAL_PACKETS to 1,
+                Counter.PASSED_ALLOCATE_FAILURE to 1), counterMap)
+    }
+
+    @Test
+    fun testTransmitFailure() {
+        val program = ApfV6Generator()
+                .addData(byteArrayOf())
+                .addAllocate(14)
+                // len: 13 is less than ETH_HLEN, trigger transmit failure.
+                .addLoadImmediate(R0, 13)
+                .addStoreToMemory(R0, 10)
+                .addTransmitWithoutChecksum()
+                .addDrop()
+                .generate()
+        val dataRegion = ByteArray(Counter.totalSize()) { 0 }
+        assertVerdict(MIN_APF_VERSION_IN_DEV, PASS, program, testPacket, dataRegion)
+        val counterMap = decodeCountersIntoMap(dataRegion)
+        assertEquals(mapOf<Counter, Long>(
+                Counter.TOTAL_PACKETS to 1,
+                Counter.PASSED_TRANSMIT_FAILURE to 1), counterMap)
+    }
+
+    @Test
+    fun testTransmitL4() {
+        val etherIpv4UdpPacket = intArrayOf(
+            0x01, 0x00, 0x5e, 0x00, 0x00, 0xfb,
+            0x38, 0xca, 0x84, 0xb7, 0x7f, 0x16,
+            0x08, 0x00, // end of ethernet header
+            0x45,
+            0x04,
+            0x00, 0x3f,
+            0x43, 0xcd,
+            0x40, 0x00,
+            0xff,
+            0x11,
+            0x00, 0x00, // ipv4 checksum set to 0
+            0xc0, 0xa8, 0x01, 0x03,
+            0xe0, 0x00, 0x00, 0xfb, // end of ipv4 header
+            0x14, 0xe9,
+            0x14, 0xe9,
+            0x00, 0x2b,
+            0x00, 0x2b, // end of udp header. udp checksum set to udp (header + payload) size
+            0x00, 0x00, 0x84, 0x00, 0x00, 0x00,
+            0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x62, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
+            0x00, 0x00, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x04, 0xc0, 0xa8, 0x01,
+            0x09,
+        ).map { it.toByte() }.toByteArray()
+        val program = ApfV6Generator()
+                .addData(etherIpv4UdpPacket)
+                .addAllocate(etherIpv4UdpPacket.size)
+                .addDataCopy(2 /* src */, etherIpv4UdpPacket.size /* len */)
+                .addTransmitL4(ETH_HLEN /* ipOfs */,
+                        ETH_HLEN + IPV4_HLEN + 6 /* csumOfs */,
+                        ETH_HLEN + IPV4_HLEN - 8 /* csumStart */,
+                        IPPROTO_UDP /* partialCsum */,
+                        true /* isUdp */)
+                .generate()
+        assertPass(MIN_APF_VERSION_IN_DEV, program, testPacket)
+        val txBuf = ByteBuffer.wrap(ApfJniUtils.getTransmittedPacket())
+        Struct.parse(EthernetHeader::class.java, txBuf)
+        val ipv4Hdr = Struct.parse(Ipv4Header::class.java, txBuf)
+        val udpHdr = Struct.parse(UdpHeader::class.java, txBuf)
+        assertEquals(0x9535.toShort(), ipv4Hdr.checksum)
+        assertEquals(0xa73d.toShort(), udpHdr.checksum)
+    }
+
+    @Test
+    fun testDnsQuestionMatch() {
+        // needles = { A, B.LOCAL }
+        val needlesMatch = intArrayOf(
+            0x01, 'A'.code,
+            0x00,
+            0x01, 'B'.code,
+            0x05, 'L'.code, 'O'.code, 'C'.code, 'A'.code, 'L'.code,
+            0x00,
+            0x00
+        ).map { it.toByte() }.toByteArray()
+        val udpPayload = intArrayOf(
+            0x00, 0x00, 0x00, 0x00, // tid = 0x00, flags = 0x00,
+            0x00, 0x02, // qdcount = 2
+            0x00, 0x00, // ancount = 0
+            0x00, 0x00, // nscount = 0
+            0x00, 0x00, // arcount = 0
+            0x01, 'a'.code,
+            0x01, 'b'.code,
+            0x05, 'l'.code, 'o'.code, 'c'.code, 'a'.code, 'l'.code,
+            0x00, // qname1 = a.b.local
+            0x00, 0x01, 0x00, 0x01, // type = A, class = 0x0001
+            0xc0, 0x0e, // qname2 = b.local (name compression)
+            0x00, 0x01, 0x00, 0x01 // type = A, class = 0x0001
+        ).map { it.toByte() }.toByteArray()
+
+        var program = ApfV6Generator()
+                .addData(byteArrayOf())
+                .addLoadImmediate(R0, 0)
+                .addJumpIfPktAtR0ContainDnsQ(needlesMatch, 0x01 /* qtype */, DROP_LABEL)
+                .addPass()
+                .generate()
+        assertDrop(MIN_APF_VERSION_IN_DEV, program, udpPayload)
+
+        program = ApfV6Generator()
+                .addData(byteArrayOf())
+                .addLoadImmediate(R0, 0)
+                .addJumpIfPktAtR0ContainDnsQSafe(needlesMatch, 0x01 /* qtype */, DROP_LABEL)
+                .addPass()
+                .generate()
+        assertDrop(MIN_APF_VERSION_IN_DEV, program, udpPayload)
+
+        program = ApfV6Generator()
+                .addData(byteArrayOf())
+                .addLoadImmediate(R0, 0)
+                .addJumpIfPktAtR0DoesNotContainDnsQ(needlesMatch, 0x01 /* qtype */, DROP_LABEL)
+                .addPass()
+                .generate()
+        assertPass(MIN_APF_VERSION_IN_DEV, program, udpPayload)
+
+        program = ApfV6Generator()
+                .addData(byteArrayOf())
+                .addLoadImmediate(R0, 0)
+                .addJumpIfPktAtR0DoesNotContainDnsQSafe(needlesMatch, 0x01 /* qtype */, DROP_LABEL)
+                .addPass()
+                .generate()
+        assertPass(MIN_APF_VERSION_IN_DEV, program, udpPayload)
+
+        val badUdpPayload = intArrayOf(
+                0x00, 0x00, 0x00, 0x00, // tid = 0x00, flags = 0x00,
+                0x00, 0x02, // qdcount = 2
+                0x00, 0x00, // ancount = 0
+                0x00, 0x00, // nscount = 0
+                0x00, 0x00, // arcount = 0
+                0x01, 'a'.code,
+                0x01, 'b'.code,
+                0x05, 'l'.code, 'o'.code, 'c'.code, 'a'.code, 'l'.code,
+                0x00, // qname1 = a.b.local
+                0x00, 0x01, 0x00, 0x01, // type = A, class = 0x0001
+                0xc0, 0x1b, // corrupted pointer cause infinite loop
+                0x00, 0x01, 0x00, 0x01 // type = A, class = 0x0001
+        ).map { it.toByte() }.toByteArray()
+
+        program = ApfV6Generator()
+                .addData(byteArrayOf())
+                .addLoadImmediate(R0, 0)
+                .addJumpIfPktAtR0ContainDnsQ(needlesMatch, 0x01 /* qtype */, DROP_LABEL)
+                .addPass()
+                .generate()
+        var dataRegion = ByteArray(Counter.totalSize()) { 0 }
+        assertVerdict(MIN_APF_VERSION_IN_DEV, DROP, program, badUdpPayload, dataRegion)
+        var counterMap = decodeCountersIntoMap(dataRegion)
+        assertEquals(mapOf<Counter, Long>(
+                Counter.TOTAL_PACKETS to 1,
+                Counter.CORRUPT_DNS_PACKET to 1), counterMap)
+
+        program = ApfV6Generator()
+                .addData(byteArrayOf())
+                .addLoadImmediate(R0, 0)
+                .addJumpIfPktAtR0ContainDnsQSafe(needlesMatch, 0x01 /* qtype */, DROP_LABEL)
+                .addPass()
+                .generate()
+        dataRegion = ByteArray(Counter.totalSize()) { 0 }
+        assertVerdict(MIN_APF_VERSION_IN_DEV, PASS, program, badUdpPayload, dataRegion)
+        counterMap = decodeCountersIntoMap(dataRegion)
+        assertEquals(mapOf<Counter, Long>(
+                Counter.TOTAL_PACKETS to 1,
+                Counter.CORRUPT_DNS_PACKET to 1), counterMap)
+    }
+
+    @Test
+    fun testDnsAnswerMatch() {
+        // needles = { A, B.LOCAL }
+        val needlesMatch = intArrayOf(
+                0x01, 'A'.code,
+                0x00,
+                0x01, 'B'.code,
+                0x05, 'L'.code, 'O'.code, 'C'.code, 'A'.code, 'L'.code,
+                0x00,
+                0x00
+        ).map { it.toByte() }.toByteArray()
+
+        val udpPayload = intArrayOf(
+                0x00, 0x00, 0x84, 0x00, // tid = 0x00, flags = 0x8400,
+                0x00, 0x00, // qdcount = 0
+                0x00, 0x02, // ancount = 2
+                0x00, 0x00, // nscount = 0
+                0x00, 0x00, // arcount = 0
+                0x01, 'a'.code,
+                0x01, 'b'.code,
+                0x05, 'l'.code, 'o'.code, 'c'.code, 'a'.code, 'l'.code,
+                0x00, // name1 = a.b.local
+                0x00, 0x01, 0x80, 0x01, // type = A, class = 0x8001
+                0x00, 0x00, 0x00, 0x78, // ttl = 120
+                0x00, 0x04, 0xc0, 0xa8, 0x01, 0x09, // rdlengh = 4, rdata = 192.168.1.9
+                0xc0, 0x0e, // name2 = b.local (name compression)
+                0x00, 0x01, 0x80, 0x01, // type = A, class = 0x8001
+                0x00, 0x00, 0x00, 0x78, // ttl = 120
+                0x00, 0x04, 0xc0, 0xa8, 0x01, 0x09 // rdlengh = 4, rdata = 192.168.1.9
+        ).map { it.toByte() }.toByteArray()
+
+        var program = ApfV6Generator()
+                .addData(byteArrayOf())
+                .addLoadImmediate(R0, 0)
+                .addJumpIfPktAtR0ContainDnsA(needlesMatch, DROP_LABEL)
+                .addPass()
+                .generate()
+        assertDrop(MIN_APF_VERSION_IN_DEV, program, udpPayload)
+
+        program = ApfV6Generator()
+                .addData(byteArrayOf())
+                .addLoadImmediate(R0, 0)
+                .addJumpIfPktAtR0ContainDnsASafe(needlesMatch, DROP_LABEL)
+                .addPass()
+                .generate()
+        assertDrop(MIN_APF_VERSION_IN_DEV, program, udpPayload)
+
+        program = ApfV6Generator()
+                .addData(byteArrayOf())
+                .addLoadImmediate(R0, 0)
+                .addJumpIfPktAtR0DoesNotContainDnsA(needlesMatch, DROP_LABEL)
+                .addPass()
+                .generate()
+        assertPass(MIN_APF_VERSION_IN_DEV, program, udpPayload)
+
+        program = ApfV6Generator()
+                .addData(byteArrayOf())
+                .addLoadImmediate(R0, 0)
+                .addJumpIfPktAtR0DoesNotContainDnsASafe(needlesMatch, DROP_LABEL)
+                .addPass()
+                .generate()
+        assertPass(MIN_APF_VERSION_IN_DEV, program, udpPayload)
+
+        val badUdpPayload = intArrayOf(
+                0x00, 0x00, 0x84, 0x00, // tid = 0x00, flags = 0x8400,
+                0x00, 0x00, // qdcount = 0
+                0x00, 0x02, // ancount = 2
+                0x00, 0x00, // nscount = 0
+                0x00, 0x00, // arcount = 0
+                0x01, 'a'.code,
+                0x01, 'b'.code,
+                0x05, 'l'.code, 'o'.code, 'c'.code, 'a'.code, 'l'.code,
+                0x00, // name1 = a.b.local
+                0x00, 0x01, 0x80, 0x01, // type = A, class = 0x8001
+                0x00, 0x00, 0x00, 0x78, // ttl = 120
+                0x00, 0x04, 0xc0, 0xa8, 0x01, 0x09, // rdlengh = 4, rdata = 192.168.1.9
+                0xc0, 0x25, // corrupted pointer cause infinite loop
+                0x00, 0x01, 0x80, 0x01, // type = A, class = 0x8001
+                0x00, 0x00, 0x00, 0x78, // ttl = 120
+                0x00, 0x04, 0xc0, 0xa8, 0x01, 0x09 // rdlengh = 4, rdata = 192.168.1.9
+        ).map { it.toByte() }.toByteArray()
+
+        program = ApfV6Generator()
+                .addData(byteArrayOf())
+                .addLoadImmediate(R0, 0)
+                .addJumpIfPktAtR0ContainDnsA(needlesMatch, DROP_LABEL)
+                .addPass()
+                .generate()
+        var dataRegion = ByteArray(Counter.totalSize()) { 0 }
+        assertVerdict(MIN_APF_VERSION_IN_DEV, DROP, program, badUdpPayload, dataRegion)
+        var counterMap = decodeCountersIntoMap(dataRegion)
+        assertEquals(mapOf<Counter, Long>(
+                Counter.TOTAL_PACKETS to 1,
+                Counter.CORRUPT_DNS_PACKET to 1), counterMap)
+
+        program = ApfV6Generator()
+                .addData(byteArrayOf())
+                .addLoadImmediate(R0, 0)
+                .addJumpIfPktAtR0ContainDnsASafe(needlesMatch, DROP_LABEL)
+                .addPass()
+                .generate()
+        dataRegion = ByteArray(Counter.totalSize()) { 0 }
+        assertVerdict(MIN_APF_VERSION_IN_DEV, PASS, program, badUdpPayload, dataRegion)
+        counterMap = decodeCountersIntoMap(dataRegion)
+        assertEquals(mapOf<Counter, Long>(
+                Counter.TOTAL_PACKETS to 1,
+                Counter.CORRUPT_DNS_PACKET to 1), counterMap)
+    }
+
+    @Test
+    fun testGetCounterValue() {
+        val counterBytes = intArrayOf(0xff, 0, 0, 0, 0x78, 0x56, 0x34, 0x12)
+                .map { it.toByte() }.toByteArray()
+        assertEquals(0xff, ApfCounterTracker.getCounterValue(counterBytes, Counter.TOTAL_PACKETS))
+    }
+
+    private fun decodeCountersIntoMap(counterBytes: ByteArray): Map<Counter, Long> {
+        val counters = Counter::class.java.enumConstants
+        val ret = HashMap<Counter, Long>()
+        // starting from index 2 to skip the endianness mark
+        for (c in listOf(*counters).subList(2, counters.size)) {
+            val value = ApfCounterTracker.getCounterValue(counterBytes, c)
+            if (value != 0L) {
+                ret[c] = value
+            }
+        }
+        return ret
     }
 
     private fun encodeInstruction(opcode: Int, immLength: Int, register: Int): Byte {
         val immLengthEncoding = if (immLength == 4) 3 else immLength
         return opcode.shl(3).or(immLengthEncoding.shl(1)).or(register).toByte()
     }
+
+    companion object {
+        const val ETH_HLEN = 14
+        const val IPV4_HLEN = 20
+        const val IPPROTO_UDP = 17
+    }
 }
diff --git a/tests/unit/src/android/net/apf/Bpf2Apf.java b/tests/unit/src/android/net/apf/Bpf2Apf.java
index 795c2b3..5d2f9a9 100644
--- a/tests/unit/src/android/net/apf/Bpf2Apf.java
+++ b/tests/unit/src/android/net/apf/Bpf2Apf.java
@@ -16,8 +16,11 @@
 
 package android.net.apf;
 
-import android.net.apf.ApfV4Generator.IllegalInstructionException;
-import android.net.apf.ApfV4Generator.Register;
+import static android.net.apf.BaseApfGenerator.Register.R0;
+import static android.net.apf.BaseApfGenerator.Register.R1;
+
+import android.net.apf.BaseApfGenerator.IllegalInstructionException;
+import android.net.apf.BaseApfGenerator.Register;
 
 import java.io.BufferedReader;
 import java.io.InputStreamReader;
@@ -29,7 +32,7 @@
  *       translation of all BPF programs.
  *
  * Example usage:
- *   javac net/java/android/net/apf/ApfGenerator.java \
+ *   javac net/java/android/net/apf/ApfV4Generator.java \
  *         tests/servicestests/src/android/net/apf/Bpf2Apf.java
  *   sudo tcpdump -i em1 -d icmp | java -classpath tests/servicestests/src:net/java \
  *                                      android.net.apf.Bpf2Apf
@@ -67,7 +70,7 @@
             case "ldx":
             case "ldxb":
             case "ldxh":
-                Register dest = opcode.contains("x") ? Register.R1 : Register.R0;
+                Register dest = opcode.contains("x") ? R1 : R0;
                 if (arg.equals("4*([14]&0xf)")) {
                     if (!opcode.equals("ldxb")) {
                         throw new IllegalArgumentException("Unhandled instruction: " + line);
@@ -140,7 +143,7 @@
                 break;
             case "st":
             case "stx":
-                Register src = opcode.contains("x") ? Register.R1 : Register.R0;
+                Register src = opcode.contains("x") ? R1 : R0;
                 if (!arg.startsWith("M[")) {
                     throw new IllegalArgumentException("Unhandled instruction: " + line);
                 }
@@ -169,9 +172,9 @@
                             gen.addOrR1();
                             break;
                         case "sub":
-                            gen.addNeg(Register.R1);
+                            gen.addNeg(R1);
                             gen.addAddR1();
-                            gen.addNeg(Register.R1);
+                            gen.addNeg(R1);
                             break;
                     }
                 } else {
@@ -291,10 +294,10 @@
                 }
                 break;
             case "tax":
-                gen.addMove(Register.R1);
+                gen.addMove(R1);
                 break;
             case "txa":
-                gen.addMove(Register.R0);
+                gen.addMove(R0);
                 break;
             default:
                 throw new IllegalArgumentException("Unhandled instruction: " + line);
diff --git a/tests/unit/src/android/net/apf/JumpTableTest.kt b/tests/unit/src/android/net/apf/JumpTableTest.kt
index 3858aac..2c48e38 100644
--- a/tests/unit/src/android/net/apf/JumpTableTest.kt
+++ b/tests/unit/src/android/net/apf/JumpTableTest.kt
@@ -16,6 +16,7 @@
 
 package android.net.apf
 
+import android.net.apf.BaseApfGenerator.Register.R0
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
 import com.android.testutils.assertThrows
@@ -34,7 +35,7 @@
 class JumpTableTest {
 
     @Mock
-    lateinit var gen: ApfV4Generator
+    lateinit var gen: ApfV4Generator<ApfV4Generator<BaseApfGenerator>>
 
     @Before
     fun setUp() {
@@ -94,7 +95,7 @@
         j.generate(gen)
 
         inOrder.verify(gen).defineLabel(name)
-        inOrder.verify(gen).addLoadFromMemory(ApfV4Generator.Register.R0, slot)
+        inOrder.verify(gen).addLoadFromMemory(R0, slot)
         inOrder.verify(gen).addJumpIfR0Equals(0, "foo")
         inOrder.verify(gen).addJumpIfR0Equals(1, "bar")
         inOrder.verify(gen).addJumpIfR0Equals(2, "baz")
diff --git a/tests/unit/src/android/net/dhcp/DhcpServerTest.java b/tests/unit/src/android/net/dhcp/DhcpServerTest.java
index 6d4bc13..1853071 100644
--- a/tests/unit/src/android/net/dhcp/DhcpServerTest.java
+++ b/tests/unit/src/android/net/dhcp/DhcpServerTest.java
@@ -197,7 +197,8 @@
         when(mDeps.makeLeaseRepository(any(), any(), any())).thenReturn(mRepository);
         when(mDeps.makeClock()).thenReturn(mClock);
         when(mDeps.makePacketListener(any())).thenReturn(mPacketListener);
-        when(mDeps.isFeatureEnabled(eq(mContext), eq(DHCP_RAPID_COMMIT_VERSION))).thenReturn(true);
+        when(mDeps.isFeatureNotChickenedOut(eq(mContext), eq(DHCP_RAPID_COMMIT_VERSION)))
+                .thenReturn(true);
         doNothing().when(mDeps)
                 .sendPacket(any(), mSentPacketCaptor.capture(), mResponseDstAddrCaptor.capture());
         when(mClock.elapsedRealtime()).thenReturn(TEST_CLOCK_TIME);
@@ -253,7 +254,8 @@
     public void testDiscover_RapidCommit() throws Exception {
         startServer();
 
-        when(mDeps.isFeatureEnabled(eq(mContext), eq(DHCP_RAPID_COMMIT_VERSION))).thenReturn(true);
+        when(mDeps.isFeatureNotChickenedOut(eq(mContext), eq(DHCP_RAPID_COMMIT_VERSION)))
+                .thenReturn(true);
         when(mRepository.getCommittedLease(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
                 eq(INADDR_ANY) /* relayAddr */, isNull() /* hostname */)).thenReturn(TEST_LEASE);
 
diff --git a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
index f93a3bd..fbf311b 100644
--- a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
+++ b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
@@ -72,7 +72,9 @@
 import kotlin.test.fail
 import org.junit.After
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
+import org.junit.rules.TestName
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.any
@@ -186,6 +188,7 @@
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class IpReachabilityMonitorTest {
+    @get:Rule val mTestName = TestName()
     private val callback = mock(IpReachabilityMonitor.Callback::class.java)
     private val dependencies = mock(IpReachabilityMonitor.Dependencies::class.java)
     private val log = mock(SharedLog::class.java)
@@ -272,6 +275,19 @@
         doReturn(true).`when`(dependencies).isFeatureNotChickenedOut(any(),
             eq(IP_REACHABILITY_ROUTER_MAC_CHANGE_FAILURE_ONLY_AFTER_ROAM_VERSION))
 
+        val ignoreOrganicNudFailureTestList = listOf(
+                "testLoseProvisioning_ignoreOrganicIpv4DnsLost",
+                "testLoseProvisioning_ignoreOrganicIpv6DnsLost",
+                "testLoseProvisioning_ignoreOrganicIpv4GatewayLost",
+                "testLoseProvisioning_ignoreOrganicIpv6GatewayLost")
+        // The experiment flag: IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION is read at
+        // the IpReachabilityMonitor constructor, so we have to set the value before initializing
+        // an IpReachabilityMonitor instance.
+        if (ignoreOrganicNudFailureTestList.contains(mTestName.methodName)) {
+            doReturn(true).`when`(dependencies).isFeatureEnabled(any(),
+                    eq(IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION))
+        }
+
         val monitorFuture = CompletableFuture<IpReachabilityMonitor>()
         // IpReachabilityMonitor needs to be started from the handler thread
         handler.post {
@@ -330,6 +346,8 @@
         neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV6_DNS, NUD_STALE))
 
         neighborMonitor.enqueuePacket(makeNewNeighMessage(lostNeighbor, NUD_FAILED))
+        handlerThread.waitForIdle(TEST_TIMEOUT_MS)
+
         if (expectedNotifyLost) {
             verify(callback, timeout(TEST_TIMEOUT_MS)).notifyLost(eq(lostNeighbor), anyString(),
                     eq(eventType))
@@ -411,32 +429,24 @@
 
     @Test
     fun testLoseProvisioning_ignoreOrganicIpv4DnsLost() {
-        doReturn(true).`when`(dependencies).isFeatureEnabled(any(),
-            eq(IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION))
         runLoseProvisioningTest(TEST_LINK_PROPERTIES, TEST_IPV4_DNS, NUD_ORGANIC_FAILED_CRITICAL,
                 false /* expectedNotifyLost */)
     }
 
     @Test
     fun testLoseProvisioning_ignoreOrganicIpv6DnsLost() {
-        doReturn(true).`when`(dependencies).isFeatureEnabled(any(),
-            eq(IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION))
         runLoseProvisioningTest(TEST_LINK_PROPERTIES, TEST_IPV6_DNS, NUD_ORGANIC_FAILED_CRITICAL,
                 false /* expectedNotifyLost */)
     }
 
     @Test
     fun testLoseProvisioning_ignoreOrganicIpv4GatewayLost() {
-        doReturn(true).`when`(dependencies).isFeatureEnabled(any(),
-            eq(IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION))
         runLoseProvisioningTest(TEST_LINK_PROPERTIES, TEST_IPV4_GATEWAY,
                 NUD_ORGANIC_FAILED_CRITICAL, false /* expectedNotifyLost */)
     }
 
     @Test
     fun testLoseProvisioning_ignoreOrganicIpv6GatewayLost() {
-        doReturn(true).`when`(dependencies).isFeatureEnabled(any(),
-            eq(IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION))
         runLoseProvisioningTest(TEST_LINK_PROPERTIES, TEST_IPV6_GATEWAY,
                 NUD_ORGANIC_FAILED_CRITICAL, false /* expectedNotifyLost */)
     }
diff --git a/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java b/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java
index 29ddf21..11bf5a0 100644
--- a/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java
+++ b/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java
@@ -23,6 +23,9 @@
 import static android.net.ip.IIpClient.PROV_IPV6_DISABLED;
 import static android.net.ip.IIpClient.PROV_IPV6_LINKLOCAL;
 import static android.net.ip.IIpClient.PROV_IPV6_SLAAC;
+import static android.net.ip.IIpClient.HOSTNAME_SETTING_UNSET;
+import static android.net.ip.IIpClient.HOSTNAME_SETTING_DO_NOT_SEND;
+import static android.net.ip.IIpClient.HOSTNAME_SETTING_SEND;
 import static android.net.shared.ProvisioningConfiguration.fromStableParcelable;
 import static android.net.shared.ProvisioningConfiguration.ipv4ProvisioningModeToString;
 import static android.net.shared.ProvisioningConfiguration.ipv6ProvisioningModeToString;
@@ -113,6 +116,7 @@
         config.mIPv6ProvisioningMode = PROV_IPV6_SLAAC;
         config.mUniqueEui64AddressesOnly = false;
         config.mCreatorUid = 10136;
+        config.mHostnameSetting = HOSTNAME_SETTING_SEND;
         return config;
     }
 
@@ -144,6 +148,7 @@
         p.layer2Info = layer2Info.toStableParcelable();
         p.options = makeCustomizedDhcpOptions((byte) 60, new String("android-dhcp-11").getBytes());
         p.creatorUid = 10136;
+        p.hostnameSetting = HOSTNAME_SETTING_SEND;
         return p;
     }
 
@@ -151,7 +156,7 @@
     public void setUp() {
         mConfig = makeTestProvisioningConfiguration();
         // Any added field must be included in equals() to be tested properly
-        assertFieldCountEquals(18, ProvisioningConfiguration.class);
+        assertFieldCountEquals(19, ProvisioningConfiguration.class);
     }
 
     @Test
@@ -284,7 +289,9 @@
         assertNotEqualsAfterChange(c -> c.mIPv6ProvisioningMode = PROV_IPV6_LINKLOCAL);
         assertNotEqualsAfterChange(c -> c.mUniqueEui64AddressesOnly = true);
         assertNotEqualsAfterChange(c -> c.mCreatorUid = 10138);
-        assertFieldCountEquals(18, ProvisioningConfiguration.class);
+        assertNotEqualsAfterChange(c -> c.mHostnameSetting = HOSTNAME_SETTING_UNSET);
+        assertNotEqualsAfterChange(c -> c.mHostnameSetting = HOSTNAME_SETTING_DO_NOT_SEND);
+        assertFieldCountEquals(19, ProvisioningConfiguration.class);
     }
 
     private void assertNotEqualsAfterChange(Consumer<ProvisioningConfiguration> mutator) {
diff --git a/tests/unit/src/com/android/networkstack/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/unit/src/com/android/networkstack/ipmemorystore/IpMemoryStoreServiceTest.java
index 37bd808..d192425 100644
--- a/tests/unit/src/com/android/networkstack/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/tests/unit/src/com/android/networkstack/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -937,4 +937,43 @@
             assertEquals(testCase.first, Utils.byteArrayToString(testCase.second));
         }
     }
+
+    @Test
+    public void testNullDb() throws Exception {
+        // Init IpMemoryStoreService with a file that can't be opened
+        final File file = new File("/", TEST_DATABASE_NAME);
+        doReturn(file).when(mMockContext).getDatabasePath(anyString());
+        final IpMemoryStoreService ipMemoryStoreService =
+                new IpMemoryStoreService(mMockContext);
+
+        //test delete, no NullPointerException, got expected status and deleting count
+        doLatched("Did not get fail callback", latch ->
+                ipMemoryStoreService.delete("key", false /* needWipe */,
+                        onDeleteStatus((status, deleted) -> {
+                            assertEquals("Unexpected status: ",
+                                    Status.ERROR_DATABASE_CANNOT_BE_OPENED,
+                                    status.resultCode);
+                            assertEquals("Deleting count != 1 :" +
+                                    deleted, 0, deleted.intValue());
+                            latch.countDown();
+                        })), LONG_TIMEOUT_MS);
+
+        //Test deleteCluster, no NullPointerException, got expected status and deletedCount
+        doLatched("Did not get fail callback", latch ->
+                ipMemoryStoreService.deleteCluster("key", false /* needWipe */,
+                        onDeleteStatus((status, deletedCount) -> {
+                            assertEquals("Unexpected status: ",
+                                    Status.ERROR_DATABASE_CANNOT_BE_OPENED,
+                                    status.resultCode);
+                            assertEquals("Unexpected deleted count : ",
+                                    0, deletedCount.intValue());
+                            latch.countDown();
+                        })), LONG_TIMEOUT_MS);
+
+        // Try to wipe all data in tables, no NullPointerException
+        ipMemoryStoreService.factoryReset();
+
+        //db is null, the db size could not over the threshold.
+        assertFalse(ipMemoryStoreService.isDbSizeOverThreshold());
+    }
 }
diff --git a/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java b/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java
index 3857b04..4944812 100644
--- a/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java
+++ b/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java
@@ -332,15 +332,17 @@
         assertFalse(tst.isDataStallSuspected());
     }
 
-    @IgnoreAfter(Build.VERSION_CODES.S_V2)
+    // b/326143935 isUidNetworkingBlocked is not supported on pre-U device.
+    @IgnoreAfter(Build.VERSION_CODES.TIRAMISU)
     @Test
-    public void testPollSocketsInfo_ignoreBlockedUid_featureDisabled_beforeT() throws Exception {
+    public void testPollSocketsInfo_ignoreBlockedUid_featureDisabled_beforeU() throws Exception {
         doTestPollSocketsInfo_ignoreBlockedUid_featureDisabled();
     }
 
-    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    // b/326143935 isUidNetworkingBlocked is not supported on pre-U device.
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
     @Test
-    public void testPollSocketsInfo_ignoreBlockedUid_featureDisabled_TOrAbove() throws Exception {
+    public void testPollSocketsInfo_ignoreBlockedUid_featureDisabled_UOrAbove() throws Exception {
         doTestPollSocketsInfo_ignoreBlockedUid_featureDisabled();
         verify(mCm, never()).isUidNetworkingBlocked(anyInt(), anyBoolean());
     }
@@ -371,8 +373,8 @@
         assertTrue(tst.isDataStallSuspected());
     }
 
-    // The feature is not enabled on pre-T device, because it needs bpf support.
-    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    // b/326143935 isUidNetworkingBlocked is not supported on pre-U device.
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
     @Test
     public void testPollSocketsInfo_ignoreBlockedUid_featureEnabled() throws Exception {
         doReturn(true).when(mDependencies).shouldIgnoreTcpInfoForBlockedUids();
@@ -403,8 +405,8 @@
         assertTrue(tst.isDataStallSuspected());
     }
 
-    // The feature is not enabled on pre-T device, because it needs bpf support.
-    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    // b/326143935 isUidNetworkingBlocked is not supported on pre-U device.
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
     @Test
     public void testPollSocketsInfo_ignoreBlockedUid_featureEnabled_dataSaver() throws Exception {
         doReturn(true).when(mDependencies).shouldIgnoreTcpInfoForBlockedUids();
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
index 43bee55..d6e9c8e 100644
--- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -154,8 +154,8 @@
 import android.util.ArrayMap;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.SharedLog;
 import com.android.networkstack.NetworkStackNotifier;
@@ -180,6 +180,8 @@
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.FunctionalUtils.ThrowingConsumer;
 import com.android.testutils.HandlerUtils;
 
 import com.google.protobuf.nano.MessageNano;
@@ -194,6 +196,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
 import org.mockito.invocation.InvocationOnMock;
@@ -223,12 +226,15 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.function.Supplier;
 
 import javax.net.ssl.SSLHandshakeException;
 
-@RunWith(AndroidJUnit4.class)
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
 @SuppressLint("NewApi")  // Uses hidden APIs, which the linter would identify as missing APIs.
 public class NetworkMonitorTest {
@@ -272,8 +278,10 @@
     private @Mock TcpSocketTracker.Dependencies mTstDependencies;
     private @Mock INetd mNetd;
     private @Mock TcpSocketTracker mTst;
-    private HashSet<WrappedNetworkMonitor> mCreatedNetworkMonitors;
-    private HashSet<BroadcastReceiver> mRegisteredReceivers;
+    @GuardedBy("mCreatedNetworkMonitors")
+    private final HashSet<WrappedNetworkMonitor> mCreatedNetworkMonitors = new HashSet<>();
+    @GuardedBy("mRegisteredReceivers")
+    private final HashSet<BroadcastReceiver> mRegisteredReceivers = new HashSet<>();
     private @Mock Context mMccContext;
     private @Mock Resources mMccResource;
     private @Mock WifiInfo mWifiInfo;
@@ -321,6 +329,7 @@
     private static final NetworkAgentConfigShim TEST_AGENT_CONFIG =
             NetworkAgentConfigShimImpl.newInstance(null);
     private static final LinkProperties TEST_LINK_PROPERTIES = new LinkProperties();
+    private static final int THREAD_QUIT_MAX_RETRY_COUNT = 3;
 
     // Cannot have a static member for the LinkProperties with captive portal API information, as
     // the initializer would crash on Q (the members in LinkProperties were introduced in R).
@@ -564,6 +573,11 @@
 
     private FakeDns mFakeDns;
 
+    @GuardedBy("mThreadsToBeCleared")
+    private final ArrayList<Thread> mThreadsToBeCleared = new ArrayList<>();
+    @GuardedBy("mExecutorServiceToBeCleared")
+    private final ArrayList<ExecutorService> mExecutorServiceToBeCleared = new ArrayList<>();
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -578,6 +592,18 @@
                 .getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTP_URL), any());
         doReturn(TEST_HTTPS_URL).when(mDependencies)
                 .getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTPS_URL), any());
+        doAnswer((invocation) -> {
+            synchronized (mThreadsToBeCleared) {
+                mThreadsToBeCleared.add(invocation.getArgument(0));
+            }
+            return null;
+        }).when(mDependencies).onThreadCreated(any());
+        doAnswer((invocation) -> {
+            synchronized (mExecutorServiceToBeCleared) {
+                mExecutorServiceToBeCleared.add(invocation.getArgument(0));
+            }
+            return null;
+        }).when(mDependencies).onExecutorServiceCreated(any());
 
         doReturn(mCleartextDnsNetwork).when(mNetwork).getPrivateDnsBypassingCopy();
 
@@ -657,16 +683,22 @@
         mFakeDns.setAnswer(PRIVATE_DNS_PROBE_HOST_SUFFIX, new String[]{"2001:db8::1"}, TYPE_AAAA);
 
         doAnswer((invocation) -> {
-            mRegisteredReceivers.add(invocation.getArgument(0));
+            synchronized (mRegisteredReceivers) {
+                mRegisteredReceivers.add(invocation.getArgument(0));
+            }
             return new Intent();
         }).when(mContext).registerReceiver(any(BroadcastReceiver.class), any());
         doAnswer((invocation) -> {
-            mRegisteredReceivers.add(invocation.getArgument(0));
+            synchronized (mRegisteredReceivers) {
+                mRegisteredReceivers.add(invocation.getArgument(0));
+            }
             return new Intent();
         }).when(mContext).registerReceiver(any(BroadcastReceiver.class), any(), anyInt());
 
         doAnswer((invocation) -> {
-            mRegisteredReceivers.remove(invocation.getArgument(0));
+            synchronized (mRegisteredReceivers) {
+                mRegisteredReceivers.remove(invocation.getArgument(0));
+            }
             return null;
         }).when(mContext).unregisterReceiver(any());
 
@@ -676,25 +708,79 @@
         setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS);
         setValidDataStallDnsTimeThreshold(TEST_MIN_VALID_STALL_DNS_TIME_THRESHOLD_MS);
         setConsecutiveDnsTimeoutThreshold(5);
-        mCreatedNetworkMonitors = new HashSet<>();
-        mRegisteredReceivers = new HashSet<>();
+    }
+
+    private static <T> void quitResourcesThat(Supplier<List<T>> supplier,
+            ThrowingConsumer terminator) throws Exception {
+        // Run it multiple times since new threads might be generated in a thread
+        // that is about to be terminated, e.g. each thread that runs
+        // isCaptivePortal could generate 2 more probing threads.
+        for (int retryCount = 0; retryCount < THREAD_QUIT_MAX_RETRY_COUNT; retryCount++) {
+            final List<T> resourcesToBeCleared = supplier.get();
+            if (resourcesToBeCleared.isEmpty()) return;
+            for (final T resource : resourcesToBeCleared) {
+                terminator.accept(resource);
+            }
+        }
+
+        assertEquals(Collections.emptyList(), supplier.get());
+    }
+
+    private void quitNetworkMonitors() throws Exception {
+        quitResourcesThat(() -> {
+            synchronized (mCreatedNetworkMonitors) {
+                final ArrayList<WrappedNetworkMonitor> ret =
+                        new ArrayList<>(mCreatedNetworkMonitors);
+                mCreatedNetworkMonitors.clear();
+                return ret;
+            }
+        }, (it) -> {
+            final WrappedNetworkMonitor nm = (WrappedNetworkMonitor) it;
+            nm.notifyNetworkDisconnected();
+            nm.awaitQuit();
+        });
+        synchronized (mRegisteredReceivers) {
+            assertEquals("BroadcastReceiver still registered after disconnect",
+                    0, mRegisteredReceivers.size());
+        }
+        quitThreads();
+        quitExecutorServices();
+    }
+
+    private void quitExecutorServices() throws Exception {
+        quitResourcesThat(() -> {
+            synchronized (mExecutorServiceToBeCleared) {
+                final ArrayList<ExecutorService> ret = new ArrayList<>(mExecutorServiceToBeCleared);
+                mExecutorServiceToBeCleared.clear();
+                return ret;
+            }
+        }, (it) -> {
+            final ExecutorService ecs = (ExecutorService) it;
+            ecs.awaitTermination(HANDLER_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        });
+    }
+
+    private void quitThreads() throws Exception {
+        quitResourcesThat(() -> {
+            synchronized (mThreadsToBeCleared) {
+                final ArrayList<Thread> ret = new ArrayList<>(mThreadsToBeCleared);
+                mThreadsToBeCleared.clear();
+                return ret;
+            }
+        }, (it) -> {
+            final Thread th = (Thread) it;
+            th.interrupt();
+            th.join(HANDLER_TIMEOUT_MS);
+            if (th.isAlive()) fail("Threads did not terminate within timeout.");
+        });
     }
 
     @After
-    public void tearDown() {
+    public void tearDown() throws Exception {
         mFakeDns.clearAll();
-        // Make a local copy of mCreatedNetworkMonitors because during the iteration below,
-        // WrappedNetworkMonitor#onQuitting will delete elements from it on the handler threads.
-        WrappedNetworkMonitor[] networkMonitors = mCreatedNetworkMonitors.toArray(
-                new WrappedNetworkMonitor[0]);
-        for (WrappedNetworkMonitor nm : networkMonitors) {
-            nm.notifyNetworkDisconnected();
-            nm.awaitQuit();
-        }
-        assertEquals("NetworkMonitor still running after disconnect",
-                0, mCreatedNetworkMonitors.size());
-        assertEquals("BroadcastReceiver still registered after disconnect",
-                0, mRegisteredReceivers.size());
+        quitNetworkMonitors();
+        // Clear mocks to prevent from stubs holding instances and cause memory leaks.
+        Mockito.framework().clearInlineMocks();
     }
 
     private void initHttpConnection(HttpURLConnection connection) {
@@ -777,7 +863,6 @@
         @Override
         protected void onQuitting() {
             super.onQuitting();
-            assertTrue(mCreatedNetworkMonitors.remove(this));
             mQuitCv.open();
         }
 
@@ -1099,7 +1184,9 @@
         verify(mContext, never()).registerReceiver(receiverCaptor.capture(),
                 argThat(receiver -> ACTION_CONFIGURATION_CHANGED.equals(receiver.getAction(0))));
         nm.start();
-        mCreatedNetworkMonitors.add(nm);
+        synchronized (mCreatedNetworkMonitors) {
+            mCreatedNetworkMonitors.add(nm);
+        }
         HandlerUtils.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
         verify(mContext, times(1)).registerReceiver(receiverCaptor.capture(),
                 argThat(receiver -> ACTION_CONFIGURATION_CHANGED.equals(receiver.getAction(0))));
@@ -3701,6 +3788,8 @@
         // started. If captive portal app receiver is registered, then the size of the registered
         // receivers will be 2. Otherwise, mRegisteredReceivers should only contain 1 configuration
         // change receiver.
-        assertEquals(isPortal ? 2 : 1, mRegisteredReceivers.size());
+        synchronized (mRegisteredReceivers) {
+            assertEquals(isPortal ? 2 : 1, mRegisteredReceivers.size());
+        }
     }
 }