Snap for 8426163 from e4b5a883015cd7dd8da5d9603498cde4c6db018d to mainline-tzdata2-release

Change-Id: I80f044bf8d3d9009a9b7ad13f0f03f7aefa7fbdb
diff --git a/Android.bp b/Android.bp
index 0838a8d..01a8e77 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,27 +1,6 @@
-package {
-    default_applicable_licenses: ["system_netd_license"],
-}
-
-// Added automatically by a large-scale-change
-// See: http://go/android-license-faq
-license {
-    name: "system_netd_license",
-    visibility: [":__subpackages__"],
-    license_kinds: [
-        "SPDX-license-identifier-Apache-2.0",
-    ],
-    license_text: [
-        "NOTICE",
-    ],
-}
-
 cc_library_headers {
     name: "libnetd_client_headers",
     export_include_dirs: ["include"],
-    apex_available: [
-        "//apex_available:platform",
-        "com.android.tethering",
-    ],
 }
 
 cc_library_headers {
@@ -59,22 +38,12 @@
         "google-*",
         "misc-*",
         "performance-*",
-        "-bugprone-macro-parentheses",
         "-bugprone-narrowing-conversions",  // lots of unsigned -> int conversions
-        "-bugprone-unhandled-self-assignment", // found in DnsResolver/stats.pb.h
-        "-cert-dcl50-cpp",
         "-cert-err34-c",  // TODO: re-enable after removing atoi() and sscanf() calls
-        "-cert-oop54-cpp", // found in DnsResolver/stats.pb.h
-        "-google-default-arguments",
-        "-google-explicit-constructor",
-        "-google-global-names-in-headers",
         "-google-readability-*",  // Too pedantic
         "-google-runtime-int",  // Too many unavoidable warnings due to strtol()
         "-google-runtime-references",  // Grandfathered usage of pass by non-const reference
         "-misc-non-private-member-variables-in-classes",  // Also complains about structs
-        "-performance-noexcept-move-constructor",
-        "-performance-unnecessary-value-param",
-        "-performance-no-int-to-ptr",
     ],
     tidy_flags: [
         "-warnings-as-errors="
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/OWNERS b/OWNERS
index 94e70d5..7d617ec 100644
--- a/OWNERS
+++ b/OWNERS
@@ -8,4 +8,3 @@
 reminv@google.com
 satk@google.com
 xiaom@google.com
-yumike@google.com
\ No newline at end of file
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index 1311ca9..7b1f015 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -14,15 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "system_netd_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["system_netd_license"],
-}
-
 cc_library_headers {
     name: "netd_bpf_progs_headers",
     export_include_dirs: ["."],
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index 31e0522..e758692 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -37,7 +37,7 @@
 // From kernel:include/net/ip.h
 #define IP_DF 0x4000  // Flag: "Don't Fragment"
 
-DEFINE_BPF_MAP(clat_ingress6_map, HASH, ClatIngress6Key, ClatIngress6Value, 16)
+DEFINE_BPF_MAP(clat_ingress_map, HASH, ClatIngressKey, ClatIngressValue, 16)
 
 static inline __always_inline int nat64(struct __sk_buff* skb, bool is_ethernet) {
     const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
@@ -46,9 +46,6 @@
     const struct ethhdr* const eth = is_ethernet ? data : NULL;  // used iff is_ethernet
     const struct ipv6hdr* const ip6 = is_ethernet ? (void*)(eth + 1) : data;
 
-    // Require ethernet dst mac address to be our unicast address.
-    if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_OK;
-
     // Must be meta-ethernet IPv6 frame
     if (skb->protocol != htons(ETH_P_IPV6)) return TC_ACT_OK;
 
@@ -75,7 +72,7 @@
             return TC_ACT_OK;
     }
 
-    ClatIngress6Key k = {
+    ClatIngressKey k = {
             .iif = skb->ifindex,
             .pfx96.in6_u.u6_addr32 =
                     {
@@ -86,7 +83,7 @@
             .local6 = ip6->daddr,
     };
 
-    ClatIngress6Value* v = bpf_clat_ingress6_map_lookup_elem(&k);
+    ClatIngressValue* v = bpf_clat_ingress_map_lookup_elem(&k);
 
     if (!v) return TC_ACT_OK;
 
@@ -179,25 +176,25 @@
     return TC_ACT_OK;
 }
 
-DEFINE_BPF_PROG("schedcls/ingress6/clat_ether", AID_ROOT, AID_ROOT, sched_cls_ingress6_clat_ether)
-(struct __sk_buff* skb) {
+SEC("schedcls/ingress/clat_ether")
+int sched_cls_ingress_clat_ether(struct __sk_buff* skb) {
     return nat64(skb, true);
 }
 
-DEFINE_BPF_PROG("schedcls/ingress6/clat_rawip", AID_ROOT, AID_ROOT, sched_cls_ingress6_clat_rawip)
-(struct __sk_buff* skb) {
+SEC("schedcls/ingress/clat_rawip")
+int sched_cls_ingress_clat_rawip(struct __sk_buff* skb) {
     return nat64(skb, false);
 }
 
-DEFINE_BPF_MAP(clat_egress4_map, HASH, ClatEgress4Key, ClatEgress4Value, 16)
+DEFINE_BPF_MAP(clat_egress_map, HASH, ClatEgressKey, ClatEgressValue, 16)
 
-DEFINE_BPF_PROG("schedcls/egress4/clat_ether", AID_ROOT, AID_ROOT, sched_cls_egress4_clat_ether)
-(struct __sk_buff* skb) {
+SEC("schedcls/egress/clat_ether")
+int sched_cls_egress_clat_ether(struct __sk_buff* skb) {
     return TC_ACT_OK;
 }
 
-DEFINE_BPF_PROG("schedcls/egress4/clat_rawip", AID_ROOT, AID_ROOT, sched_cls_egress4_clat_rawip)
-(struct __sk_buff* skb) {
+SEC("schedcls/egress/clat_rawip")
+int sched_cls_egress_clat_rawip(struct __sk_buff* skb) {
     void* data = (void*)(long)skb->data;
     const void* data_end = (void*)(long)skb->data_end;
     const struct iphdr* const ip4 = data;
@@ -251,12 +248,12 @@
             return TC_ACT_OK;
     }
 
-    ClatEgress4Key k = {
+    ClatEgressKey k = {
             .iif = skb->ifindex,
             .local4.s_addr = ip4->saddr,
     };
 
-    ClatEgress4Value* v = bpf_clat_egress4_map_lookup_elem(&k);
+    ClatEgressValue* v = bpf_clat_egress_map_lookup_elem(&k);
 
     if (!v) return TC_ACT_OK;
 
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index e9e1477..f347028 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -203,12 +203,9 @@
         if ((enabledRules & POWERSAVE_MATCH) && !(uidRules & POWERSAVE_MATCH)) {
             return BPF_DROP;
         }
-        if ((enabledRules & RESTRICTED_MATCH) && !(uidRules & RESTRICTED_MATCH)) {
-            return BPF_DROP;
-        }
     }
     if (direction == BPF_INGRESS && (uidRules & IIF_MATCH)) {
-        // Drops packets not coming from lo nor the allowlisted interface
+        // Drops packets not coming from lo nor the whitelisted interface
         if (allowed_iif && skb->ifindex != 1 && skb->ifindex != allowed_iif) {
             return BPF_DROP_UNLESS_DNS;
         }
@@ -283,13 +280,13 @@
     return match;
 }
 
-DEFINE_BPF_PROG("cgroupskb/ingress/stats", AID_ROOT, AID_ROOT, bpf_cgroup_ingress)
-(struct __sk_buff* skb) {
+SEC("cgroupskb/ingress/stats")
+int bpf_cgroup_ingress(struct __sk_buff* skb) {
     return bpf_traffic_account(skb, BPF_INGRESS);
 }
 
-DEFINE_BPF_PROG("cgroupskb/egress/stats", AID_ROOT, AID_ROOT, bpf_cgroup_egress)
-(struct __sk_buff* skb) {
+SEC("cgroupskb/egress/stats")
+int bpf_cgroup_egress(struct __sk_buff* skb) {
     return bpf_traffic_account(skb, BPF_EGRESS);
 }
 
@@ -318,7 +315,7 @@
     return BPF_MATCH;
 }
 
-DEFINE_BPF_PROG("skfilter/allowlist/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_allowlist_prog)
+DEFINE_BPF_PROG("skfilter/whitelist/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_whitelist_prog)
 (struct __sk_buff* skb) {
     uint32_t sock_uid = bpf_get_socket_uid(skb);
     if (is_system_uid(sock_uid)) return BPF_MATCH;
@@ -330,16 +327,16 @@
     if ((sock_uid == 65534) && !bpf_get_socket_cookie(skb) && is_received_skb(skb))
         return BPF_MATCH;
 
-    UidOwnerValue* allowlistMatch = bpf_uid_owner_map_lookup_elem(&sock_uid);
-    if (allowlistMatch) return allowlistMatch->rule & HAPPY_BOX_MATCH ? BPF_MATCH : BPF_NOMATCH;
+    UidOwnerValue* whitelistMatch = bpf_uid_owner_map_lookup_elem(&sock_uid);
+    if (whitelistMatch) return whitelistMatch->rule & HAPPY_BOX_MATCH ? BPF_MATCH : BPF_NOMATCH;
     return BPF_NOMATCH;
 }
 
-DEFINE_BPF_PROG("skfilter/denylist/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_denylist_prog)
+DEFINE_BPF_PROG("skfilter/blacklist/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_blacklist_prog)
 (struct __sk_buff* skb) {
     uint32_t sock_uid = bpf_get_socket_uid(skb);
-    UidOwnerValue* denylistMatch = bpf_uid_owner_map_lookup_elem(&sock_uid);
-    if (denylistMatch) return denylistMatch->rule & PENALTY_BOX_MATCH ? BPF_MATCH : BPF_NOMATCH;
+    UidOwnerValue* blacklistMatch = bpf_uid_owner_map_lookup_elem(&sock_uid);
+    if (blacklistMatch) return blacklistMatch->rule & PENALTY_BOX_MATCH ? BPF_MATCH : BPF_NOMATCH;
     return BPF_NOMATCH;
 }
 
diff --git a/client/Android.bp b/client/Android.bp
index b8eb56c..53afc97 100644
--- a/client/Android.bp
+++ b/client/Android.bp
@@ -12,15 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "system_netd_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["system_netd_license"],
-}
-
 cc_library {
     name: "libnetd_client",
     srcs: [
@@ -31,6 +22,7 @@
         "dnsproxyd_protocol_headers", // NETID_USE_LOCAL_NAMESERVERS
         "libnetd_client_headers",
         "libbase_headers", // for unique_fd.h
+        "libnetd_resolv_headers",
     ],
     export_header_lib_headers: ["libnetd_client_headers"],
     include_dirs: [
@@ -40,10 +32,6 @@
     sanitize: {
         cfi: true,
     },
-    apex_available: [
-        "//apex_available:platform",
-        "com.android.tethering",
-    ],
 }
 
 cc_test {
@@ -56,13 +44,16 @@
     include_dirs: [
         "system/netd/include",
     ],
+    header_libs: [
+        "libnetd_resolv_headers",
+    ],
     static_libs: [
         "libgmock",
         "libbase",
         "libnetd_client",
     ],
     sanitize: {
-        address: false,
+        address: true,
         recover: [ "all" ],
     },
 }
diff --git a/include/binder_utils/BinderUtil.h b/include/binder_utils/BinderUtil.h
index 469fa68..6b41465 100644
--- a/include/binder_utils/BinderUtil.h
+++ b/include/binder_utils/BinderUtil.h
@@ -16,8 +16,9 @@
 
 #pragma once
 
-#include <android-base/strings.h>
-#include <fmt/format.h>
+#include <android-base/stringprintf.h>
+#include <json/value.h>
+#include <json/writer.h>
 
 #ifdef ANDROID_BINDER_STATUS_H
 #define IS_BINDER_OK(__ex__) (__ex__ == ::android::binder::Status::EX_NONE)
@@ -58,26 +59,31 @@
 
 using LogFn = std::function<void(const std::string& msg)>;
 
-template <typename LogType>
-void binderCallLogFn(const LogType& log, const LogFn& logFn) {
+void binderCallLogFn(const Json::Value& logTransaction, const LogFn& logFn) {
     using namespace std::string_literals;
 
     bool hasReturnArgs;
     std::string output;
+    const Json::Value& returnArgs = logTransaction["_aidl_return"];
+    const Json::Value& inputArgsArray = logTransaction["input_args"];
 
-    hasReturnArgs = !log.result.empty();
-    output.append(log.method_name + "("s);
+    hasReturnArgs = !returnArgs.empty();
+    output.append(logTransaction["method_name"].asString() + "("s);
 
     // input args
-    for (size_t i = 0; i < log.input_args.size(); ++i) {
-        output.append(log.input_args[i].second);
-        if (i != log.input_args.size() - 1) {
+    Json::FastWriter fastWriter;
+    fastWriter.omitEndingLineFeed();
+    for (Json::Value::ArrayIndex i = 0; i < inputArgsArray.size(); ++i) {
+        std::string value = fastWriter.write(inputArgsArray[i]["value"]);
+        output.append(value);
+        if (i != inputArgsArray.size() - 1) {
             output.append(", "s);
         }
     }
     output.append(")"s);
 
-    const int exceptionCode = TO_EXCEPTION(log.exception_code);
+    const int exceptionCode =
+            TO_EXCEPTION(logTransaction["binder_status"]["exception_code"].asInt());
 
     if (hasReturnArgs || !IS_BINDER_OK(exceptionCode)) {
         output.append(" -> "s);
@@ -86,17 +92,18 @@
     // return status
     if (!IS_BINDER_OK(exceptionCode)) {
         // an exception occurred
-        const int errCode = log.service_specific_error_code;
-        output.append(fmt::format("{}({}, \"{}\")", exceptionToString(exceptionCode),
-                                  (errCode != 0) ? errCode : exceptionCode, log.exception_message));
+        const int errCode = logTransaction["binder_status"]["service_specific_error_code"].asInt();
+        output.append(::android::base::StringPrintf(
+                "%s(%d, \"%s\")", exceptionToString(exceptionCode).c_str(),
+                (errCode != 0) ? errCode : exceptionCode,
+                logTransaction["binder_status"]["exception_message"].asString().c_str()));
     }
     // return args
     if (hasReturnArgs) {
-        output.append("{" + log.result + "}");
+        output.append(::android::base::StringPrintf("{%s}", fastWriter.write(returnArgs).c_str()));
     }
     // duration time
-    output.append(fmt::format(" <{:.2f}ms>", log.duration_ms));
-
-    // escape newline characters to avoid multiline log entries
-    logFn(::android::base::StringReplace(output, "\n", "\\n", true));
+    output.append(
+            ::android::base::StringPrintf(" <%.2fms>", logTransaction["duration_ms"].asFloat()));
+    logFn(output);
 }
diff --git a/libnetdbpf/Android.bp b/libnetdbpf/Android.bp
index 7c0207c..72e0fec 100644
--- a/libnetdbpf/Android.bp
+++ b/libnetdbpf/Android.bp
@@ -14,15 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "system_netd_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["system_netd_license"],
-}
-
 cc_library {
     name: "libnetdbpf",
     vendor_available: false,
diff --git a/libnetdbpf/BpfNetworkStatsTest.cpp b/libnetdbpf/BpfNetworkStatsTest.cpp
index fb8f0ec..a9581ce 100644
--- a/libnetdbpf/BpfNetworkStatsTest.cpp
+++ b/libnetdbpf/BpfNetworkStatsTest.cpp
@@ -78,6 +78,7 @@
     BpfMap<uint32_t, StatsValue> mFakeIfaceStatsMap;
 
     void SetUp() {
+        SKIP_IF_BPF_NOT_SUPPORTED;
         ASSERT_EQ(0, setrlimitForTest());
 
         mFakeCookieTagMap = BpfMap<uint64_t, UidTagValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
@@ -139,6 +140,8 @@
 // TEST to verify the behavior of bpf map when cocurrent deletion happens when
 // iterating the same map.
 TEST_F(BpfNetworkStatsHelperTest, TestIterateMapWithDeletion) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     for (int i = 0; i < 5; i++) {
         uint64_t cookie = i + 1;
         UidTagValue tag = {.uid = TEST_UID1, .tag = TEST_TAG};
@@ -168,6 +171,8 @@
 }
 
 TEST_F(BpfNetworkStatsHelperTest, TestBpfIterateMap) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     for (int i = 0; i < 5; i++) {
         uint64_t cookie = i + 1;
         UidTagValue tag = {.uid = TEST_UID1, .tag = TEST_TAG};
@@ -188,6 +193,8 @@
 }
 
 TEST_F(BpfNetworkStatsHelperTest, TestUidStatsNoTraffic) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     StatsValue value1 = {
             .rxPackets = 0,
             .rxBytes = 0,
@@ -200,6 +207,8 @@
 }
 
 TEST_F(BpfNetworkStatsHelperTest, TestGetUidStatsTotal) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
     updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
     updateIfaceMap(IFACE_NAME3, IFACE_INDEX3);
@@ -240,6 +249,8 @@
 }
 
 TEST_F(BpfNetworkStatsHelperTest, TestGetIfaceStatsInternal) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
     updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
     updateIfaceMap(IFACE_NAME3, IFACE_INDEX3);
@@ -283,6 +294,8 @@
 }
 
 TEST_F(BpfNetworkStatsHelperTest, TestGetStatsDetail) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
     updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
     StatsValue value1 = {
@@ -318,6 +331,8 @@
 }
 
 TEST_F(BpfNetworkStatsHelperTest, TestGetStatsWithSkippedIface) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
     updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
     StatsValue value1 = {
@@ -353,6 +368,8 @@
 }
 
 TEST_F(BpfNetworkStatsHelperTest, TestUnkownIfaceError) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
     StatsValue value1 = {
             .rxPackets = TEST_PACKET0,
@@ -395,6 +412,8 @@
 }
 
 TEST_F(BpfNetworkStatsHelperTest, TestGetIfaceStatsDetail) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
     updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
     updateIfaceMap(IFACE_NAME3, IFACE_INDEX3);
@@ -432,6 +451,8 @@
 }
 
 TEST_F(BpfNetworkStatsHelperTest, TestGetStatsSortedAndGrouped) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     // Create iface indexes with duplicate iface name.
     updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
     updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
@@ -522,6 +543,8 @@
 // Test to verify that subtract overflow will not be triggered by the compare function invoked from
 // sorting. See http:/b/119193941.
 TEST_F(BpfNetworkStatsHelperTest, TestGetStatsSortAndOverflow) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
 
     StatsValue value1 = {
diff --git a/libnetdbpf/include/netdbpf/bpf_shared.h b/libnetdbpf/include/netdbpf/bpf_shared.h
index 2fcb612..6f31879 100644
--- a/libnetdbpf/include/netdbpf/bpf_shared.h
+++ b/libnetdbpf/include/netdbpf/bpf_shared.h
@@ -14,26 +14,20 @@
  * limitations under the License.
  */
 
-#pragma once
+#ifndef NETDBPF_BPF_SHARED_H
+#define NETDBPF_BPF_SHARED_H
 
-#include <linux/if.h>
 #include <linux/if_ether.h>
 #include <linux/in.h>
 #include <linux/in6.h>
 #include <netdutils/UidConstants.h>
 
-// This header file is shared by eBPF kernel programs (C) and netd (C++) and
-// some of the maps are also accessed directly from Java mainline module code.
-//
-// Hence: explicitly pad all relevant structures and assert that their size
-// is the sum of the sizes of their fields.
-#define STRUCT_SIZE(name, size) _Static_assert(sizeof(name) == (size), "Incorrect struct size.")
+// This header file is shared by eBPF kernel programs and netd
 
 typedef struct {
     uint32_t uid;
     uint32_t tag;
 } UidTagValue;
-STRUCT_SIZE(UidTagValue, 2 * 4);  // 8
 
 typedef struct {
     uint32_t uid;
@@ -41,7 +35,6 @@
     uint32_t counterSet;
     uint32_t ifaceIndex;
 } StatsKey;
-STRUCT_SIZE(StatsKey, 4 * 4);  // 16
 
 typedef struct {
     uint64_t rxPackets;
@@ -49,12 +42,10 @@
     uint64_t txPackets;
     uint64_t txBytes;
 } StatsValue;
-STRUCT_SIZE(StatsValue, 4 * 8);  // 32
 
 typedef struct {
     char name[IFNAMSIZ];
 } IfaceValue;
-STRUCT_SIZE(IfaceValue, 16);
 
 typedef struct {
     uint64_t rxBytes;
@@ -98,26 +89,26 @@
 const int CONFIGURATION_MAP_SIZE = 2;
 const int UID_OWNER_MAP_SIZE = 2000;
 
-#define BPF_PATH "/sys/fs/bpf/"
+#define BPF_PATH "/sys/fs/bpf"
 
-#define BPF_EGRESS_PROG_PATH BPF_PATH "prog_netd_cgroupskb_egress_stats"
-#define BPF_INGRESS_PROG_PATH BPF_PATH "prog_netd_cgroupskb_ingress_stats"
-#define XT_BPF_INGRESS_PROG_PATH BPF_PATH "prog_netd_skfilter_ingress_xtbpf"
-#define XT_BPF_EGRESS_PROG_PATH BPF_PATH "prog_netd_skfilter_egress_xtbpf"
-#define XT_BPF_ALLOWLIST_PROG_PATH BPF_PATH "prog_netd_skfilter_allowlist_xtbpf"
-#define XT_BPF_DENYLIST_PROG_PATH BPF_PATH "prog_netd_skfilter_denylist_xtbpf"
-#define CGROUP_SOCKET_PROG_PATH BPF_PATH "prog_netd_cgroupsock_inet_create"
+#define BPF_EGRESS_PROG_PATH BPF_PATH "/prog_netd_cgroupskb_egress_stats"
+#define BPF_INGRESS_PROG_PATH BPF_PATH "/prog_netd_cgroupskb_ingress_stats"
+#define XT_BPF_INGRESS_PROG_PATH BPF_PATH "/prog_netd_skfilter_ingress_xtbpf"
+#define XT_BPF_EGRESS_PROG_PATH BPF_PATH "/prog_netd_skfilter_egress_xtbpf"
+#define XT_BPF_WHITELIST_PROG_PATH BPF_PATH "/prog_netd_skfilter_whitelist_xtbpf"
+#define XT_BPF_BLACKLIST_PROG_PATH BPF_PATH "/prog_netd_skfilter_blacklist_xtbpf"
+#define CGROUP_SOCKET_PROG_PATH BPF_PATH "/prog_netd_cgroupsock_inet_create"
 
-#define COOKIE_TAG_MAP_PATH BPF_PATH "map_netd_cookie_tag_map"
-#define UID_COUNTERSET_MAP_PATH BPF_PATH "map_netd_uid_counterset_map"
-#define APP_UID_STATS_MAP_PATH BPF_PATH "map_netd_app_uid_stats_map"
-#define STATS_MAP_A_PATH BPF_PATH "map_netd_stats_map_A"
-#define STATS_MAP_B_PATH BPF_PATH "map_netd_stats_map_B"
-#define IFACE_INDEX_NAME_MAP_PATH BPF_PATH "map_netd_iface_index_name_map"
-#define IFACE_STATS_MAP_PATH BPF_PATH "map_netd_iface_stats_map"
-#define CONFIGURATION_MAP_PATH BPF_PATH "map_netd_configuration_map"
-#define UID_OWNER_MAP_PATH BPF_PATH "map_netd_uid_owner_map"
-#define UID_PERMISSION_MAP_PATH BPF_PATH "map_netd_uid_permission_map"
+#define COOKIE_TAG_MAP_PATH BPF_PATH "/map_netd_cookie_tag_map"
+#define UID_COUNTERSET_MAP_PATH BPF_PATH "/map_netd_uid_counterset_map"
+#define APP_UID_STATS_MAP_PATH BPF_PATH "/map_netd_app_uid_stats_map"
+#define STATS_MAP_A_PATH BPF_PATH "/map_netd_stats_map_A"
+#define STATS_MAP_B_PATH BPF_PATH "/map_netd_stats_map_B"
+#define IFACE_INDEX_NAME_MAP_PATH BPF_PATH "/map_netd_iface_index_name_map"
+#define IFACE_STATS_MAP_PATH BPF_PATH "/map_netd_iface_stats_map"
+#define CONFIGURATION_MAP_PATH BPF_PATH "/map_netd_configuration_map"
+#define UID_OWNER_MAP_PATH BPF_PATH "/map_netd_uid_owner_map"
+#define UID_PERMISSION_MAP_PATH BPF_PATH "/map_netd_uid_permission_map"
 
 enum UidOwnerMatchType {
     NO_MATCH = 0,
@@ -126,8 +117,7 @@
     DOZABLE_MATCH = (1 << 2),
     STANDBY_MATCH = (1 << 3),
     POWERSAVE_MATCH = (1 << 4),
-    RESTRICTED_MATCH = (1 << 5),
-    IIF_MATCH = (1 << 6),
+    IIF_MATCH = (1 << 5),
 };
 
 enum BpfPermissionMatch {
@@ -151,55 +141,84 @@
     // Allowed interface index. Only applicable if IIF_MATCH is set in the rule bitmask above.
     uint32_t iif;
     // A bitmask of enum values in UidOwnerMatchType.
-    uint32_t rule;
+    uint8_t rule;
 } UidOwnerValue;
-STRUCT_SIZE(UidOwnerValue, 2 * 4);  // 8
 
 #define UID_RULES_CONFIGURATION_KEY 1
 #define CURRENT_STATS_MAP_CONFIGURATION_KEY 2
 
-#define CLAT_INGRESS6_PROG_RAWIP_NAME "prog_clatd_schedcls_ingress6_clat_rawip"
-#define CLAT_INGRESS6_PROG_ETHER_NAME "prog_clatd_schedcls_ingress6_clat_ether"
+#define CLAT_INGRESS_PROG_RAWIP_NAME "prog_clatd_schedcls_ingress_clat_rawip"
+#define CLAT_INGRESS_PROG_ETHER_NAME "prog_clatd_schedcls_ingress_clat_ether"
 
-#define CLAT_INGRESS6_PROG_RAWIP_PATH BPF_PATH CLAT_INGRESS6_PROG_RAWIP_NAME
-#define CLAT_INGRESS6_PROG_ETHER_PATH BPF_PATH CLAT_INGRESS6_PROG_ETHER_NAME
+#define CLAT_INGRESS_PROG_RAWIP_PATH BPF_PATH "/" CLAT_INGRESS_PROG_RAWIP_NAME
+#define CLAT_INGRESS_PROG_ETHER_PATH BPF_PATH "/" CLAT_INGRESS_PROG_ETHER_NAME
 
-#define CLAT_INGRESS6_MAP_PATH BPF_PATH "map_clatd_clat_ingress6_map"
+#define CLAT_INGRESS_MAP_PATH BPF_PATH "/map_clatd_clat_ingress_map"
 
 typedef struct {
     uint32_t iif;            // The input interface index
     struct in6_addr pfx96;   // The source /96 nat64 prefix, bottom 32 bits must be 0
     struct in6_addr local6;  // The full 128-bits of the destination IPv6 address
-} ClatIngress6Key;
-STRUCT_SIZE(ClatIngress6Key, 4 + 2 * 16);  // 36
+} ClatIngressKey;
 
 typedef struct {
     uint32_t oif;           // The output interface to redirect to (0 means don't redirect)
     struct in_addr local4;  // The destination IPv4 address
-} ClatIngress6Value;
-STRUCT_SIZE(ClatIngress6Value, 4 + 4);  // 8
+} ClatIngressValue;
 
-#define CLAT_EGRESS4_PROG_RAWIP_NAME "prog_clatd_schedcls_egress4_clat_rawip"
-#define CLAT_EGRESS4_PROG_ETHER_NAME "prog_clatd_schedcls_egress4_clat_ether"
+#define CLAT_EGRESS_PROG_RAWIP_NAME "prog_clatd_schedcls_egress_clat_rawip"
+#define CLAT_EGRESS_PROG_ETHER_NAME "prog_clatd_schedcls_egress_clat_ether"
 
-#define CLAT_EGRESS4_PROG_RAWIP_PATH BPF_PATH CLAT_EGRESS4_PROG_RAWIP_NAME
-#define CLAT_EGRESS4_PROG_ETHER_PATH BPF_PATH CLAT_EGRESS4_PROG_ETHER_NAME
+#define CLAT_EGRESS_PROG_RAWIP_PATH BPF_PATH "/" CLAT_EGRESS_PROG_RAWIP_NAME
+#define CLAT_EGRESS_PROG_ETHER_PATH BPF_PATH "/" CLAT_EGRESS_PROG_ETHER_NAME
 
-#define CLAT_EGRESS4_MAP_PATH BPF_PATH "map_clatd_clat_egress4_map"
+#define CLAT_EGRESS_MAP_PATH BPF_PATH "/map_clatd_clat_egress_map"
 
 typedef struct {
     uint32_t iif;           // The input interface index
     struct in_addr local4;  // The source IPv4 address
-} ClatEgress4Key;
-STRUCT_SIZE(ClatEgress4Key, 4 + 4);  // 8
+} ClatEgressKey;
 
 typedef struct {
     uint32_t oif;            // The output interface to redirect to
     struct in6_addr local6;  // The full 128-bits of the source IPv6 address
     struct in6_addr pfx96;   // The destination /96 nat64 prefix, bottom 32 bits must be 0
     bool oifIsEthernet;      // Whether the output interface requires ethernet header
-    uint8_t pad[3];
-} ClatEgress4Value;
-STRUCT_SIZE(ClatEgress4Value, 4 + 2 * 16 + 1 + 3);  // 40
+} ClatEgressValue;
 
-#undef STRUCT_SIZE
+#define TETHER_INGRESS_PROG_RAWIP_NAME "prog_offload_schedcls_ingress_tether_rawip"
+#define TETHER_INGRESS_PROG_ETHER_NAME "prog_offload_schedcls_ingress_tether_ether"
+
+#define TETHER_INGRESS_PROG_RAWIP_PATH BPF_PATH "/" TETHER_INGRESS_PROG_RAWIP_NAME
+#define TETHER_INGRESS_PROG_ETHER_PATH BPF_PATH "/" TETHER_INGRESS_PROG_ETHER_NAME
+
+#define TETHER_INGRESS_MAP_PATH BPF_PATH "/map_offload_tether_ingress_map"
+
+typedef struct {
+    uint32_t iif;            // The input interface index
+    struct in6_addr neigh6;  // The destination IPv6 address
+} TetherIngressKey;
+
+typedef struct {
+    uint32_t oif;  // The output interface to redirect to
+    // For now tethering offload only needs to support downstreams that use 6-byte MAC addresses,
+    // because all downstream types that are currently supported (WiFi, USB, Bluetooth and
+    // Ethernet) have 6-byte MAC addresses.
+    struct ethhdr macHeader;  // includes dst/src mac and ethertype
+    uint16_t pmtu;            // The maximum L3 output path/route mtu
+} TetherIngressValue;
+
+#define TETHER_STATS_MAP_PATH BPF_PATH "/map_offload_tether_stats_map"
+
+typedef struct {
+    uint64_t rxPackets;
+    uint64_t rxBytes;
+    uint64_t rxErrors;
+    uint64_t txPackets;
+    uint64_t txBytes;
+    uint64_t txErrors;
+} TetherStatsValue;
+
+#define TETHER_LIMIT_MAP_PATH BPF_PATH "/map_offload_tether_limit_map"
+
+#endif  // NETDBPF_BPF_SHARED_H
diff --git a/libnetdutils/Android.bp b/libnetdutils/Android.bp
index 31f2c53..00bdc74 100644
--- a/libnetdutils/Android.bp
+++ b/libnetdutils/Android.bp
@@ -1,12 +1,3 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "system_netd_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["system_netd_license"],
-}
-
 cc_library {
     name: "libnetdutils",
     srcs: [
@@ -53,6 +44,7 @@
         "InternetAddressesTest.cpp",
         "LogTest.cpp",
         "MemBlockTest.cpp",
+        "OperationLimiterTest.cpp",
         "SliceTest.cpp",
         "StatusTest.cpp",
         "SyscallsTest.cpp",
diff --git a/libnetdutils/OperationLimiterTest.cpp b/libnetdutils/OperationLimiterTest.cpp
new file mode 100644
index 0000000..8d72d75
--- /dev/null
+++ b/libnetdutils/OperationLimiterTest.cpp
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+#include "netdutils/OperationLimiter.h"
+
+#include <gtest/gtest-spi.h>
+
+namespace android {
+namespace netdutils {
+
+TEST(OperationLimiter, limits) {
+    OperationLimiter<int> limiter(3);
+
+    EXPECT_TRUE(limiter.start(42));
+    EXPECT_TRUE(limiter.start(42));
+    EXPECT_TRUE(limiter.start(42));
+
+    // Limit reached... calling any number of times should have no effect.
+    EXPECT_FALSE(limiter.start(42));
+    EXPECT_FALSE(limiter.start(42));
+    EXPECT_FALSE(limiter.start(42));
+
+    // Finishing a single operations is enough for starting a new one...
+    limiter.finish(42);
+    EXPECT_TRUE(limiter.start(42));
+
+    // ...but not two!
+    EXPECT_FALSE(limiter.start(42));
+
+    // Different ids should still have quota...
+    EXPECT_TRUE(limiter.start(666));
+    limiter.finish(666);
+
+    // Finish all pending operations
+    limiter.finish(42);
+    limiter.finish(42);
+    limiter.finish(42);
+}
+
+TEST(OperationLimiter, finishWithoutStart) {
+    OperationLimiter<int> limiter(1);
+
+    // Will output a LOG(FATAL_WITHOUT_ABORT), but we have no way to probe this.
+    limiter.finish(42);
+
+    // This will ensure that the finish() above didn't set a negative value.
+    EXPECT_TRUE(limiter.start(42));
+    EXPECT_FALSE(limiter.start(42));
+}
+
+TEST(OperationLimiter, destroyWithActiveOperations) {
+    // The death message doesn't seem to be captured on Android.
+    EXPECT_DEBUG_DEATH({
+        OperationLimiter<int> limiter(3);
+        limiter.start(42);
+    }, "" /* "active operations */);
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/libnetdutils/include/netdutils/OperationLimiter.h b/libnetdutils/include/netdutils/OperationLimiter.h
new file mode 100644
index 0000000..992a849
--- /dev/null
+++ b/libnetdutils/include/netdutils/OperationLimiter.h
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+
+#ifndef NETUTILS_OPERATIONLIMITER_H
+#define NETUTILS_OPERATIONLIMITER_H
+
+#include <mutex>
+#include <unordered_map>
+
+#include <android-base/logging.h>
+#include <android-base/thread_annotations.h>
+
+namespace android {
+namespace netdutils {
+
+// Tracks the number of operations in progress on behalf of a particular key or
+// ID, rejecting further attempts to start new operations after a configurable
+// limit has been reached.
+//
+// The intended usage pattern is:
+//     OperationLimiter<UserId> connections_per_user;
+//     ...
+//     // Before opening a new connection
+//     if (!limiter.start(user)) {
+//         return error;
+//     } else {
+//         // open the connection
+//         // ...do some work...
+//         // close the connection
+//         limiter.finish(user);
+//     }
+//
+// This class is thread-safe.
+template<typename KeyType>
+class OperationLimiter {
+public:
+    explicit OperationLimiter(int limit) : mLimitPerKey(limit) {}
+
+    ~OperationLimiter() {
+        DCHECK(mCounters.empty())
+                << "Destroying OperationLimiter with active operations";
+    }
+
+    // Returns false if |key| has reached the maximum number of concurrent
+    // operations, otherwise increments the counter and returns true.
+    //
+    // Note: each successful start(key) must be matched by exactly one call to
+    // finish(key).
+    bool start(KeyType key) EXCLUDES(mMutex) {
+        std::lock_guard lock(mMutex);
+        auto& cnt = mCounters[key];  // operator[] creates new entries as needed.
+        if (cnt >= mLimitPerKey) {
+            // Oh, no!
+            return false;
+        }
+        ++cnt;
+        return true;
+    }
+
+    // Decrements the number of operations in progress accounted to |key|.
+    // See usage notes on start().
+    void finish(KeyType key) EXCLUDES(mMutex) {
+        std::lock_guard lock(mMutex);
+        auto it = mCounters.find(key);
+        if (it == mCounters.end()) {
+            LOG(FATAL_WITHOUT_ABORT) << "Decremented non-existent counter for key=" << key;
+            return;
+        }
+        auto& cnt = it->second;
+        --cnt;
+        if (cnt <= 0) {
+            // Cleanup counters once they drop down to zero.
+            mCounters.erase(it);
+        }
+    }
+
+private:
+    // Protects access to the map below.
+    std::mutex mMutex;
+
+    // Tracks the number of outstanding queries by key.
+    std::unordered_map<KeyType, int> mCounters GUARDED_BY(mMutex);
+
+    // Maximum number of outstanding queries from a single key.
+    const int mLimitPerKey;
+};
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif // NETUTILS_OPERATIONLIMITER_H
diff --git a/netutils_wrappers/Android.bp b/netutils_wrappers/Android.bp
index fdf802f..af7d8c3 100644
--- a/netutils_wrappers/Android.bp
+++ b/netutils_wrappers/Android.bp
@@ -1,12 +1,3 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "system_netd_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["system_netd_license"],
-}
-
 cc_binary {
     name: "netutils-wrapper-1.0",
     defaults: ["netd_defaults"],
@@ -32,7 +23,6 @@
     ],
     sanitize: {
         cfi: true,
-        memtag_heap: true,
     },
 }
 
diff --git a/netutils_wrappers/NetUtilsWrapper-1.0.cpp b/netutils_wrappers/NetUtilsWrapper-1.0.cpp
index d81b0ec..cdc454e 100644
--- a/netutils_wrappers/NetUtilsWrapper-1.0.cpp
+++ b/netutils_wrappers/NetUtilsWrapper-1.0.cpp
@@ -73,6 +73,8 @@
     // Other activities observed on current devices. In future releases, these should be supported
     // in a way that is less likely to interfere with general Android networking behaviour.
     CMD "tc qdisc del dev root",
+    CMD "ip( -4| -6)? rule .* goto 13000 prio 11999",
+    CMD "ip( -4| -6)? rule .* prio 25000",
     CMD "ip(6)?tables -w .* -j " VENDOR_CHAIN,
     CMD "iptables -w -t mangle -[AD] PREROUTING -m socket --nowildcard --restore-skmark -j ACCEPT",
     CMD "ndc network interface (add|remove) oem[0-9]+$",  // Invalid command: no interface removed.
diff --git a/server/Android.bp b/server/Android.bp
index 18042ae..8fc0d03 100644
--- a/server/Android.bp
+++ b/server/Android.bp
@@ -1,10 +1,125 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "system_netd_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["system_netd_license"],
+java_library {
+    name: "netd_aidl_interface-lateststable-java",
+    sdk_version: "system_current",
+    min_sdk_version: "29",
+    static_libs: [
+        "netd_aidl_interface-java",
+    ],
+    apex_available: [
+        "//apex_available:platform", // used from services.net
+        "com.android.bluetooth.updatable",
+        "com.android.tethering",
+        "com.android.wifi",
+    ],
+}
+
+cc_library_static {
+    name: "netd_event_listener_interface-lateststable-ndk_platform",
+    whole_static_libs: [
+        "netd_event_listener_interface-ndk_platform",
+    ],
+    apex_available: [
+        "com.android.resolv",
+    ],
+    min_sdk_version: "29",
+}
+
+cc_library_static {
+    name: "netd_aidl_interface-lateststable-ndk_platform",
+    whole_static_libs: [
+        "netd_aidl_interface-ndk_platform",
+    ],
+    apex_available: [
+        "com.android.resolv",
+    ],
+    min_sdk_version: "29",
+}
+
+cc_library_static {
+    name: "netd_aidl_interface-lateststable-cpp",
+    whole_static_libs: [
+        "netd_aidl_interface-cpp",
+    ],
+}
+
+aidl_interface {
+    name: "netd_aidl_interface",
+    local_include_dir: "binder",
+    srcs: [
+        "binder/android/net/INetd.aidl",
+        // AIDL interface that callers can implement to receive networking events from netd.
+        "binder/android/net/INetdUnsolicitedEventListener.aidl",
+        "binder/android/net/InterfaceConfigurationParcel.aidl",
+        "binder/android/net/MarkMaskParcel.aidl",
+        "binder/android/net/RouteInfoParcel.aidl",
+        "binder/android/net/TetherConfigParcel.aidl",
+        "binder/android/net/TetherOffloadRuleParcel.aidl",
+        "binder/android/net/TetherStatsParcel.aidl",
+        "binder/android/net/UidRangeParcel.aidl",
+    ],
+    backend: {
+        cpp: {
+            gen_log: true,
+        },
+        java: {
+            // TODO: Remove apex_available and restrict visibility to only mainline modules that are
+            // either outside the system server or use jarjar to rename the generated AIDL classes.
+            apex_available: [
+                "//apex_available:platform", // used from services.net
+                "com.android.bluetooth.updatable",
+                "com.android.tethering",
+                "com.android.wifi",
+            ],
+            // this is part of updatable modules(NetworkStack) which targets 29(Q)
+            min_sdk_version: "29",
+        },
+    },
+    versions: [
+        "1",
+        "2",
+        "3",
+        "4",
+    ],
+}
+
+java_library {
+    name: "netd_event_listener_interface-lateststable-java",
+    sdk_version: "system_current",
+    min_sdk_version: "29",
+    static_libs: [
+        "netd_event_listener_interface-java",
+    ],
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.bluetooth.updatable",
+        "com.android.wifi",
+        "com.android.tethering",
+    ],
+}
+
+aidl_interface {
+    name: "netd_event_listener_interface",
+    local_include_dir: "binder",
+    srcs: [
+        "binder/android/net/metrics/INetdEventListener.aidl",
+    ],
+    versions: ["1"],
+    backend: {
+        ndk: {
+            apex_available: [
+                "//apex_available:platform",
+                "com.android.resolv",
+            ],
+            min_sdk_version: "29",
+        },
+        java: {
+            apex_available: [
+                "//apex_available:platform",
+                "com.android.tethering",
+            ],
+            min_sdk_version: "29",
+        },
+    },
 }
 
 aidl_interface {
@@ -21,6 +136,27 @@
     ],
 }
 
+// Convenience build target for the version of the netd stable AIDL interface used by the platform.
+// This exists to ensure that all non-updatable code in the system server uses the same version.
+// Mixing different versions of generated classes results in code non-deterministically(?) using one
+// of the compiled-in versions, and potentially crashing when code compiled against a newer version
+// ends up using a generated class from an older version and calls methods that don't exist.
+// This must be a frozen version on REL builds and can be -unstable on development builds.
+// See http://b/143560726 for an example.
+java_library {
+    name: "netd_aidl_interfaces-platform-java",
+    static_libs: [
+        "netd_aidl_interface-java",
+        "netd_event_listener_interface-java",
+    ],
+    // TODO: remove bluetooth, which doesn't actually use netd at all.
+    apex_available: [
+        "//apex_available:platform", // due to the dependency from services.net
+        "com.android.bluetooth.updatable",
+    ],
+    sdk_version: "system_current",
+}
+
 // These are used in netd_integration_test
 // TODO: fold these into a cc_library_static after converting netd/server to Android.bp
 filegroup {
@@ -79,9 +215,8 @@
         "libpcap",
         "libqtaguid",
         "libssl",
-        "libsysutils",
-        "netd_aidl_interface-V7-cpp",
-        "netd_event_listener_interface-V1-cpp",
+        "netd_aidl_interface-cpp",
+        "netd_event_listener_interface-cpp",
     ],
     aidl: {
         export_aidl_headers: true,
@@ -111,6 +246,7 @@
         "libcutils",
         "libdl",
         "libhidlbase",
+        "libjsoncpp",
         "liblog",
         "libmdnssd",
         "libnetd_resolv",
@@ -123,8 +259,8 @@
         "libselinux",
         "libsysutils",
         "libutils",
-        "netd_aidl_interface-V7-cpp",
-        "netd_event_listener_interface-V1-cpp",
+        "netd_aidl_interface-cpp",
+        "netd_event_listener_interface-cpp",
         "oemnetd_aidl_interface-cpp",
     ],
     static_libs: [
@@ -146,7 +282,6 @@
         "PhysicalNetwork.cpp",
         "PppController.cpp",
         "Process.cpp",
-        "UnreachableNetwork.cpp",
         "VirtualNetwork.cpp",
         "main.cpp",
         "oem_iptables_hook.cpp",
@@ -173,8 +308,8 @@
         "liblog",
         "libutils",
         "libbinder",
-        "dnsresolver_aidl_interface-V7-cpp",
-        "netd_aidl_interface-V6-cpp",
+        "dnsresolver_aidl_interface-cpp",
+        "netd_aidl_interface-cpp",
     ],
     srcs: [
         "ndc.cpp",
@@ -183,7 +318,6 @@
     ],
     sanitize: {
         cfi: true,
-        memtag_heap: true,
     },
 }
 
@@ -221,8 +355,8 @@
         "libnetd_server",
         "libnetd_test_tun_interface",
         "libqtaguid",
-        "netd_aidl_interface-V7-cpp",
-        "netd_event_listener_interface-V1-cpp",
+        "netd_aidl_interface-unstable-cpp",
+        "netd_event_listener_interface-cpp",
     ],
     shared_libs: [
         "libbase",
@@ -237,5 +371,4 @@
         "libsysutils",
         "libutils",
     ],
-    // tidy: false,  // cuts test build time by almost 1 minute
 }
diff --git a/server/BandwidthController.cpp b/server/BandwidthController.cpp
index 1b46234..e1ce56f 100644
--- a/server/BandwidthController.cpp
+++ b/server/BandwidthController.cpp
@@ -70,7 +70,6 @@
 auto BandwidthController::iptablesRestoreFunction = execIptablesRestoreWithOutput;
 
 using android::base::Join;
-using android::base::StartsWith;
 using android::base::StringAppendF;
 using android::base::StringPrintf;
 using android::net::FirewallController;
@@ -84,6 +83,9 @@
 const char ALERT_GLOBAL_NAME[] = "globalAlert";
 const std::string NEW_CHAIN_COMMAND = "-N ";
 
+const char NAUGHTY_CHAIN[] = "bw_penalty_box";
+const char NICE_CHAIN[] = "bw_happy_box";
+
 /**
  * Some comments about the rules:
  *  * Ordering
@@ -118,12 +120,12 @@
  *      iptables -A bw_costly_iface0 -j bw_penalty_box
  *
  * * Penalty box, happy box and data saver.
- *   - bw_penalty box is a denylist of apps that are rejected.
- *   - bw_happy_box is an allowlist of apps. It always includes all system apps
+ *   - bw_penalty box is a blacklist of apps that are rejected.
+ *   - bw_happy_box is a whitelist of apps. It always includes all system apps
  *   - bw_data_saver implements data usage restrictions.
- *   - Via the UI the user can add and remove apps from the allowlist and
- *     denylist, and turn on/off data saver.
- *   - The denylist takes precedence over the allowlist and the allowlist
+ *   - Via the UI the user can add and remove apps from the whitelist and
+ *     blacklist, and turn on/off data saver.
+ *   - The blacklist takes precedence over the whitelist and the whitelist
  *     takes precedence over data saver.
  *
  * * bw_penalty_box handling:
@@ -147,8 +149,12 @@
  */
 
 const std::string COMMIT_AND_CLOSE = "COMMIT\n";
-const std::string BPF_PENALTY_BOX_MATCH_DENYLIST_COMMAND = StringPrintf(
-        "-I bw_penalty_box -m bpf --object-pinned %s -j REJECT", XT_BPF_DENYLIST_PROG_PATH);
+const std::string HAPPY_BOX_MATCH_WHITELIST_COMMAND =
+        StringPrintf("-I bw_happy_box -m owner --uid-owner %d-%d -j RETURN", 0, MAX_SYSTEM_UID);
+const std::string BPF_HAPPY_BOX_MATCH_WHITELIST_COMMAND = StringPrintf(
+        "-I bw_happy_box -m bpf --object-pinned %s -j RETURN", XT_BPF_WHITELIST_PROG_PATH);
+const std::string BPF_PENALTY_BOX_MATCH_BLACKLIST_COMMAND = StringPrintf(
+        "-I bw_penalty_box -m bpf --object-pinned %s -j REJECT", XT_BPF_BLACKLIST_PROG_PATH);
 
 static const std::vector<std::string> IPT_FLUSH_COMMANDS = {
         /*
@@ -206,8 +212,7 @@
  * See go/ipsec-data-accounting for more information.
  */
 
-std::vector<std::string> getBasicAccountingCommands() {
-    // clang-format off
+std::vector<std::string> getBasicAccountingCommands(const bool useBpf) {
     std::vector<std::string> ipt_basic_accounting_commands = {
             "*filter",
 
@@ -216,14 +221,29 @@
             "-A bw_INPUT -p esp -j RETURN",
             StringPrintf("-A bw_INPUT -m mark --mark 0x%x/0x%x -j RETURN", uidBillingMask,
                          uidBillingMask),
+            // This is ingress application UID xt_qtaguid (pre-ebpf) accounting,
+            // for bpf this is handled out of cgroup hooks instead.
+            useBpf ? "" : "-A bw_INPUT -m owner --socket-exists",
             StringPrintf("-A bw_INPUT -j MARK --or-mark 0x%x", uidBillingMask),
+
             "-A bw_OUTPUT -j bw_global_alert",
+            // Prevents IPSec double counting (Tunnel mode and Transport mode,
+            // respectively)
+            useBpf ? "" : "-A bw_OUTPUT -o " IPSEC_IFACE_PREFIX "+ -j RETURN",
+            useBpf ? "" : "-A bw_OUTPUT -m policy --pol ipsec --dir out -j RETURN",
+            // Don't count clat traffic, as it has already been counted (and subject to
+            // costly / happy_box / data_saver / penalty_box etc. based on the real UID)
+            // on the stacked interface.
+            useBpf ? "" : "-A bw_OUTPUT -m owner --uid-owner clat -j RETURN",
+            // This is egress application UID xt_qtaguid (pre-ebpf) accounting,
+            // for bpf this is handled out of cgroup hooks instead.
+            useBpf ? "" : "-A bw_OUTPUT -m owner --socket-exists",
+
             "-A bw_costly_shared -j bw_penalty_box",
-            ("-I bw_penalty_box -m bpf --object-pinned " XT_BPF_DENYLIST_PROG_PATH " -j REJECT"),
-            "-A bw_penalty_box -j bw_happy_box",
-            "-A bw_happy_box -j bw_data_saver",
+            useBpf ? BPF_PENALTY_BOX_MATCH_BLACKLIST_COMMAND : "",
+            "-A bw_penalty_box -j bw_happy_box", "-A bw_happy_box -j bw_data_saver",
             "-A bw_data_saver -j RETURN",
-            ("-I bw_happy_box -m bpf --object-pinned " XT_BPF_ALLOWLIST_PROG_PATH " -j RETURN"),
+            useBpf ? BPF_HAPPY_BOX_MATCH_WHITELIST_COMMAND : HAPPY_BOX_MATCH_WHITELIST_COMMAND,
             "COMMIT",
 
             "*raw",
@@ -242,7 +262,8 @@
             //
             // Hence we will never double count and additional corrections are not needed.
             // We can simply take the sum of base and stacked (+20B/pkt) interface counts.
-            ("-A bw_raw_PREROUTING -m bpf --object-pinned " XT_BPF_INGRESS_PROG_PATH),
+            useBpf ? "-A bw_raw_PREROUTING -m bpf --object-pinned " XT_BPF_INGRESS_PROG_PATH
+                   : "-A bw_raw_PREROUTING -m owner --socket-exists",
             "COMMIT",
 
             "*mangle",
@@ -258,14 +279,22 @@
             // This is egress interface accounting: we account 464xlat traffic only on
             // the clat interface (as offloaded packets never hit base interface's ip6tables)
             // and later sum base and stacked with overhead (+20B/pkt) in higher layers
-            ("-A bw_mangle_POSTROUTING -m bpf --object-pinned " XT_BPF_EGRESS_PROG_PATH),
+            useBpf ? "-A bw_mangle_POSTROUTING -m bpf --object-pinned " XT_BPF_EGRESS_PROG_PATH
+                   : "-A bw_mangle_POSTROUTING -m owner --socket-exists",
             COMMIT_AND_CLOSE};
-    // clang-format on
     return ipt_basic_accounting_commands;
 }
 
+std::vector<std::string> toStrVec(int num, const char* const strs[]) {
+    return std::vector<std::string>(strs, strs + num);
+}
+
 }  // namespace
 
+void BandwidthController::setBpfEnabled(bool isEnabled) {
+    mBpfSupported = isEnabled;
+}
+
 BandwidthController::BandwidthController() {
 }
 
@@ -292,7 +321,7 @@
 
     flushCleanTables(false);
 
-    std::string commands = Join(getBasicAccountingCommands(), '\n');
+    std::string commands = Join(getBasicAccountingCommands(mBpfSupported), '\n');
     return iptablesRestoreFunction(V4V6, commands, nullptr);
 }
 
@@ -323,29 +352,63 @@
     return ret;
 }
 
-int BandwidthController::addNaughtyApps(const std::vector<uint32_t>& appUids) {
-    return manipulateSpecialApps(appUids, PENALTY_BOX_MATCH, IptOpInsert);
+// TODO: Remove after removing these commands in CommandListener
+int BandwidthController::addNaughtyApps(int numUids, const char* const appUids[]) {
+    return manipulateSpecialApps(toStrVec(numUids, appUids), NAUGHTY_CHAIN,
+                                 IptJumpReject, IptOpInsert);
 }
 
-int BandwidthController::removeNaughtyApps(const std::vector<uint32_t>& appUids) {
-    return manipulateSpecialApps(appUids, PENALTY_BOX_MATCH, IptOpDelete);
+// TODO: Remove after removing these commands in CommandListener
+int BandwidthController::removeNaughtyApps(int numUids, const char* const appUids[]) {
+    return manipulateSpecialApps(toStrVec(numUids, appUids), NAUGHTY_CHAIN,
+                                 IptJumpReject, IptOpDelete);
 }
 
-int BandwidthController::addNiceApps(const std::vector<uint32_t>& appUids) {
-    return manipulateSpecialApps(appUids, HAPPY_BOX_MATCH, IptOpInsert);
+// TODO: Remove after removing these commands in CommandListener
+int BandwidthController::addNiceApps(int numUids, const char* const appUids[]) {
+    return manipulateSpecialApps(toStrVec(numUids, appUids), NICE_CHAIN,
+                                 IptJumpReturn, IptOpInsert);
 }
 
-int BandwidthController::removeNiceApps(const std::vector<uint32_t>& appUids) {
-    return manipulateSpecialApps(appUids, HAPPY_BOX_MATCH, IptOpDelete);
+// TODO: Remove after removing these commands in CommandListener
+int BandwidthController::removeNiceApps(int numUids, const char* const appUids[]) {
+    return manipulateSpecialApps(toStrVec(numUids, appUids), NICE_CHAIN,
+                                 IptJumpReturn, IptOpDelete);
 }
 
-int BandwidthController::manipulateSpecialApps(const std::vector<uint32_t>& appUids,
-                                               UidOwnerMatchType matchType, IptOp op) {
-    Status status = gCtls->trafficCtrl.updateUidOwnerMap(appUids, matchType, op);
-    if (!isOk(status)) {
-        ALOGE("unable to update the Bandwidth Uid Map: %s", toString(status).c_str());
+int BandwidthController::addNaughtyApps(const std::vector<std::string>& appStrUid) {
+    return manipulateSpecialApps(appStrUid, NAUGHTY_CHAIN, IptJumpReject, IptOpInsert);
+}
+
+int BandwidthController::removeNaughtyApps(const std::vector<std::string>& appStrUid) {
+    return manipulateSpecialApps(appStrUid, NAUGHTY_CHAIN, IptJumpReject, IptOpDelete);
+}
+
+int BandwidthController::addNiceApps(const std::vector<std::string>& appStrUid) {
+    return manipulateSpecialApps(appStrUid, NICE_CHAIN, IptJumpReturn, IptOpInsert);
+}
+
+int BandwidthController::removeNiceApps(const std::vector<std::string>& appStrUid) {
+    return manipulateSpecialApps(appStrUid, NICE_CHAIN, IptJumpReturn, IptOpDelete);
+}
+
+int BandwidthController::manipulateSpecialApps(const std::vector<std::string>& appStrUids,
+                                               const std::string& chain, IptJumpOp jumpHandling,
+                                               IptOp op) {
+    if (mBpfSupported) {
+        Status status = gCtls->trafficCtrl.updateUidOwnerMap(appStrUids, jumpHandling, op);
+        if (!isOk(status)) {
+            ALOGE("unable to update the Bandwidth Uid Map: %s", toString(status).c_str());
+      }
+      return status.code();
     }
-    return status.code();
+    std::string cmd = "*filter\n";
+    for (const auto& appStrUid : appStrUids) {
+        StringAppendF(&cmd, "%s %s -m owner --uid-owner %s%s\n", opToString(op), chain.c_str(),
+                      appStrUid.c_str(), jumpToString(jumpHandling));
+    }
+    StringAppendF(&cmd, "COMMIT\n");
+    return iptablesRestoreFunction(V4V6, cmd, nullptr);
 }
 
 int BandwidthController::setInterfaceSharedQuota(const std::string& iface, int64_t maxBytes) {
@@ -772,11 +835,11 @@
 
     // Find and flush all rules starting with "-N bw_costly_<iface>" except "-N bw_costly_shared".
     while (std::getline(stream, rule, '\n')) {
-        if (!StartsWith(rule, NEW_CHAIN_COMMAND)) continue;
+        if (rule.find(NEW_CHAIN_COMMAND) != 0) continue;
         chainName = rule.substr(NEW_CHAIN_COMMAND.size());
         ALOGV("parse chainName=<%s> orig line=<%s>", chainName.c_str(), rule.c_str());
 
-        if (!StartsWith(chainName, "bw_costly_") || chainName == std::string("bw_costly_shared")) {
+        if (chainName.find("bw_costly_") != 0 || chainName == std::string("bw_costly_shared")) {
             continue;
         }
 
@@ -811,6 +874,8 @@
      * For port-unreachable (default), TCP should consider as an abort (RFC1122).
      */
     switch (jumpHandling) {
+    case IptJumpNoAdd:
+        return "";
     case IptJumpReject:
         return " -j REJECT";
     case IptJumpReturn:
diff --git a/server/BandwidthController.h b/server/BandwidthController.h
index 414e91b..b8691dc 100644
--- a/server/BandwidthController.h
+++ b/server/BandwidthController.h
@@ -24,7 +24,6 @@
 #include <mutex>
 
 #include "NetdConstants.h"
-#include "netdbpf/bpf_shared.h"
 
 class BandwidthController {
 public:
@@ -33,6 +32,7 @@
     BandwidthController();
 
     int setupIptablesHooks();
+    void setBpfEnabled(bool isEnabled);
 
     int enableBandwidthControl();
     int disableBandwidthControl();
@@ -46,10 +46,16 @@
     int getInterfaceQuota(const std::string& iface, int64_t* bytes);
     int removeInterfaceQuota(const std::string& iface);
 
-    int addNaughtyApps(const std::vector<uint32_t>& appUids);
-    int removeNaughtyApps(const std::vector<uint32_t>& appUids);
-    int addNiceApps(const std::vector<uint32_t>& appUids);
-    int removeNiceApps(const std::vector<uint32_t>& appUids);
+    // TODO: Remove after removing these commands in CommandListener
+    int addNaughtyApps(int numUids, const char* const appUids[]);
+    int removeNaughtyApps(int numUids, const char* const appUids[]);
+    int addNiceApps(int numUids, const char* const appUids[]);
+    int removeNiceApps(int numUids, const char* const appUids[]);
+
+    int addNaughtyApps(const std::vector<std::string>& appStrUid);
+    int removeNaughtyApps(const std::vector<std::string>& appStrUid);
+    int addNiceApps(const std::vector<std::string>& appStrUid);
+    int removeNiceApps(const std::vector<std::string>& appStrUid);
 
     int setGlobalAlert(int64_t bytes);
     int removeGlobalAlert();
@@ -69,7 +75,7 @@
     static const char LOCAL_MANGLE_POSTROUTING[];
     static const char LOCAL_GLOBAL_ALERT[];
 
-    enum IptJumpOp { IptJumpReject, IptJumpReturn };
+    enum IptJumpOp { IptJumpReject, IptJumpReturn, IptJumpNoAdd };
     enum IptOp { IptOpInsert, IptOpDelete };
 
   private:
@@ -90,8 +96,8 @@
 
     std::string makeDataSaverCommand(IptablesTarget target, bool enable);
 
-    int manipulateSpecialApps(const std::vector<uint32_t>& appStrUids, UidOwnerMatchType matchType,
-                              IptOp appOp);
+    int manipulateSpecialApps(const std::vector<std::string>& appStrUids, const std::string& chain,
+                              IptJumpOp jumpHandling, IptOp appOp);
 
     int runIptablesAlertCmd(IptOp op, const std::string& alertName, int64_t bytes);
     int runIptablesAlertFwdCmd(IptOp op, const std::string& alertName, int64_t bytes);
@@ -126,6 +132,8 @@
     static const char *opToString(IptOp op);
     static const char *jumpToString(IptJumpOp jumpHandling);
 
+    bool mBpfSupported = false;
+
     int64_t mSharedQuotaBytes = 0;
     int64_t mSharedAlertBytes = 0;
     int64_t mGlobalAlertBytes = 0;
diff --git a/server/BandwidthControllerTest.cpp b/server/BandwidthControllerTest.cpp
index d635daf..115a0da 100644
--- a/server/BandwidthControllerTest.cpp
+++ b/server/BandwidthControllerTest.cpp
@@ -50,6 +50,73 @@
 using android::netdutils::UniqueFile;
 using android::netdutils::status::ok;
 
+const std::string ACCOUNT_RULES_WITHOUT_BPF =
+        "*filter\n"
+        "-A bw_INPUT -j bw_global_alert\n"
+        "-A bw_INPUT -p esp -j RETURN\n"
+        "-A bw_INPUT -m mark --mark 0x100000/0x100000 -j RETURN\n"
+        "-A bw_INPUT -m owner --socket-exists\n"
+        "-A bw_INPUT -j MARK --or-mark 0x100000\n"
+        "-A bw_OUTPUT -j bw_global_alert\n"
+        "-A bw_OUTPUT -o ipsec+ -j RETURN\n"
+        "-A bw_OUTPUT -m policy --pol ipsec --dir out -j RETURN\n"
+        "-A bw_OUTPUT -m owner --uid-owner clat -j RETURN\n"
+        "-A bw_OUTPUT -m owner --socket-exists\n"
+        "-A bw_costly_shared -j bw_penalty_box\n"
+        "\n"
+        "-A bw_penalty_box -j bw_happy_box\n"
+        "-A bw_happy_box -j bw_data_saver\n"
+        "-A bw_data_saver -j RETURN\n"
+        "-I bw_happy_box -m owner --uid-owner 0-9999 -j RETURN\n"
+        "COMMIT\n"
+        "*raw\n"
+        "-A bw_raw_PREROUTING -i ipsec+ -j RETURN\n"
+        "-A bw_raw_PREROUTING -m policy --pol ipsec --dir in -j RETURN\n"
+        "-A bw_raw_PREROUTING -m owner --socket-exists\n"
+        "COMMIT\n"
+        "*mangle\n"
+        "-A bw_mangle_POSTROUTING -o ipsec+ -j RETURN\n"
+        "-A bw_mangle_POSTROUTING -m policy --pol ipsec --dir out -j RETURN\n"
+        "-A bw_mangle_POSTROUTING -j MARK --set-mark 0x0/0x100000\n"
+        "-A bw_mangle_POSTROUTING -m owner --uid-owner clat -j RETURN\n"
+        "-A bw_mangle_POSTROUTING -m owner --socket-exists\n"
+        "COMMIT\n";
+
+const std::string ACCOUNT_RULES_WITH_BPF =
+        "*filter\n"
+        "-A bw_INPUT -j bw_global_alert\n"
+        "-A bw_INPUT -p esp -j RETURN\n"
+        "-A bw_INPUT -m mark --mark 0x100000/0x100000 -j RETURN\n"
+        "\n"
+        "-A bw_INPUT -j MARK --or-mark 0x100000\n"
+        "-A bw_OUTPUT -j bw_global_alert\n"
+        "\n"
+        "\n"
+        "\n"
+        "\n"
+        "-A bw_costly_shared -j bw_penalty_box\n" +
+        StringPrintf("-I bw_penalty_box -m bpf --object-pinned %s -j REJECT\n",
+                     XT_BPF_BLACKLIST_PROG_PATH) +
+        "-A bw_penalty_box -j bw_happy_box\n"
+        "-A bw_happy_box -j bw_data_saver\n"
+        "-A bw_data_saver -j RETURN\n" +
+        StringPrintf("-I bw_happy_box -m bpf --object-pinned %s -j RETURN\n",
+                     XT_BPF_WHITELIST_PROG_PATH) +
+        "COMMIT\n"
+        "*raw\n"
+        "-A bw_raw_PREROUTING -i ipsec+ -j RETURN\n"
+        "-A bw_raw_PREROUTING -m policy --pol ipsec --dir in -j RETURN\n" +
+        StringPrintf("-A bw_raw_PREROUTING -m bpf --object-pinned %s\n", XT_BPF_INGRESS_PROG_PATH) +
+        "COMMIT\n"
+        "*mangle\n"
+        "-A bw_mangle_POSTROUTING -o ipsec+ -j RETURN\n"
+        "-A bw_mangle_POSTROUTING -m policy --pol ipsec --dir out -j RETURN\n"
+        "-A bw_mangle_POSTROUTING -j MARK --set-mark 0x0/0x100000\n"
+        "-A bw_mangle_POSTROUTING -m owner --uid-owner clat -j RETURN\n" +
+        StringPrintf("-A bw_mangle_POSTROUTING -m bpf --object-pinned %s\n",
+                     XT_BPF_EGRESS_PROG_PATH) +
+        "COMMIT\n";
+
 class BandwidthControllerTest : public IptablesBaseTest {
 protected:
     BandwidthControllerTest() {
@@ -128,6 +195,23 @@
         EXPECT_CALL(mSyscalls, fclose(dummyFile)).WillOnce(Return(ok));
     }
 
+    void checkBandwithControl(bool useBpf) {
+        // Pretend no bw_costly_shared_<iface> rules already exist...
+        addIptablesRestoreOutput(
+                "-P OUTPUT ACCEPT\n"
+                "-N bw_costly_shared\n"
+                "-N unrelated\n");
+
+        // ... so none are flushed or deleted.
+        std::string expectedClean = "";
+
+        std::string expectedAccounting =
+                useBpf ? ACCOUNT_RULES_WITH_BPF : ACCOUNT_RULES_WITHOUT_BPF;
+        mBw.setBpfEnabled(useBpf);
+        mBw.enableBandwidthControl();
+        expectSetupCommands(expectedClean, expectedAccounting);
+    }
+
     StrictMock<android::netdutils::ScopedMockSyscalls> mSyscalls;
 };
 
@@ -163,46 +247,12 @@
     EXPECT_TRUE(isPowerOfTwo);
 }
 
-TEST_F(BandwidthControllerTest, TestEnableBandwidthControl) {
-    // Pretend no bw_costly_shared_<iface> rules already exist...
-    addIptablesRestoreOutput(
-            "-P OUTPUT ACCEPT\n"
-            "-N bw_costly_shared\n"
-            "-N unrelated\n");
+TEST_F(BandwidthControllerTest, TestEnableBandwidthControlWithBpf) {
+    checkBandwithControl(true);
+}
 
-    // ... so none are flushed or deleted.
-    // clang-format off
-    static const std::string expectedClean = "";
-    static const std::string expectedAccounting =
-            "*filter\n"
-            "-A bw_INPUT -j bw_global_alert\n"
-            "-A bw_INPUT -p esp -j RETURN\n"
-            "-A bw_INPUT -m mark --mark 0x100000/0x100000 -j RETURN\n"
-            "-A bw_INPUT -j MARK --or-mark 0x100000\n"
-            "-A bw_OUTPUT -j bw_global_alert\n"
-            "-A bw_costly_shared -j bw_penalty_box\n"
-            "-I bw_penalty_box -m bpf --object-pinned " XT_BPF_DENYLIST_PROG_PATH " -j REJECT\n"
-            "-A bw_penalty_box -j bw_happy_box\n"
-            "-A bw_happy_box -j bw_data_saver\n"
-            "-A bw_data_saver -j RETURN\n"
-            "-I bw_happy_box -m bpf --object-pinned " XT_BPF_ALLOWLIST_PROG_PATH " -j RETURN\n"
-            "COMMIT\n"
-            "*raw\n"
-            "-A bw_raw_PREROUTING -i ipsec+ -j RETURN\n"
-            "-A bw_raw_PREROUTING -m policy --pol ipsec --dir in -j RETURN\n"
-            "-A bw_raw_PREROUTING -m bpf --object-pinned " XT_BPF_INGRESS_PROG_PATH "\n"
-            "COMMIT\n"
-            "*mangle\n"
-            "-A bw_mangle_POSTROUTING -o ipsec+ -j RETURN\n"
-            "-A bw_mangle_POSTROUTING -m policy --pol ipsec --dir out -j RETURN\n"
-            "-A bw_mangle_POSTROUTING -j MARK --set-mark 0x0/0x100000\n"
-            "-A bw_mangle_POSTROUTING -m owner --uid-owner clat -j RETURN\n"
-            "-A bw_mangle_POSTROUTING -m bpf --object-pinned " XT_BPF_EGRESS_PROG_PATH "\n"
-            "COMMIT\n";
-    // clang-format on
-
-    mBw.enableBandwidthControl();
-    expectSetupCommands(expectedClean, expectedAccounting);
+TEST_F(BandwidthControllerTest, TestEnableBandwidthControlWithoutBpf) {
+    checkBandwithControl(false);
 }
 
 TEST_F(BandwidthControllerTest, TestDisableBandwidthControl) {
@@ -462,3 +512,24 @@
     expectIptablesRestoreCommands(expected);
 }
 
+TEST_F(BandwidthControllerTest, ManipulateSpecialApps) {
+    std::vector<const char *> appUids = { "1000", "1001", "10012" };
+
+    std::vector<std::string> expected = {
+            "*filter\n"
+            "-I bw_happy_box -m owner --uid-owner 1000 -j RETURN\n"
+            "-I bw_happy_box -m owner --uid-owner 1001 -j RETURN\n"
+            "-I bw_happy_box -m owner --uid-owner 10012 -j RETURN\n"
+            "COMMIT\n"};
+    EXPECT_EQ(0, mBw.addNiceApps(appUids.size(), const_cast<char**>(&appUids[0])));
+    expectIptablesRestoreCommands(expected);
+
+    expected = {
+            "*filter\n"
+            "-D bw_penalty_box -m owner --uid-owner 1000 -j REJECT\n"
+            "-D bw_penalty_box -m owner --uid-owner 1001 -j REJECT\n"
+            "-D bw_penalty_box -m owner --uid-owner 10012 -j REJECT\n"
+            "COMMIT\n"};
+    EXPECT_EQ(0, mBw.removeNaughtyApps(appUids.size(), const_cast<char**>(&appUids[0])));
+    expectIptablesRestoreCommands(expected);
+}
diff --git a/server/ClatdController.cpp b/server/ClatdController.cpp
index 847a6db..90afa3a 100644
--- a/server/ClatdController.cpp
+++ b/server/ClatdController.cpp
@@ -73,23 +73,51 @@
 void ClatdController::init(void) {
     std::lock_guard guard(mutex);
 
-    int rv = getClatEgress4MapFd();
-    if (rv < 0) {
-        ALOGE("getClatEgress4MapFd() failure: %s", strerror(-rv));
+    // TODO: should refactor into separate function for testability
+    if (!bpf::isBpfSupported()) {
+        ALOGI("Pre-4.9 kernel or pre-P api shipping level - disabling clat ebpf.");
+        mClatEbpfMode = ClatEbpfDisabled;
         return;
     }
-    mClatEgress4Map.reset(rv);
 
-    rv = getClatIngress6MapFd();
+    // We know the device initially shipped with at least P...,
+    // but did it ship with at least Q?
+
+    uint64_t api_level = base::GetUintProperty<uint64_t>("ro.product.first_api_level", 0);
+    if (api_level == 0) {
+        ALOGE("Cannot determine initial API level of the device.");
+        api_level = base::GetUintProperty<uint64_t>("ro.build.version.sdk", 0);
+    }
+
+    // Note: MINIMUM_API_REQUIRED is for eBPF as a whole and is thus P
+    if (api_level > bpf::MINIMUM_API_REQUIRED) {
+        ALOGI("4.9+ kernel and device shipped with Q+ - clat ebpf should work.");
+        mClatEbpfMode = ClatEbpfEnabled;
+    } else {
+        // We cannot guarantee that 4.9-P kernels will include NET_CLS_BPF support.
+        ALOGI("4.9+ kernel and device shipped with P - clat ebpf might work.");
+        mClatEbpfMode = ClatEbpfMaybe;
+    }
+
+    int rv = getClatEgressMapFd();
     if (rv < 0) {
-        ALOGE("getClatIngress6MapFd() failure: %s", strerror(-rv));
-        mClatEgress4Map.reset(-1);
+        ALOGE("getClatEgressMapFd() failure: %s", strerror(-rv));
+        mClatEbpfMode = ClatEbpfDisabled;
         return;
     }
-    mClatIngress6Map.reset(rv);
+    mClatEgressMap.reset(rv);
 
-    mClatEgress4Map.clear();
-    mClatIngress6Map.clear();
+    rv = getClatIngressMapFd();
+    if (rv < 0) {
+        ALOGE("getClatIngressMapFd() failure: %s", strerror(-rv));
+        mClatEbpfMode = ClatEbpfDisabled;
+        mClatEgressMap.reset(-1);
+        return;
+    }
+    mClatIngressMap.reset(rv);
+
+    mClatEgressMap.clear();
+    mClatIngressMap.clear();
 }
 
 bool ClatdController::isIpv4AddressFree(in_addr_t addr) {
@@ -198,6 +226,8 @@
 }
 
 void ClatdController::maybeStartBpf(const ClatdTracker& tracker) {
+    if (mClatEbpfMode == ClatEbpfDisabled) return;
+
     auto isEthernet = android::net::isEthernet(tracker.iface);
     if (!isEthernet.ok()) {
         ALOGE("isEthernet(%s[%d]) failure: %s", tracker.iface, tracker.ifIndex,
@@ -206,54 +236,54 @@
     }
 
     // This program will be attached to the v4-* interface which is a TUN and thus always rawip.
-    int rv = getClatEgress4ProgFd(RAWIP);
+    int rv = getClatEgressProgFd(RAWIP);
     if (rv < 0) {
-        ALOGE("getClatEgress4ProgFd(RAWIP) failure: %s", strerror(-rv));
+        ALOGE("getClatEgressProgFd(RAWIP) failure: %s", strerror(-rv));
         return;
     }
     unique_fd txRawIpProgFd(rv);
 
-    rv = getClatIngress6ProgFd(isEthernet.value());
+    rv = getClatIngressProgFd(isEthernet.value());
     if (rv < 0) {
-        ALOGE("getClatIngress6ProgFd(%d) failure: %s", isEthernet.value(), strerror(-rv));
+        ALOGE("getClatIngressProgFd(%d) failure: %s", isEthernet.value(), strerror(-rv));
         return;
     }
     unique_fd rxProgFd(rv);
 
-    ClatEgress4Key txKey = {
+    ClatEgressKey txKey = {
             .iif = tracker.v4ifIndex,
             .local4 = tracker.v4,
     };
-    ClatEgress4Value txValue = {
+    ClatEgressValue txValue = {
             .oif = tracker.ifIndex,
             .local6 = tracker.v6,
             .pfx96 = tracker.pfx96,
             .oifIsEthernet = isEthernet.value(),
     };
 
-    auto ret = mClatEgress4Map.writeValue(txKey, txValue, BPF_ANY);
+    auto ret = mClatEgressMap.writeValue(txKey, txValue, BPF_ANY);
     if (!ret.ok()) {
-        ALOGE("mClatEgress4Map.writeValue failure: %s", strerror(ret.error().code()));
+        ALOGE("mClatEgressMap.writeValue failure: %s", strerror(ret.error().code()));
         return;
     }
 
-    ClatIngress6Key rxKey = {
+    ClatIngressKey rxKey = {
             .iif = tracker.ifIndex,
             .pfx96 = tracker.pfx96,
             .local6 = tracker.v6,
     };
-    ClatIngress6Value rxValue = {
+    ClatIngressValue rxValue = {
             // TODO: move all the clat code to eBPF and remove the tun interface entirely.
             .oif = tracker.v4ifIndex,
             .local4 = tracker.v4,
     };
 
-    ret = mClatIngress6Map.writeValue(rxKey, rxValue, BPF_ANY);
+    ret = mClatIngressMap.writeValue(rxKey, rxValue, BPF_ANY);
     if (!ret.ok()) {
-        ALOGE("mClatIngress6Map.writeValue failure: %s", strerror(ret.error().code()));
-        ret = mClatEgress4Map.deleteValue(txKey);
+        ALOGE("mClatIngressMap.writeValue failure: %s", strerror(ret.error().code()));
+        ret = mClatEgressMap.deleteValue(txKey);
         if (!ret.ok())
-            ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
+            ALOGE("mClatEgressMap.deleteValue failure: %s", strerror(ret.error().code()));
         return;
     }
 
@@ -268,37 +298,47 @@
     if (rv) {
         ALOGE("tcQdiscAddDevClsact(%d[%s]) failure: %s", tracker.v4ifIndex, tracker.v4iface,
               strerror(-rv));
-        ret = mClatEgress4Map.deleteValue(txKey);
+        ret = mClatEgressMap.deleteValue(txKey);
         if (!ret.ok())
-            ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
-        ret = mClatIngress6Map.deleteValue(rxKey);
+            ALOGE("mClatEgressMap.deleteValue failure: %s", strerror(ret.error().code()));
+        ret = mClatIngressMap.deleteValue(rxKey);
         if (!ret.ok())
-            ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
+            ALOGE("mClatIngressMap.deleteValue failure: %s", strerror(ret.error().code()));
         return;
     }
 
     rv = tcFilterAddDevEgressClatIpv4(tracker.v4ifIndex, txRawIpProgFd, RAWIP);
     if (rv) {
-        ALOGE("tcFilterAddDevEgressClatIpv4(%d[%s], RAWIP) failure: %s", tracker.v4ifIndex,
-              tracker.v4iface, strerror(-rv));
+        if ((rv == -ENOENT) && (mClatEbpfMode == ClatEbpfMaybe)) {
+            ALOGI("tcFilterAddDevEgressClatIpv4(%d[%s], RAWIP): %s", tracker.v4ifIndex,
+                  tracker.v4iface, strerror(-rv));
+        } else {
+            ALOGE("tcFilterAddDevEgressClatIpv4(%d[%s], RAWIP) failure: %s", tracker.v4ifIndex,
+                  tracker.v4iface, strerror(-rv));
+        }
 
         // The v4- interface clsact is not deleted for unwinding error because once it is created
         // with interface addition, the lifetime is till interface deletion. Moreover, the clsact
         // has no clat filter now. It should not break anything.
 
-        ret = mClatEgress4Map.deleteValue(txKey);
+        ret = mClatEgressMap.deleteValue(txKey);
         if (!ret.ok())
-            ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
-        ret = mClatIngress6Map.deleteValue(rxKey);
+            ALOGE("mClatEgressMap.deleteValue failure: %s", strerror(ret.error().code()));
+        ret = mClatIngressMap.deleteValue(rxKey);
         if (!ret.ok())
-            ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
+            ALOGE("mClatIngressMap.deleteValue failure: %s", strerror(ret.error().code()));
         return;
     }
 
     rv = tcFilterAddDevIngressClatIpv6(tracker.ifIndex, rxProgFd, isEthernet.value());
     if (rv) {
-        ALOGE("tcFilterAddDevIngressClatIpv6(%d[%s], %d) failure: %s", tracker.ifIndex,
-              tracker.iface, isEthernet.value(), strerror(-rv));
+        if ((rv == -ENOENT) && (mClatEbpfMode == ClatEbpfMaybe)) {
+            ALOGI("tcFilterAddDevIngressClatIpv6(%d[%s], %d): %s", tracker.ifIndex, tracker.iface,
+                  isEthernet.value(), strerror(-rv));
+        } else {
+            ALOGE("tcFilterAddDevIngressClatIpv6(%d[%s], %d) failure: %s", tracker.ifIndex,
+                  tracker.iface, isEthernet.value(), strerror(-rv));
+        }
         rv = tcFilterDelDevEgressClatIpv4(tracker.v4ifIndex);
         if (rv) {
             ALOGE("tcFilterDelDevEgressClatIpv4(%d[%s]) failure: %s", tracker.v4ifIndex,
@@ -308,12 +348,12 @@
         // The v4- interface clsact is not deleted. See the reason in the error unwinding code of
         // the egress filter attaching of v4- tun interface.
 
-        ret = mClatEgress4Map.deleteValue(txKey);
+        ret = mClatEgressMap.deleteValue(txKey);
         if (!ret.ok())
-            ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
-        ret = mClatIngress6Map.deleteValue(rxKey);
+            ALOGE("mClatEgressMap.deleteValue failure: %s", strerror(ret.error().code()));
+        ret = mClatIngressMap.deleteValue(rxKey);
         if (!ret.ok())
-            ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
+            ALOGE("mClatIngressMap.deleteValue failure: %s", strerror(ret.error().code()));
         return;
     }
 
@@ -332,6 +372,8 @@
 }
 
 void ClatdController::maybeStopBpf(const ClatdTracker& tracker) {
+    if (mClatEbpfMode == ClatEbpfDisabled) return;
+
     int rv = tcFilterDelDevIngressClatIpv6(tracker.ifIndex);
     if (rv < 0) {
         ALOGE("tcFilterDelDevIngressClatIpv6(%d[%s]) failure: %s", tracker.ifIndex, tracker.iface,
@@ -347,22 +389,22 @@
     // We cleanup the maps last, so scanning through them can be used to
     // determine what still needs cleanup.
 
-    ClatEgress4Key txKey = {
+    ClatEgressKey txKey = {
             .iif = tracker.v4ifIndex,
             .local4 = tracker.v4,
     };
 
-    auto ret = mClatEgress4Map.deleteValue(txKey);
-    if (!ret.ok()) ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
+    auto ret = mClatEgressMap.deleteValue(txKey);
+    if (!ret.ok()) ALOGE("mClatEgressMap.deleteValue failure: %s", strerror(ret.error().code()));
 
-    ClatIngress6Key rxKey = {
+    ClatIngressKey rxKey = {
             .iif = tracker.ifIndex,
             .pfx96 = tracker.pfx96,
             .local6 = tracker.v6,
     };
 
-    ret = mClatIngress6Map.deleteValue(rxKey);
-    if (!ret.ok()) ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
+    ret = mClatIngressMap.deleteValue(rxKey);
+    if (!ret.ok()) ALOGE("mClatIngressMap.deleteValue failure: %s", strerror(ret.error().code()));
 }
 
 // Finds the tracker of the clatd running on interface |interface|, or nullptr if clatd has not been
@@ -577,14 +619,14 @@
 }
 
 void ClatdController::dumpEgress(DumpWriter& dw) {
-    if (!mClatEgress4Map.isValid()) return;  // if unsupported just don't dump anything
+    if (!mClatEgressMap.isValid()) return;  // if unsupported just don't dump anything
 
     ScopedIndent bpfIndent(dw);
     dw.println("BPF egress map: iif(iface) v4Addr -> v6Addr nat64Prefix oif(iface)");
 
     ScopedIndent bpfDetailIndent(dw);
-    const auto printClatMap = [&dw](const ClatEgress4Key& key, const ClatEgress4Value& value,
-                                    const BpfMap<ClatEgress4Key, ClatEgress4Value>&) {
+    const auto printClatMap = [&dw](const ClatEgressKey& key, const ClatEgressValue& value,
+                                    const BpfMap<ClatEgressKey, ClatEgressValue>&) {
         char iifStr[IFNAMSIZ] = "?";
         char local4Str[INET_ADDRSTRLEN] = "?";
         char local6Str[INET6_ADDRSTRLEN] = "?";
@@ -601,21 +643,21 @@
                    pfx96Str, value.oif, oifStr, value.oifIsEthernet ? "ether" : "rawip");
         return Result<void>();
     };
-    auto res = mClatEgress4Map.iterateWithValue(printClatMap);
+    auto res = mClatEgressMap.iterateWithValue(printClatMap);
     if (!res.ok()) {
         dw.println("Error printing BPF map: %s", res.error().message().c_str());
     }
 }
 
 void ClatdController::dumpIngress(DumpWriter& dw) {
-    if (!mClatIngress6Map.isValid()) return;  // if unsupported just don't dump anything
+    if (!mClatIngressMap.isValid()) return;  // if unsupported just don't dump anything
 
     ScopedIndent bpfIndent(dw);
     dw.println("BPF ingress map: iif(iface) nat64Prefix v6Addr -> v4Addr oif(iface)");
 
     ScopedIndent bpfDetailIndent(dw);
-    const auto printClatMap = [&dw](const ClatIngress6Key& key, const ClatIngress6Value& value,
-                                    const BpfMap<ClatIngress6Key, ClatIngress6Value>&) {
+    const auto printClatMap = [&dw](const ClatIngressKey& key, const ClatIngressValue& value,
+                                    const BpfMap<ClatIngressKey, ClatIngressValue>&) {
         char iifStr[IFNAMSIZ] = "?";
         char pfx96Str[INET6_ADDRSTRLEN] = "?";
         char local6Str[INET6_ADDRSTRLEN] = "?";
@@ -632,7 +674,7 @@
                    value.oif, oifStr);
         return Result<void>();
     };
-    auto res = mClatIngress6Map.iterateWithValue(printClatMap);
+    auto res = mClatIngressMap.iterateWithValue(printClatMap);
     if (!res.ok()) {
         dw.println("Error printing BPF map: %s", res.error().message().c_str());
     }
diff --git a/server/ClatdController.h b/server/ClatdController.h
index 4b7c60b..2c13958 100644
--- a/server/ClatdController.h
+++ b/server/ClatdController.h
@@ -89,8 +89,19 @@
                                    in6_addr* v6);
     static void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Prefix);
 
-    bpf::BpfMap<ClatEgress4Key, ClatEgress4Value> mClatEgress4Map GUARDED_BY(mutex);
-    bpf::BpfMap<ClatIngress6Key, ClatIngress6Value> mClatIngress6Map GUARDED_BY(mutex);
+    enum eClatEbpfMode {
+        ClatEbpfDisabled,  //  <4.9 kernel ||  <P api shipping level -- will not work
+        ClatEbpfMaybe,     // >=4.9 kernel &&   P api shipping level -- might work
+        ClatEbpfEnabled,   // >=4.9 kernel && >=Q api shipping level -- must work
+    };
+    eClatEbpfMode mClatEbpfMode GUARDED_BY(mutex);
+    eClatEbpfMode getEbpfMode() EXCLUDES(mutex) {
+        std::lock_guard guard(mutex);
+        return mClatEbpfMode;
+    }
+
+    bpf::BpfMap<ClatEgressKey, ClatEgressValue> mClatEgressMap GUARDED_BY(mutex);
+    bpf::BpfMap<ClatIngressKey, ClatIngressValue> mClatIngressMap GUARDED_BY(mutex);
 
     void maybeStartBpf(const ClatdTracker& tracker) REQUIRES(mutex);
     void maybeStopBpf(const ClatdTracker& tracker) REQUIRES(mutex);
diff --git a/server/ClatdControllerTest.cpp b/server/ClatdControllerTest.cpp
index d3a2aa7..dc71a03 100644
--- a/server/ClatdControllerTest.cpp
+++ b/server/ClatdControllerTest.cpp
@@ -69,6 +69,7 @@
 
   protected:
     ClatdController mClatdCtrl;
+    bool isEbpfDisabled() { return mClatdCtrl.getEbpfMode() == ClatdController::ClatEbpfDisabled; }
     void setIptablesDropRule(bool a, const char* b, const char* c, const char* d) {
         std::lock_guard guard(mClatdCtrl.mutex);
         return mClatdCtrl.setIptablesDropRule(a, b, c, d);
diff --git a/server/Controllers.cpp b/server/Controllers.cpp
index 1f2bac2..5af8a91 100644
--- a/server/Controllers.cpp
+++ b/server/Controllers.cpp
@@ -285,15 +285,10 @@
     netdutils::Status tcStatus = trafficCtrl.start();
     if (!isOk(tcStatus)) {
         gLog.error("Failed to start trafficcontroller: (%s)", toString(tcStatus).c_str());
-        gLog.error("CRITICAL: sleeping 60 seconds, netd exiting with failure, crash loop likely!");
-        // The expected reason we get here is a major kernel or other code bug, as such
-        // the probability that things will succeed on restart of netd is pretty small.
-        // So, let's wait a minute to at least try to limit the log spam a little bit.
-        sleep(60);
-        exit(1);
     }
     gLog.info("Initializing traffic control: %" PRId64 "us", s.getTimeAndResetUs());
 
+    bandwidthCtrl.setBpfEnabled(trafficCtrl.getBpfEnabled());
     bandwidthCtrl.enableBandwidthControl();
     gLog.info("Enabling bandwidth control: %" PRId64 "us", s.getTimeAndResetUs());
 
diff --git a/server/DummyNetwork.cpp b/server/DummyNetwork.cpp
index 186072d..6cd6791 100644
--- a/server/DummyNetwork.cpp
+++ b/server/DummyNetwork.cpp
@@ -14,22 +14,17 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "Netd"
-
 #include "DummyNetwork.h"
 
+#include "RouteController.h"
+
+#define LOG_TAG "Netd"
+#include "log/log.h"
 #include "errno.h"
 
 namespace android {
 namespace net {
 
-// The dummy network is used to blackhole or reject traffic.
-// It has an IPv4 and an IPv6 default route that point to a dummy interface
-// which drops packets. It is used for system purposes only. Applications
-// cannot use multinetwork APIs such as Network#bindSocket or
-// android_setsocknetwork to send packets on the dummy network.
-// Any attempt to do so will fail with ENETUNREACH.
-
 const char* DummyNetwork::INTERFACE_NAME = "dummy0";
 
 DummyNetwork::DummyNetwork(unsigned netId) : Network(netId) {
@@ -39,5 +34,17 @@
 DummyNetwork::~DummyNetwork() {
 }
 
+Network::Type DummyNetwork::getType() const {
+    return DUMMY;
+}
+
+int DummyNetwork::addInterface(const std::string& /* interface */) {
+    return -EINVAL;
+}
+
+int DummyNetwork::removeInterface(const std::string& /* interface */) {
+    return -EINVAL;
+}
+
 }  // namespace net
 }  // namespace android
diff --git a/server/DummyNetwork.h b/server/DummyNetwork.h
index 8f9960b..5823ce5 100644
--- a/server/DummyNetwork.h
+++ b/server/DummyNetwork.h
@@ -27,7 +27,9 @@
     virtual ~DummyNetwork();
 
   private:
-    std::string getTypeString() const override { return "DUMMY"; };
+    Type getType() const override;
+    [[nodiscard]] int addInterface(const std::string& interface) override;
+    [[nodiscard]] int removeInterface(const std::string& interface) override;
 };
 
 }  // namespace android::net
diff --git a/server/FirewallController.cpp b/server/FirewallController.cpp
index 0a0f8d8..3c070ce 100644
--- a/server/FirewallController.cpp
+++ b/server/FirewallController.cpp
@@ -53,6 +53,10 @@
 // Proc file containing the uid mapping for the user namespace of the current process.
 const char kUidMapProcFile[] = "/proc/self/uid_map";
 
+bool getBpfOwnerStatus() {
+    return gCtls->trafficCtrl.getBpfEnabled();
+}
+
 }  // namespace
 
 namespace android {
@@ -69,7 +73,6 @@
 const char* FirewallController::LOCAL_DOZABLE = "fw_dozable";
 const char* FirewallController::LOCAL_STANDBY = "fw_standby";
 const char* FirewallController::LOCAL_POWERSAVE = "fw_powersave";
-const char* FirewallController::LOCAL_RESTRICTED = "fw_restricted";
 
 // ICMPv6 types that are required for any form of IPv6 connectivity to work. Note that because the
 // fw_dozable chain is called from both INPUT and OUTPUT, this includes both packets that we need
@@ -84,22 +87,20 @@
 };
 
 FirewallController::FirewallController(void) : mMaxUid(discoverMaximumValidUid(kUidMapProcFile)) {
-    // If no rules are set, it's in DENYLIST mode
-    mFirewallType = DENYLIST;
+    // If no rules are set, it's in BLACKLIST mode
+    mFirewallType = BLACKLIST;
     mIfaceRules = {};
 }
 
 int FirewallController::setupIptablesHooks(void) {
     int res = 0;
-    // mUseBpfOwnerMatch should be removed, but it is still depended upon by test code.
-    mUseBpfOwnerMatch = true;
+    mUseBpfOwnerMatch = getBpfOwnerStatus();
     if (mUseBpfOwnerMatch) {
         return res;
     }
     res |= createChain(LOCAL_DOZABLE, getFirewallType(DOZABLE));
     res |= createChain(LOCAL_STANDBY, getFirewallType(STANDBY));
     res |= createChain(LOCAL_POWERSAVE, getFirewallType(POWERSAVE));
-    res |= createChain(LOCAL_RESTRICTED, getFirewallType(RESTRICTED));
     return res;
 }
 
@@ -109,7 +110,7 @@
         // flush any existing rules
         resetFirewall();
 
-        if (ftype == ALLOWLIST) {
+        if (ftype == WHITELIST) {
             // create default rule to drop all traffic
             std::string command =
                 "*filter\n"
@@ -120,14 +121,14 @@
             res = execIptablesRestore(V4V6, command.c_str());
         }
 
-        // Set this after calling disableFirewall(), since it defaults to ALLOWLIST there
+        // Set this after calling disableFirewall(), since it defaults to WHITELIST there
         mFirewallType = ftype;
     }
     return res ? -EREMOTEIO : 0;
 }
 
 int FirewallController::resetFirewall(void) {
-    mFirewallType = ALLOWLIST;
+    mFirewallType = WHITELIST;
     mIfaceRules.clear();
 
     // flush any existing rules
@@ -154,9 +155,6 @@
         case POWERSAVE:
             name = LOCAL_POWERSAVE;
             break;
-        case RESTRICTED:
-            name = LOCAL_RESTRICTED;
-            break;
         default:
             return res;
     }
@@ -180,8 +178,8 @@
 }
 
 int FirewallController::setInterfaceRule(const char* iface, FirewallRule rule) {
-    if (mFirewallType == DENYLIST) {
-        // Unsupported in DENYLIST mode
+    if (mFirewallType == BLACKLIST) {
+        // Unsupported in BLACKLIST mode
         return -EINVAL;
     }
 
@@ -216,17 +214,15 @@
 FirewallType FirewallController::getFirewallType(ChildChain chain) {
     switch(chain) {
         case DOZABLE:
-            return ALLOWLIST;
+            return WHITELIST;
         case STANDBY:
-            return DENYLIST;
+            return BLACKLIST;
         case POWERSAVE:
-            return ALLOWLIST;
-        case RESTRICTED:
-            return ALLOWLIST;
+            return WHITELIST;
         case NONE:
             return mFirewallType;
         default:
-            return DENYLIST;
+            return BLACKLIST;
     }
 }
 
@@ -234,11 +230,11 @@
     const char* op;
     const char* target;
     FirewallType firewallType = getFirewallType(chain);
-    if (firewallType == ALLOWLIST) {
+    if (firewallType == WHITELIST) {
         target = "RETURN";
         // When adding, insert RETURN rules at the front, before the catch-all DROP at the end.
         op = (rule == ALLOW)? "-I" : "-D";
-    } else {  // DENYLIST mode
+    } else { // BLACKLIST mode
         target = "DROP";
         // When adding, append DROP rules at the end, after the RETURN rule that matches TCP RSTs.
         op = (rule == DENY)? "-A" : "-D";
@@ -247,19 +243,16 @@
     std::vector<std::string> chainNames;
     switch(chain) {
         case DOZABLE:
-            chainNames = {LOCAL_DOZABLE};
+            chainNames = { LOCAL_DOZABLE };
             break;
         case STANDBY:
-            chainNames = {LOCAL_STANDBY};
+            chainNames = { LOCAL_STANDBY };
             break;
         case POWERSAVE:
-            chainNames = {LOCAL_POWERSAVE};
-            break;
-        case RESTRICTED:
-            chainNames = {LOCAL_RESTRICTED};
+            chainNames = { LOCAL_POWERSAVE };
             break;
         case NONE:
-            chainNames = {LOCAL_INPUT, LOCAL_OUTPUT};
+            chainNames = { LOCAL_INPUT, LOCAL_OUTPUT };
             break;
         default:
             ALOGW("Unknown child chain: %d", chain);
@@ -281,7 +274,7 @@
 
 int FirewallController::createChain(const char* chain, FirewallType type) {
     static const std::vector<int32_t> NO_UIDS;
-    return replaceUidChain(chain, type == ALLOWLIST, NO_UIDS);
+    return replaceUidChain(chain, type == WHITELIST, NO_UIDS);
 }
 
 /* static */
@@ -297,18 +290,18 @@
     return commands;
 }
 
-std::string FirewallController::makeUidRules(IptablesTarget target, const char* name,
-                                             bool isAllowlist, const std::vector<int32_t>& uids) {
+std::string FirewallController::makeUidRules(IptablesTarget target, const char *name,
+        bool isWhitelist, const std::vector<int32_t>& uids) {
     std::string commands;
     StringAppendF(&commands, "*filter\n:%s -\n", name);
 
-    // Allowlist chains have UIDs at the beginning, and new UIDs are added with '-I'.
-    if (isAllowlist) {
+    // Whitelist chains have UIDs at the beginning, and new UIDs are added with '-I'.
+    if (isWhitelist) {
         for (auto uid : uids) {
             StringAppendF(&commands, "-A %s -m owner --uid-owner %d -j RETURN\n", name, uid);
         }
 
-        // Always allowlist system UIDs.
+        // Always whitelist system UIDs.
         StringAppendF(&commands,
                 "-A %s -m owner --uid-owner %d-%d -j RETURN\n", name, 0, MAX_SYSTEM_UID);
 
@@ -317,7 +310,7 @@
         StringAppendF(&commands,
                 "-A %s -m owner ! --uid-owner %d-%u -j RETURN\n", name, 0, mMaxUid);
 
-        // Always allowlist traffic with protocol ESP, or no known socket - required for IPSec
+        // Always whitelist traffic with protocol ESP, or no known socket - required for IPSec
         StringAppendF(&commands, "-A %s -p esp -j RETURN\n", name);
     }
 
@@ -329,20 +322,20 @@
     // access. Both incoming and outgoing RSTs are allowed.
     StringAppendF(&commands, "-A %s -p tcp --tcp-flags RST RST -j RETURN\n", name);
 
-    if (isAllowlist) {
+    if (isWhitelist) {
         commands.append(makeCriticalCommands(target, name));
     }
 
-    // Denylist chains have UIDs at the end, and new UIDs are added with '-A'.
-    if (!isAllowlist) {
+    // Blacklist chains have UIDs at the end, and new UIDs are added with '-A'.
+    if (!isWhitelist) {
         for (auto uid : uids) {
             StringAppendF(&commands, "-A %s -m owner --uid-owner %d -j DROP\n", name, uid);
         }
     }
 
-    // If it's an allowlist chain, add a default DROP at the end. This is not necessary for a
-    // denylist chain, because all user-defined chains implicitly RETURN at the end.
-    if (isAllowlist) {
+    // If it's a whitelist chain, add a default DROP at the end. This is not necessary for a
+    // blacklist chain, because all user-defined chains implicitly RETURN at the end.
+    if (isWhitelist) {
         StringAppendF(&commands, "-A %s -j DROP\n", name);
     }
 
@@ -351,13 +344,13 @@
     return commands;
 }
 
-int FirewallController::replaceUidChain(const std::string& name, bool isAllowlist,
-                                        const std::vector<int32_t>& uids) {
+int FirewallController::replaceUidChain(
+        const std::string &name, bool isWhitelist, const std::vector<int32_t>& uids) {
     if (mUseBpfOwnerMatch) {
-        return gCtls->trafficCtrl.replaceUidOwnerMap(name, isAllowlist, uids);
+        return gCtls->trafficCtrl.replaceUidOwnerMap(name, isWhitelist, uids);
     }
-    std::string commands4 = makeUidRules(V4, name.c_str(), isAllowlist, uids);
-    std::string commands6 = makeUidRules(V6, name.c_str(), isAllowlist, uids);
+    std::string commands4 = makeUidRules(V4, name.c_str(), isWhitelist, uids);
+    std::string commands6 = makeUidRules(V6, name.c_str(), isWhitelist, uids);
     return execIptablesRestore(V4, commands4.c_str()) | execIptablesRestore(V6, commands6.c_str());
 }
 
@@ -405,4 +398,4 @@
 }
 
 }  // namespace net
-}  // namespace android
+}  // namespace android
\ No newline at end of file
diff --git a/server/FirewallController.h b/server/FirewallController.h
index 6cabfb5..620f196 100644
--- a/server/FirewallController.h
+++ b/server/FirewallController.h
@@ -33,17 +33,16 @@
 
 enum FirewallRule { ALLOW = INetd::FIREWALL_RULE_ALLOW, DENY = INetd::FIREWALL_RULE_DENY };
 
-// ALLOWLIST means the firewall denies all by default, uids must be explicitly ALLOWed
-// DENYLIST means the firewall allows all by default, uids must be explicitly DENYed
+// WHITELIST means the firewall denies all by default, uids must be explicitly ALLOWed
+// BLACKLIST means the firewall allows all by default, uids must be explicitly DENYed
 
-enum FirewallType { ALLOWLIST = INetd::FIREWALL_ALLOWLIST, DENYLIST = INetd::FIREWALL_DENYLIST };
+enum FirewallType { WHITELIST = INetd::FIREWALL_WHITELIST, BLACKLIST = INetd::FIREWALL_BLACKLIST };
 
 enum ChildChain {
     NONE = INetd::FIREWALL_CHAIN_NONE,
     DOZABLE = INetd::FIREWALL_CHAIN_DOZABLE,
     STANDBY = INetd::FIREWALL_CHAIN_STANDBY,
     POWERSAVE = INetd::FIREWALL_CHAIN_POWERSAVE,
-    RESTRICTED = INetd::FIREWALL_CHAIN_RESTRICTED,
     INVALID_CHAIN
 };
 
@@ -86,7 +85,6 @@
     static const char* LOCAL_DOZABLE;
     static const char* LOCAL_STANDBY;
     static const char* LOCAL_POWERSAVE;
-    static const char* LOCAL_RESTRICTED;
 
     static const char* ICMPV6_TYPES[];
 
@@ -94,7 +92,7 @@
 
 protected:
     friend class FirewallControllerTest;
-    std::string makeUidRules(IptablesTarget target, const char* name, bool isAllowlist,
+    std::string makeUidRules(IptablesTarget target, const char *name, bool isWhitelist,
                              const std::vector<int32_t>& uids);
     static int (*execIptablesRestore)(IptablesTarget target, const std::string& commands);
 
diff --git a/server/FirewallControllerTest.cpp b/server/FirewallControllerTest.cpp
index df6ca82..1b53fb8 100644
--- a/server/FirewallControllerTest.cpp
+++ b/server/FirewallControllerTest.cpp
@@ -56,58 +56,61 @@
     }
 };
 
-TEST_F(FirewallControllerTest, TestCreateAllowlistChain) {
+TEST_F(FirewallControllerTest, TestCreateWhitelistChain) {
     std::vector<std::string> expectedRestore4 = {
-            "*filter",
-            ":fw_allowlist -",
-            "-A fw_allowlist -m owner --uid-owner 0-9999 -j RETURN",
-            "-A fw_allowlist -m owner ! --uid-owner 0-4294967294 -j RETURN",
-            "-A fw_allowlist -p esp -j RETURN",
-            "-A fw_allowlist -i lo -j RETURN",
-            "-A fw_allowlist -o lo -j RETURN",
-            "-A fw_allowlist -p tcp --tcp-flags RST RST -j RETURN",
-            "-A fw_allowlist -j DROP",
-            "COMMIT\n"};
+        "*filter",
+        ":fw_whitelist -",
+        "-A fw_whitelist -m owner --uid-owner 0-9999 -j RETURN",
+        "-A fw_whitelist -m owner ! --uid-owner 0-4294967294 -j RETURN",
+        "-A fw_whitelist -p esp -j RETURN",
+        "-A fw_whitelist -i lo -j RETURN",
+        "-A fw_whitelist -o lo -j RETURN",
+        "-A fw_whitelist -p tcp --tcp-flags RST RST -j RETURN",
+        "-A fw_whitelist -j DROP",
+        "COMMIT\n"
+    };
     std::vector<std::string> expectedRestore6 = {
-            "*filter",
-            ":fw_allowlist -",
-            "-A fw_allowlist -m owner --uid-owner 0-9999 -j RETURN",
-            "-A fw_allowlist -m owner ! --uid-owner 0-4294967294 -j RETURN",
-            "-A fw_allowlist -p esp -j RETURN",
-            "-A fw_allowlist -i lo -j RETURN",
-            "-A fw_allowlist -o lo -j RETURN",
-            "-A fw_allowlist -p tcp --tcp-flags RST RST -j RETURN",
-            "-A fw_allowlist -p icmpv6 --icmpv6-type packet-too-big -j RETURN",
-            "-A fw_allowlist -p icmpv6 --icmpv6-type router-solicitation -j RETURN",
-            "-A fw_allowlist -p icmpv6 --icmpv6-type router-advertisement -j RETURN",
-            "-A fw_allowlist -p icmpv6 --icmpv6-type neighbour-solicitation -j RETURN",
-            "-A fw_allowlist -p icmpv6 --icmpv6-type neighbour-advertisement -j RETURN",
-            "-A fw_allowlist -p icmpv6 --icmpv6-type redirect -j RETURN",
-            "-A fw_allowlist -j DROP",
-            "COMMIT\n"};
+        "*filter",
+        ":fw_whitelist -",
+        "-A fw_whitelist -m owner --uid-owner 0-9999 -j RETURN",
+        "-A fw_whitelist -m owner ! --uid-owner 0-4294967294 -j RETURN",
+        "-A fw_whitelist -p esp -j RETURN",
+        "-A fw_whitelist -i lo -j RETURN",
+        "-A fw_whitelist -o lo -j RETURN",
+        "-A fw_whitelist -p tcp --tcp-flags RST RST -j RETURN",
+        "-A fw_whitelist -p icmpv6 --icmpv6-type packet-too-big -j RETURN",
+        "-A fw_whitelist -p icmpv6 --icmpv6-type router-solicitation -j RETURN",
+        "-A fw_whitelist -p icmpv6 --icmpv6-type router-advertisement -j RETURN",
+        "-A fw_whitelist -p icmpv6 --icmpv6-type neighbour-solicitation -j RETURN",
+        "-A fw_whitelist -p icmpv6 --icmpv6-type neighbour-advertisement -j RETURN",
+        "-A fw_whitelist -p icmpv6 --icmpv6-type redirect -j RETURN",
+        "-A fw_whitelist -j DROP",
+        "COMMIT\n"
+    };
     std::vector<std::pair<IptablesTarget, std::string>> expectedRestoreCommands = {
             {V4, Join(expectedRestore4, '\n')},
             {V6, Join(expectedRestore6, '\n')},
     };
 
-    createChain("fw_allowlist", ALLOWLIST);
+    createChain("fw_whitelist", WHITELIST);
     expectIptablesRestoreCommands(expectedRestoreCommands);
 }
 
-TEST_F(FirewallControllerTest, TestCreateDenylistChain) {
+TEST_F(FirewallControllerTest, TestCreateBlacklistChain) {
     std::vector<std::string> expectedRestore = {
-            "*filter",
-            ":fw_denylist -",
-            "-A fw_denylist -i lo -j RETURN",
-            "-A fw_denylist -o lo -j RETURN",
-            "-A fw_denylist -p tcp --tcp-flags RST RST -j RETURN",
-            "COMMIT\n"};
+        "*filter",
+        ":fw_blacklist -",
+        "-A fw_blacklist -i lo -j RETURN",
+        "-A fw_blacklist -o lo -j RETURN",
+        "-A fw_blacklist -p tcp --tcp-flags RST RST -j RETURN",
+        "COMMIT\n"
+    };
     std::vector<std::pair<IptablesTarget, std::string>> expectedRestoreCommands = {
             {V4, Join(expectedRestore, '\n')},
             {V6, Join(expectedRestore, '\n')},
     };
 
-    createChain("fw_denylist", DENYLIST);
+    createChain("fw_blacklist", BLACKLIST);
     expectIptablesRestoreCommands(expectedRestoreCommands);
 }
 
@@ -159,50 +162,50 @@
     expectIptablesRestoreCommands(expected);
 }
 
-TEST_F(FirewallControllerTest, TestReplaceAllowlistUidRule) {
+TEST_F(FirewallControllerTest, TestReplaceWhitelistUidRule) {
     std::string expected =
             "*filter\n"
-            ":FW_allowchain -\n"
-            "-A FW_allowchain -m owner --uid-owner 10023 -j RETURN\n"
-            "-A FW_allowchain -m owner --uid-owner 10059 -j RETURN\n"
-            "-A FW_allowchain -m owner --uid-owner 10124 -j RETURN\n"
-            "-A FW_allowchain -m owner --uid-owner 10111 -j RETURN\n"
-            "-A FW_allowchain -m owner --uid-owner 110122 -j RETURN\n"
-            "-A FW_allowchain -m owner --uid-owner 210153 -j RETURN\n"
-            "-A FW_allowchain -m owner --uid-owner 210024 -j RETURN\n"
-            "-A FW_allowchain -m owner --uid-owner 0-9999 -j RETURN\n"
-            "-A FW_allowchain -m owner ! --uid-owner 0-4294967294 -j RETURN\n"
-            "-A FW_allowchain -p esp -j RETURN\n"
-            "-A FW_allowchain -i lo -j RETURN\n"
-            "-A FW_allowchain -o lo -j RETURN\n"
-            "-A FW_allowchain -p tcp --tcp-flags RST RST -j RETURN\n"
-            "-A FW_allowchain -p icmpv6 --icmpv6-type packet-too-big -j RETURN\n"
-            "-A FW_allowchain -p icmpv6 --icmpv6-type router-solicitation -j RETURN\n"
-            "-A FW_allowchain -p icmpv6 --icmpv6-type router-advertisement -j RETURN\n"
-            "-A FW_allowchain -p icmpv6 --icmpv6-type neighbour-solicitation -j RETURN\n"
-            "-A FW_allowchain -p icmpv6 --icmpv6-type neighbour-advertisement -j RETURN\n"
-            "-A FW_allowchain -p icmpv6 --icmpv6-type redirect -j RETURN\n"
-            "-A FW_allowchain -j DROP\n"
+            ":FW_whitechain -\n"
+            "-A FW_whitechain -m owner --uid-owner 10023 -j RETURN\n"
+            "-A FW_whitechain -m owner --uid-owner 10059 -j RETURN\n"
+            "-A FW_whitechain -m owner --uid-owner 10124 -j RETURN\n"
+            "-A FW_whitechain -m owner --uid-owner 10111 -j RETURN\n"
+            "-A FW_whitechain -m owner --uid-owner 110122 -j RETURN\n"
+            "-A FW_whitechain -m owner --uid-owner 210153 -j RETURN\n"
+            "-A FW_whitechain -m owner --uid-owner 210024 -j RETURN\n"
+            "-A FW_whitechain -m owner --uid-owner 0-9999 -j RETURN\n"
+            "-A FW_whitechain -m owner ! --uid-owner 0-4294967294 -j RETURN\n"
+            "-A FW_whitechain -p esp -j RETURN\n"
+            "-A FW_whitechain -i lo -j RETURN\n"
+            "-A FW_whitechain -o lo -j RETURN\n"
+            "-A FW_whitechain -p tcp --tcp-flags RST RST -j RETURN\n"
+            "-A FW_whitechain -p icmpv6 --icmpv6-type packet-too-big -j RETURN\n"
+            "-A FW_whitechain -p icmpv6 --icmpv6-type router-solicitation -j RETURN\n"
+            "-A FW_whitechain -p icmpv6 --icmpv6-type router-advertisement -j RETURN\n"
+            "-A FW_whitechain -p icmpv6 --icmpv6-type neighbour-solicitation -j RETURN\n"
+            "-A FW_whitechain -p icmpv6 --icmpv6-type neighbour-advertisement -j RETURN\n"
+            "-A FW_whitechain -p icmpv6 --icmpv6-type redirect -j RETURN\n"
+            "-A FW_whitechain -j DROP\n"
             "COMMIT\n";
 
     std::vector<int32_t> uids = { 10023, 10059, 10124, 10111, 110122, 210153, 210024 };
-    EXPECT_EQ(expected, makeUidRules(V6, "FW_allowchain", true, uids));
+    EXPECT_EQ(expected, makeUidRules(V6, "FW_whitechain", true, uids));
 }
 
-TEST_F(FirewallControllerTest, TestReplaceDenylistUidRule) {
+TEST_F(FirewallControllerTest, TestReplaceBlacklistUidRule) {
     std::string expected =
             "*filter\n"
-            ":FW_denychain -\n"
-            "-A FW_denychain -i lo -j RETURN\n"
-            "-A FW_denychain -o lo -j RETURN\n"
-            "-A FW_denychain -p tcp --tcp-flags RST RST -j RETURN\n"
-            "-A FW_denychain -m owner --uid-owner 10023 -j DROP\n"
-            "-A FW_denychain -m owner --uid-owner 10059 -j DROP\n"
-            "-A FW_denychain -m owner --uid-owner 10124 -j DROP\n"
+            ":FW_blackchain -\n"
+            "-A FW_blackchain -i lo -j RETURN\n"
+            "-A FW_blackchain -o lo -j RETURN\n"
+            "-A FW_blackchain -p tcp --tcp-flags RST RST -j RETURN\n"
+            "-A FW_blackchain -m owner --uid-owner 10023 -j DROP\n"
+            "-A FW_blackchain -m owner --uid-owner 10059 -j DROP\n"
+            "-A FW_blackchain -m owner --uid-owner 10124 -j DROP\n"
             "COMMIT\n";
 
     std::vector<int32_t> uids = { 10023, 10059, 10124 };
-    EXPECT_EQ(expected, makeUidRules(V4, "FW_denychain", false, uids));
+    EXPECT_EQ(expected, makeUidRules(V4 ,"FW_blackchain", false, uids));
 }
 
 TEST_F(FirewallControllerTest, TestEnableChildChains) {
@@ -248,10 +251,10 @@
     EXPECT_EQ(0, mFw.resetFirewall());
     expectIptablesRestoreCommands(disableCommands);
 
-    EXPECT_EQ(0, mFw.setFirewallType(DENYLIST));
+    EXPECT_EQ(0, mFw.setFirewallType(BLACKLIST));
     expectIptablesRestoreCommands(disableCommands);
 
-    EXPECT_EQ(0, mFw.setFirewallType(DENYLIST));
+    EXPECT_EQ(0, mFw.setFirewallType(BLACKLIST));
     expectIptablesRestoreCommands(noCommands);
 
     std::vector<std::string> disableEnableCommands;
@@ -260,7 +263,7 @@
     disableEnableCommands.insert(
             disableEnableCommands.end(), enableCommands.begin(), enableCommands.end());
 
-    EXPECT_EQ(0, mFw.setFirewallType(ALLOWLIST));
+    EXPECT_EQ(0, mFw.setFirewallType(WHITELIST));
     expectIptablesRestoreCommands(disableEnableCommands);
 
     std::vector<std::string> ifaceCommands = {
@@ -287,15 +290,15 @@
     EXPECT_EQ(0, mFw.setInterfaceRule("rmnet_data0", DENY));
     expectIptablesRestoreCommands(noCommands);
 
-    EXPECT_EQ(0, mFw.setFirewallType(ALLOWLIST));
+    EXPECT_EQ(0, mFw.setFirewallType(WHITELIST));
     expectIptablesRestoreCommands(noCommands);
 
     EXPECT_EQ(0, mFw.resetFirewall());
     expectIptablesRestoreCommands(disableCommands);
 
-    // TODO: calling resetFirewall and then setFirewallType(ALLOWLIST) does
+    // TODO: calling resetFirewall and then setFirewallType(WHITELIST) does
     // nothing. This seems like a clear bug.
-    EXPECT_EQ(0, mFw.setFirewallType(ALLOWLIST));
+    EXPECT_EQ(0, mFw.setFirewallType(WHITELIST));
     expectIptablesRestoreCommands(noCommands);
 }
 
diff --git a/server/IdletimerController.cpp b/server/IdletimerController.cpp
index cc86d1b..103e7cd 100644
--- a/server/IdletimerController.cpp
+++ b/server/IdletimerController.cpp
@@ -39,8 +39,8 @@
  * # For notifications to work the lable name must match the name of a valid interface.
  * # If the label name does match an interface, the rules will be a no-op.
  *
- * iptables -t raw -A idletimer_PREROUTING -i rmnet0 -j IDLETIMER  --timeout 5 --label test-chain --send_nl_msg
- * iptables -t mangle -A idletimer_POSTROUTING -o rmnet0 -j IDLETIMER  --timeout 5 --label test-chain --send_nl_msg
+ * iptables -t raw -A idletimer_PREROUTING -i rmnet0 -j IDLETIMER  --timeout 5 --label test-chain --send_nl_msg 1
+ * iptables -t mangle -A idletimer_POSTROUTING -o rmnet0 -j IDLETIMER  --timeout 5 --label test-chain --send_nl_msg 1
  *
  * iptables -nxvL -t raw
  * iptables -nxvL -t mangle
@@ -147,14 +147,14 @@
 
     const char *addRemove = (op == IptOpAdd) ? "-A" : "-D";
     std::vector<std::string> cmds = {
-            "*raw",
-            StringPrintf("%s %s -i %s -j IDLETIMER --timeout %u --label %s --send_nl_msg",
-                         addRemove, LOCAL_RAW_PREROUTING, iface, timeout, classLabel),
-            "COMMIT",
-            "*mangle",
-            StringPrintf("%s %s -o %s -j IDLETIMER --timeout %u --label %s --send_nl_msg",
-                         addRemove, LOCAL_MANGLE_POSTROUTING, iface, timeout, classLabel),
-            "COMMIT\n",
+        "*raw",
+        StringPrintf("%s %s -i %s -j IDLETIMER --timeout %u --label %s --send_nl_msg 1",
+                    addRemove, LOCAL_RAW_PREROUTING, iface, timeout, classLabel),
+        "COMMIT",
+        "*mangle",
+        StringPrintf("%s %s -o %s -j IDLETIMER --timeout %u --label %s --send_nl_msg 1",
+                    addRemove, LOCAL_MANGLE_POSTROUTING, iface, timeout, classLabel),
+        "COMMIT\n",
     };
 
     return (execIptablesRestore(V4V6, Join(cmds, '\n')) == 0) ? 0 : -EREMOTEIO;
diff --git a/server/IdletimerControllerTest.cpp b/server/IdletimerControllerTest.cpp
index 68d6108..30c2298 100644
--- a/server/IdletimerControllerTest.cpp
+++ b/server/IdletimerControllerTest.cpp
@@ -43,16 +43,14 @@
 const std::vector<std::string> makeAddRemoveCommands(bool add) {
     const char *op = add ? "-A" : "-D";
     std::vector<std::string> cmds = {
-            "*raw",
-            StringPrintf("%s idletimer_raw_PREROUTING -i wlan0 -j IDLETIMER"
-                         " --timeout 12345 --label hello --send_nl_msg",
-                         op),
-            "COMMIT",
-            "*mangle",
-            StringPrintf("%s idletimer_mangle_POSTROUTING -o wlan0 -j IDLETIMER"
-                         " --timeout 12345 --label hello --send_nl_msg",
-                         op),
-            "COMMIT\n",
+        "*raw",
+        StringPrintf("%s idletimer_raw_PREROUTING -i wlan0 -j IDLETIMER"
+                     " --timeout 12345 --label hello --send_nl_msg 1", op),
+        "COMMIT",
+        "*mangle",
+        StringPrintf("%s idletimer_mangle_POSTROUTING -o wlan0 -j IDLETIMER"
+                     " --timeout 12345 --label hello --send_nl_msg 1", op),
+        "COMMIT\n",
     };
     return { Join(cmds, '\n') };
 }
diff --git a/server/IptablesRestoreControllerTest.cpp b/server/IptablesRestoreControllerTest.cpp
index 3881124..20f6183 100644
--- a/server/IptablesRestoreControllerTest.cpp
+++ b/server/IptablesRestoreControllerTest.cpp
@@ -40,14 +40,7 @@
 #define XT_LOCK_ATTEMPTS 10
 #define XT_LOCK_POLL_INTERVAL_MS 100
 
-#define PROC_STAT_MIN_ELEMENTS 52U
-#define PROC_STAT_RSS_INDEX 23U
-
-#define IPTABLES_COMM "(iptables-restor)"
-#define IP6TABLES_COMM "(ip6tables-resto)"
-
 using android::base::Join;
-using android::base::StringAppendF;
 using android::base::StringPrintf;
 using android::netdutils::ScopedMockSyscalls;
 using android::netdutils::Stopwatch;
@@ -82,29 +75,10 @@
       return con.getIpRestorePid(type);
   };
 
-  const std::string getProcStatPath(pid_t pid) { return StringPrintf("/proc/%d/stat", pid); }
-
-  std::vector<std::string> parseProcStat(int fd, const std::string& path) {
-      std::vector<std::string> procStat;
-
-      char statBuf[1024];
-      EXPECT_NE(-1, read(fd, statBuf, sizeof(statBuf)))
-              << "Could not read from " << path << ": " << strerror(errno);
-
-      std::stringstream stream(statBuf);
-      std::string item;
-      while (std::getline(stream, item, ' ')) {
-          procStat.push_back(item);
-      }
-
-      EXPECT_LE(PROC_STAT_MIN_ELEMENTS, procStat.size()) << "Too few elements in " << path;
-      return procStat;
-  }
-
   void expectNoIptablesRestoreProcess(pid_t pid) {
     // We can't readlink /proc/PID/exe, because zombie processes don't have it.
     // Parse /proc/PID/stat instead.
-    std::string statPath = getProcStatPath(pid);
+    std::string statPath = StringPrintf("/proc/%d/stat", pid);
     int fd = open(statPath.c_str(), O_RDONLY | O_CLOEXEC);
     if (fd == -1) {
       // ENOENT means the process is gone (expected).
@@ -115,28 +89,14 @@
 
     // If the PID exists, it's possible (though very unlikely) that the PID was reused. Check the
     // binary name as well, to ensure the test isn't flaky.
-    std::vector<std::string> procStat = parseProcStat(fd, statPath);
-    EXPECT_FALSE(procStat[1] == IPTABLES_COMM || procStat[1] == IP6TABLES_COMM)
-            << "Previous iptables-restore or ip6tables-restore pid " << pid
-            << " still alive: " << Join(procStat, " ");
-
+    char statBuf[1024];
+    ASSERT_NE(-1, read(fd, statBuf, sizeof(statBuf)))
+        << "Could not read from " << statPath << ": " << strerror(errno);
     close(fd);
-  }
 
-  int getRssPages(pid_t pid) {
-      std::string statPath = getProcStatPath(pid);
-      int fd = open(statPath.c_str(), O_RDONLY | O_CLOEXEC);
-      EXPECT_NE(-1, fd) << "Unexpected error opening " << statPath << ": " << strerror(errno);
-      if (fd == -1) return 0;
-
-      const auto& procStat = parseProcStat(fd, statPath);
-      close(fd);
-
-      if (procStat.size() < PROC_STAT_MIN_ELEMENTS) return 0;
-      EXPECT_TRUE(procStat[1] == IPTABLES_COMM || procStat[1] == IP6TABLES_COMM)
-              << statPath << " is for unexpected process: " << procStat[1];
-
-      return std::atoi(procStat[PROC_STAT_RSS_INDEX].c_str());
+    std::string statString(statBuf);
+    EXPECT_FALSE(statString.find("iptables-restor") || statString.find("ip6tables-resto"))
+      << "Previous iptables-restore pid " << pid << " still alive: " << statString;
   }
 
   int createTestChain() {
@@ -257,7 +217,7 @@
 
 TEST_F(IptablesRestoreControllerTest, TestCommandTimeout) {
   // Don't wait 10 seconds for this test to fail.
-  setRetryParameters(3, 100);
+  setRetryParameters(3, 50);
 
   // Expected contents of the chain.
   std::vector<std::string> expectedLines = {
@@ -339,44 +299,3 @@
   EXPECT_EQ(-1, con.execute(V4V6, "malformed command\n", nullptr));
   EXPECT_EQ(0, con.execute(V4V6, "#Test\n", nullptr));
 }
-
-TEST_F(IptablesRestoreControllerTest, TestMemoryLeak) {
-    std::string cmd = "*filter\n";
-
-    // Keep command within PIPE_BUF (4096) just to make sure. Each line is 60 bytes including \n:
-    // -I netd_unit_test_9999 -p udp -m udp --sport 12345 -j DROP
-    for (int i = 0; i < 33; i++) {
-        StringAppendF(&cmd, "-I %s -p udp -m udp --sport 12345 -j DROP\n", mChainName.c_str());
-        StringAppendF(&cmd, "-D %s -p udp -m udp --sport 12345 -j DROP\n", mChainName.c_str());
-    }
-    StringAppendF(&cmd, "COMMIT\n");
-    ASSERT_GE(4096U, cmd.size());
-
-    // Run the command once in case it causes the first allocations for these iptables-restore
-    // processes, and check they don't crash.
-    pid_t pid4 = getIpRestorePid(IptablesRestoreController::IPTABLES_PROCESS);
-    pid_t pid6 = getIpRestorePid(IptablesRestoreController::IP6TABLES_PROCESS);
-    std::string output;
-    EXPECT_EQ(0, con.execute(IptablesTarget::V4V6, cmd, nullptr));
-    EXPECT_EQ(pid4, getIpRestorePid(IptablesRestoreController::IPTABLES_PROCESS));
-    EXPECT_EQ(pid6, getIpRestorePid(IptablesRestoreController::IP6TABLES_PROCESS));
-
-    // Check how much RAM the processes are using.
-    int pages4 = getRssPages(pid4);
-    ASSERT_NE(0, pages4);
-    int pages6 = getRssPages(pid6);
-    ASSERT_NE(0, pages6);
-
-    // Run the command a few times and check that it doesn't crash.
-    for (int i = 0; i < 10; i++) {
-        EXPECT_EQ(0, con.execute(IptablesTarget::V4V6, cmd, nullptr));
-    }
-    EXPECT_EQ(pid4, getIpRestorePid(IptablesRestoreController::IPTABLES_PROCESS));
-    EXPECT_EQ(pid6, getIpRestorePid(IptablesRestoreController::IP6TABLES_PROCESS));
-
-    // Don't allow a leak of more than 25 pages (100kB).
-    // This is more than enough for accuracy: the leak in b/162925719 fails with:
-    // Expected: (25U) >= (getRssPages(pid4) - pages4), actual: 5 vs 66
-    EXPECT_GE(25, getRssPages(pid4) - pages4) << "iptables-restore leaked too many pages";
-    EXPECT_GE(25, getRssPages(pid6) - pages6) << "ip6tables-restore leaked too many pages";
-}
diff --git a/server/LocalNetwork.cpp b/server/LocalNetwork.cpp
index 7127b3f..91fda3c 100644
--- a/server/LocalNetwork.cpp
+++ b/server/LocalNetwork.cpp
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "Netd"
-
 #include "LocalNetwork.h"
 
 #include "RouteController.h"
 
+#define LOG_TAG "Netd"
 #include "log/log.h"
 
 namespace android {
@@ -31,6 +30,10 @@
 LocalNetwork::~LocalNetwork() {
 }
 
+Network::Type LocalNetwork::getType() const {
+    return LOCAL;
+}
+
 int LocalNetwork::addInterface(const std::string& interface) {
     if (hasInterface(interface)) {
         return 0;
diff --git a/server/LocalNetwork.h b/server/LocalNetwork.h
index c774067..7a39a9d 100644
--- a/server/LocalNetwork.h
+++ b/server/LocalNetwork.h
@@ -26,7 +26,7 @@
     virtual ~LocalNetwork();
 
 private:
-    std::string getTypeString() const override { return "LOCAL"; };
+    Type getType() const override;
     [[nodiscard]] int addInterface(const std::string& interface) override;
     [[nodiscard]] int removeInterface(const std::string& interface) override;
 };
diff --git a/server/NdcDispatcher.cpp b/server/NdcDispatcher.cpp
index 80ad7fb..48f7d9f 100644
--- a/server/NdcDispatcher.cpp
+++ b/server/NdcDispatcher.cpp
@@ -747,13 +747,13 @@
 }
 
 int NdcDispatcher::FirewallCmd::parseFirewallType(const char* arg) {
-    if (!strcmp(arg, "allowlist")) {
-        return INetd::FIREWALL_ALLOWLIST;
-    } else if (!strcmp(arg, "denylist")) {
-        return INetd::FIREWALL_DENYLIST;
+    if (!strcmp(arg, "whitelist")) {
+        return INetd::FIREWALL_WHITELIST;
+    } else if (!strcmp(arg, "blacklist")) {
+        return INetd::FIREWALL_BLACKLIST;
     } else {
         LOG(LOGLEVEL) << "failed to parse firewall type " << arg;
-        return INetd::FIREWALL_DENYLIST;
+        return INetd::FIREWALL_BLACKLIST;
     }
 }
 
@@ -764,8 +764,6 @@
         return INetd::FIREWALL_CHAIN_STANDBY;
     } else if (!strcmp(arg, "powersave")) {
         return INetd::FIREWALL_CHAIN_POWERSAVE;
-    } else if (!strcmp(arg, "restricted")) {
-        return INetd::FIREWALL_CHAIN_RESTRICTED;
     } else if (!strcmp(arg, "none")) {
         return INetd::FIREWALL_CHAIN_NONE;
     } else {
@@ -783,7 +781,7 @@
     if (!strcmp(argv[1], "enable")) {
         if (argc != 3) {
             cli->sendMsg(ResponseCode::CommandSyntaxError,
-                         "Usage: firewall enable <allowlist|denylist>", false);
+                         "Usage: firewall enable <whitelist|blacklist>", false);
             return 0;
         }
         int res = !mNetd->firewallSetFirewallType(parseFirewallType(argv[2])).isOk();
@@ -1049,12 +1047,9 @@
             return syntaxError(cli, "Missing argument");
         }
         unsigned netId = stringToNetId(argv[2]);
-        if (argc == 5 && !strcmp(argv[3], "vpn")) {
+        if (argc == 6 && !strcmp(argv[3], "vpn")) {
             bool secure = strtol(argv[4], nullptr, 2);
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
             if (Status status = mNetd->networkCreateVpn(netId, secure); !status.isOk()) {
-#pragma clang diagnostic pop
                 return operationError(cli, "createVirtualNetwork() failed",
                                       status.serviceSpecificErrorCode());
             }
@@ -1068,10 +1063,7 @@
                     return syntaxError(cli, "Unknown permission");
                 }
             }
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
             if (Status status = mNetd->networkCreatePhysical(netId, permission); !status.isOk()) {
-#pragma clang diagnostic pop
                 return operationError(cli, "createPhysicalNetwork() failed",
                                       status.serviceSpecificErrorCode());
             }
diff --git a/server/NetdConstants.cpp b/server/NetdConstants.cpp
index f3898f5..80282b3 100644
--- a/server/NetdConstants.cpp
+++ b/server/NetdConstants.cpp
@@ -66,8 +66,7 @@
     }
 
     for (i = 1; i < name.size(); i++) {
-        if (!isalnum(name[i]) && (name[i] != '_') && (name[i] != '-')
-                && (name[i] != ':') && (name[i] != '.')) {
+        if (!isalnum(name[i]) && (name[i] != '_') && (name[i] != '-') && (name[i] != ':')) {
             return false;
         }
     }
diff --git a/server/NetdNativeService.cpp b/server/NetdNativeService.cpp
index 1f5dc97..3bf879b 100644
--- a/server/NetdNativeService.cpp
+++ b/server/NetdNativeService.cpp
@@ -52,11 +52,9 @@
 
 using android::base::StringPrintf;
 using android::base::WriteStringToFile;
-using android::net::NativeNetworkType;
 using android::net::TetherOffloadRuleParcel;
 using android::net::TetherStatsParcel;
 using android::net::UidRangeParcel;
-using android::net::netd::aidl::NativeUidRangeConfig;
 using android::netdutils::DumpWriter;
 using android::netdutils::ScopedIndent;
 using android::os::ParcelFileDescriptor;
@@ -172,9 +170,8 @@
 
 NetdNativeService::NetdNativeService() {
     // register log callback to BnNetd::logFunc
-    BnNetd::logFunc = [](const auto& log) {
-        binderCallLogFn(log, [](const std::string& msg) { gLog.info("%s", msg.c_str()); });
-    };
+    BnNetd::logFunc = std::bind(binderCallLogFn, std::placeholders::_1,
+                                [](const std::string& msg) { gLog.info("%s", msg.c_str()); });
 }
 
 status_t NetdNativeService::start() {
@@ -271,11 +268,9 @@
 }
 
 binder::Status NetdNativeService::firewallReplaceUidChain(const std::string& chainName,
-                                                          bool isAllowlist,
-                                                          const std::vector<int32_t>& uids,
-                                                          bool* ret) {
+        bool isWhitelist, const std::vector<int32_t>& uids, bool *ret) {
     NETD_LOCKING_RPC(gCtls->firewallCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
-    int err = gCtls->firewallCtrl.replaceUidChain(chainName, isAllowlist, uids);
+    int err = gCtls->firewallCtrl.replaceUidChain(chainName, isWhitelist, uids);
     *ret = (err == 0);
     return binder::Status::ok();
 }
@@ -321,60 +316,41 @@
 
 binder::Status NetdNativeService::bandwidthAddNaughtyApp(int32_t uid) {
     NETD_LOCKING_RPC(gCtls->bandwidthCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
-    std::vector<uint32_t> appUids = {static_cast<uint32_t>(abs(uid))};
-    int res = gCtls->bandwidthCtrl.addNaughtyApps(appUids);
+    std::vector<std::string> appStrUids = {std::to_string(abs(uid))};
+    int res = gCtls->bandwidthCtrl.addNaughtyApps(appStrUids);
     return statusFromErrcode(res);
 }
 
 binder::Status NetdNativeService::bandwidthRemoveNaughtyApp(int32_t uid) {
     NETD_LOCKING_RPC(gCtls->bandwidthCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
-    std::vector<uint32_t> appUids = {static_cast<uint32_t>(abs(uid))};
-    int res = gCtls->bandwidthCtrl.removeNaughtyApps(appUids);
+    std::vector<std::string> appStrUids = {std::to_string(abs(uid))};
+    int res = gCtls->bandwidthCtrl.removeNaughtyApps(appStrUids);
     return statusFromErrcode(res);
 }
 
 binder::Status NetdNativeService::bandwidthAddNiceApp(int32_t uid) {
     NETD_LOCKING_RPC(gCtls->bandwidthCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
-    std::vector<uint32_t> appUids = {static_cast<uint32_t>(abs(uid))};
-    int res = gCtls->bandwidthCtrl.addNiceApps(appUids);
+    std::vector<std::string> appStrUids = {std::to_string(abs(uid))};
+    int res = gCtls->bandwidthCtrl.addNiceApps(appStrUids);
     return statusFromErrcode(res);
 }
 
 binder::Status NetdNativeService::bandwidthRemoveNiceApp(int32_t uid) {
     NETD_LOCKING_RPC(gCtls->bandwidthCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
-    std::vector<uint32_t> appUids = {static_cast<uint32_t>(abs(uid))};
-    int res = gCtls->bandwidthCtrl.removeNiceApps(appUids);
+    std::vector<std::string> appStrUids = {std::to_string(abs(uid))};
+    int res = gCtls->bandwidthCtrl.removeNiceApps(appStrUids);
     return statusFromErrcode(res);
 }
 
-// TODO: Remove this function when there are no users. Currently, it is still used by DNS resolver
-// tests.
 binder::Status NetdNativeService::networkCreatePhysical(int32_t netId, int32_t permission) {
     ENFORCE_NETWORK_STACK_PERMISSIONS();
     int ret = gCtls->netCtrl.createPhysicalNetwork(netId, convertPermission(permission));
     return statusFromErrcode(ret);
 }
 
-// TODO: Remove this function when there are no users. Currently, it is still used by DNS resolver
-// tests.
 binder::Status NetdNativeService::networkCreateVpn(int32_t netId, bool secure) {
     ENFORCE_NETWORK_STACK_PERMISSIONS();
-    // The value of vpnType does not matter here, because it is not used in AOSP and is only
-    // implemented by OEMs. Also, the RPC is going to deprecate. Just pick a value defined in INetd
-    // as default.
-    int ret = gCtls->netCtrl.createVirtualNetwork(netId, secure, NativeVpnType::LEGACY);
-    return statusFromErrcode(ret);
-}
-
-binder::Status NetdNativeService::networkCreate(const NativeNetworkConfig& config) {
-    ENFORCE_NETWORK_STACK_PERMISSIONS();
-    int ret = -EINVAL;
-    if (config.networkType == NativeNetworkType::PHYSICAL) {
-        ret = gCtls->netCtrl.createPhysicalNetwork(config.netId,
-                                                   convertPermission(config.permission));
-    } else if (config.networkType == NativeNetworkType::VIRTUAL) {
-        ret = gCtls->netCtrl.createVirtualNetwork(config.netId, config.secure, config.vpnType);
-    }
+    int ret = gCtls->netCtrl.createVirtualNetwork(netId, secure);
     return statusFromErrcode(ret);
 }
 
@@ -401,8 +377,7 @@
         int32_t netId, const std::vector<UidRangeParcel>& uidRangeArray) {
     // NetworkController::addUsersToNetwork is thread-safe.
     ENFORCE_NETWORK_STACK_PERMISSIONS();
-    int ret = gCtls->netCtrl.addUsersToNetwork(netId, UidRanges(uidRangeArray),
-                                               UidRanges::DEFAULT_SUB_PRIORITY);
+    int ret = gCtls->netCtrl.addUsersToNetwork(netId, UidRanges(uidRangeArray));
     return statusFromErrcode(ret);
 }
 
@@ -410,22 +385,7 @@
         int32_t netId, const std::vector<UidRangeParcel>& uidRangeArray) {
     // NetworkController::removeUsersFromNetwork is thread-safe.
     ENFORCE_NETWORK_STACK_PERMISSIONS();
-    int ret = gCtls->netCtrl.removeUsersFromNetwork(netId, UidRanges(uidRangeArray),
-                                                    UidRanges::DEFAULT_SUB_PRIORITY);
-    return statusFromErrcode(ret);
-}
-
-binder::Status NetdNativeService::networkAddUidRangesParcel(const NativeUidRangeConfig& config) {
-    ENFORCE_NETWORK_STACK_PERMISSIONS();
-    int ret = gCtls->netCtrl.addUsersToNetwork(config.netId, UidRanges(config.uidRanges),
-                                               config.subPriority);
-    return statusFromErrcode(ret);
-}
-
-binder::Status NetdNativeService::networkRemoveUidRangesParcel(const NativeUidRangeConfig& config) {
-    ENFORCE_NETWORK_STACK_PERMISSIONS();
-    int ret = gCtls->netCtrl.removeUsersFromNetwork(config.netId, UidRanges(config.uidRanges),
-                                                    config.subPriority);
+    int ret = gCtls->netCtrl.removeUsersFromNetwork(netId, UidRanges(uidRangeArray));
     return statusFromErrcode(ret);
 }
 
@@ -1160,7 +1120,7 @@
     return binder::Status::ok();
 }
 
-binder::Status NetdNativeService::networkSetProtectAllow(int32_t uid) {
+binder::Status NetdNativeService::NetdNativeService::networkSetProtectAllow(int32_t uid) {
     ENFORCE_NETWORK_STACK_PERMISSIONS();
     std::vector<uid_t> uids = {(uid_t) uid};
     gCtls->netCtrl.allowProtect(uids);
@@ -1291,43 +1251,65 @@
     return binder::Status::ok();
 }
 
-// TODO: remark @deprecated in INetd.aidl.
-binder::Status NetdNativeService::tetherOffloadRuleAdd(const TetherOffloadRuleParcel& /* rule */) {
-    // deprecated
+binder::Status NetdNativeService::tetherOffloadRuleAdd(const TetherOffloadRuleParcel& rule) {
     ENFORCE_NETWORK_STACK_PERMISSIONS();
-    return binder::Status::fromExceptionCode(binder::Status::EX_UNSUPPORTED_OPERATION);
+
+    return asBinderStatus(gCtls->tetherCtrl.addOffloadRule(rule));
 }
 
-// TODO: remark @deprecated in INetd.aidl.
-binder::Status NetdNativeService::tetherOffloadRuleRemove(
-        const TetherOffloadRuleParcel& /* rule */) {
-    // deprecated
+binder::Status NetdNativeService::tetherOffloadRuleRemove(const TetherOffloadRuleParcel& rule) {
     ENFORCE_NETWORK_STACK_PERMISSIONS();
-    return binder::Status::fromExceptionCode(binder::Status::EX_UNSUPPORTED_OPERATION);
+
+    return asBinderStatus(gCtls->tetherCtrl.removeOffloadRule(rule));
 }
 
-// TODO: remark @deprecated in INetd.aidl.
+namespace {
+
+constexpr const char UNUSED_IFNAME[] = "";
+
+TetherStatsParcel toTetherStatsParcel(const TetherController::TetherOffloadStats& stats) {
+    TetherStatsParcel result;
+    result.iface = UNUSED_IFNAME;
+    result.rxBytes = stats.rxBytes;
+    result.rxPackets = stats.rxPackets;
+    result.txBytes = stats.txBytes;
+    result.txPackets = stats.txPackets;
+    result.ifIndex = stats.ifIndex;
+    return result;
+}
+
+}  // namespace
+
 binder::Status NetdNativeService::tetherOffloadGetStats(
-        std::vector<TetherStatsParcel>* /* tetherStatsParcelVec */) {
-    // deprecated
+        std::vector<TetherStatsParcel>* tetherStatsParcelVec) {
     NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
-    return binder::Status::fromExceptionCode(binder::Status::EX_UNSUPPORTED_OPERATION);
+
+    tetherStatsParcelVec->clear();
+    const auto& statsList = gCtls->tetherCtrl.getTetherOffloadStats();
+    if (!isOk(statsList)) {
+        return asBinderStatus(statsList);
+    }
+    for (const auto& stats : statsList.value()) {
+        tetherStatsParcelVec->push_back(toTetherStatsParcel(stats));
+    }
+    return binder::Status::ok();
 }
 
-// TODO: remark @deprecated in INetd.aidl.
-binder::Status NetdNativeService::tetherOffloadSetInterfaceQuota(int /* ifIndex */,
-                                                                 int64_t /* quotaBytes */) {
-    // deprecated
+binder::Status NetdNativeService::tetherOffloadSetInterfaceQuota(int ifIndex, int64_t quotaBytes) {
     NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
-    return binder::Status::fromExceptionCode(binder::Status::EX_UNSUPPORTED_OPERATION);
+    int res = gCtls->tetherCtrl.setTetherOffloadInterfaceQuota(ifIndex, quotaBytes);
+    return statusFromErrcode(res);
 }
 
-// TODO: remark @deprecated in INetd.aidl.
 binder::Status NetdNativeService::tetherOffloadGetAndClearStats(
-        int /* ifIndex */, android::net::TetherStatsParcel* /* tetherStats */) {
-    // deprecated
+        int ifIndex, android::net::TetherStatsParcel* tetherStats) {
     NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
-    return binder::Status::fromExceptionCode(binder::Status::EX_UNSUPPORTED_OPERATION);
+    const auto& stats = gCtls->tetherCtrl.getAndClearTetherOffloadStats(ifIndex);
+    if (!stats.ok()) {
+        return asBinderStatus(stats);
+    }
+    *tetherStats = toTetherStatsParcel(stats.value());
+    return binder::Status::ok();
 }
 
 }  // namespace net
diff --git a/server/NetdNativeService.h b/server/NetdNativeService.h
index 9779f36..7b7f9b3 100644
--- a/server/NetdNativeService.h
+++ b/server/NetdNativeService.h
@@ -37,8 +37,9 @@
     binder::Status isAlive(bool *alive) override;
 
     // Firewall commands.
-    binder::Status firewallReplaceUidChain(const std::string& chainName, bool isAllowlist,
-                                           const std::vector<int32_t>& uids, bool* ret) override;
+    binder::Status firewallReplaceUidChain(
+            const std::string& chainName, bool isWhitelist,
+            const std::vector<int32_t>& uids, bool *ret) override;
     binder::Status firewallSetFirewallType(int32_t firewallType) override;
     binder::Status firewallSetInterfaceRule(const std::string& ifName,
                                             int32_t firewallRule) override;
@@ -64,7 +65,6 @@
     // Network and routing commands.
     binder::Status networkCreatePhysical(int32_t netId, int32_t permission) override;
     binder::Status networkCreateVpn(int32_t netId, bool secure) override;
-    binder::Status networkCreate(const NativeNetworkConfig& config) override;
     binder::Status networkDestroy(int32_t netId) override;
 
     binder::Status networkAddInterface(int32_t netId, const std::string& iface) override;
@@ -74,10 +74,6 @@
                                        const std::vector<UidRangeParcel>& uids) override;
     binder::Status networkRemoveUidRanges(int32_t netId,
                                           const std::vector<UidRangeParcel>& uids) override;
-    binder::Status networkAddUidRangesParcel(
-            const netd::aidl::NativeUidRangeConfig& uidRangesConfig) override;
-    binder::Status networkRemoveUidRangesParcel(
-            const netd::aidl::NativeUidRangeConfig& uidRangesConfig) override;
     binder::Status networkRejectNonSecureVpn(bool enable,
                                              const std::vector<UidRangeParcel>& uids) override;
     binder::Status networkAddRouteParcel(int32_t netId, const RouteInfoParcel& route) override;
diff --git a/server/NetlinkHandler.cpp b/server/NetlinkHandler.cpp
index 525bb2d..7fb3437 100644
--- a/server/NetlinkHandler.cpp
+++ b/server/NetlinkHandler.cpp
@@ -155,11 +155,7 @@
                 if (shouldDestroy) {
                     SockDiag sd;
                     if (sd.open()) {
-                        // Pass the interface index iff. destroying sockets on a link-local address.
-                        // This cannot use an interface name as the interface might no longer exist.
-                        int destroyIfaceIndex =
-                                std::string_view(addrstr).starts_with("fe80:") ? ifaceIndex : 0;
-                        int ret = sd.destroySockets(addrstr, destroyIfaceIndex);
+                        int ret = sd.destroySockets(addrstr);
                         if (ret < 0) {
                             ALOGE("Error destroying sockets: %s", strerror(-ret));
                         }
diff --git a/server/Network.cpp b/server/Network.cpp
index 72a1545..eb2a233 100644
--- a/server/Network.cpp
+++ b/server/Network.cpp
@@ -13,12 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#define LOG_TAG "Netd"
 
 #include "Network.h"
 
-#include "RouteController.h"
-#include "SockDiag.h"
+#define LOG_TAG "Netd"
 #include "log/log.h"
 
 #include <android-base/strings.h>
@@ -61,7 +59,25 @@
     const char kSeparator[] = " ";
     std::stringstream repr;
 
-    repr << mNetId << kSeparator << getTypeString();
+    repr << mNetId;
+
+    repr << kSeparator;
+    switch (getType()) {
+        case DUMMY:
+            repr << "DUMMY";
+            break;
+        case LOCAL:
+            repr << "LOCAL";
+            break;
+        case PHYSICAL:
+            repr << "PHYSICAL";
+            break;
+        case VIRTUAL:
+            repr << "VIRTUAL";
+            break;
+        default:
+            repr << "unknown";
+    }
 
     if (mInterfaces.size() > 0) {
         repr << kSeparator << android::base::Join(mInterfaces, ",");
@@ -70,73 +86,9 @@
     return repr.str();
 }
 
-std::string Network::uidRangesToString() const {
-    if (mUidRangeMap.empty()) {
-        return "";
-    }
 
-    std::ostringstream result;
-    for (auto it = mUidRangeMap.begin(); it != mUidRangeMap.end(); ++it) {
-        result << "prio " << it->first << " " << it->second.toString();
-        if (std::next(it) != mUidRangeMap.end()) result << "; ";
-    }
-    return result.str();
+Network::Network(unsigned netId) : mNetId(netId) {
 }
 
-// Check if the user has been added to this network. If yes, the highest priority of matching
-// setting is returned by subPriority. Thus caller can make choice among several matching
-// networks.
-bool Network::appliesToUser(uid_t uid, uint32_t* subPriority) const {
-    for (const auto& [priority, uidRanges] : mUidRangeMap) {
-        if (uidRanges.hasUid(uid)) {
-            *subPriority = priority;
-            return true;
-        }
-    }
-    return false;
-}
-
-void Network::addToUidRangeMap(const UidRanges& uidRanges, uint32_t subPriority) {
-    auto iter = mUidRangeMap.find(subPriority);
-    if (iter != mUidRangeMap.end()) {
-        iter->second.add(uidRanges);
-    } else {
-        mUidRangeMap[subPriority] = uidRanges;
-    }
-}
-
-void Network::removeFromUidRangeMap(const UidRanges& uidRanges, uint32_t subPriority) {
-    auto iter = mUidRangeMap.find(subPriority);
-    if (iter != mUidRangeMap.end()) {
-        iter->second.remove(uidRanges);
-        if (iter->second.empty()) {
-            mUidRangeMap.erase(subPriority);
-        }
-    } else {
-        ALOGW("uidRanges with priority %u not found", subPriority);
-    }
-}
-
-bool Network::canAddUidRanges(const UidRanges& uidRanges, uint32_t subPriority) const {
-    if (uidRanges.overlapsSelf()) {
-        ALOGE("uid range %s overlaps self", uidRanges.toString().c_str());
-        return false;
-    }
-
-    auto iter = mUidRangeMap.find(subPriority);
-    if (iter != mUidRangeMap.end() && uidRanges.overlaps(iter->second)) {
-        ALOGE("uid range %s overlaps priority %u %s", uidRanges.toString().c_str(), subPriority,
-              iter->second.toString().c_str());
-        return false;
-    }
-    return true;
-}
-
-bool Network::isSecure() const {
-    return mSecure;
-}
-
-Network::Network(unsigned netId, bool secure) : mNetId(netId), mSecure(secure) {}
-
 }  // namespace net
 }  // namespace android
diff --git a/server/Network.h b/server/Network.h
index aa1b21a..8417f34 100644
--- a/server/Network.h
+++ b/server/Network.h
@@ -17,67 +17,45 @@
 #pragma once
 
 #include "NetdConstants.h"
-#include "UidRanges.h"
 
 #include <set>
 #include <string>
 
 namespace android::net {
 
-typedef std::map<uint32_t, UidRanges> UidRangeMap;
-
 // A Network represents a collection of interfaces participating as a single administrative unit.
 class Network {
 public:
+    enum Type {
+        DUMMY,
+        LOCAL,
+        PHYSICAL,
+        VIRTUAL,
+    };
+
     // You MUST ensure that no interfaces are still assigned to this network, say by calling
     // clearInterfaces(), before deleting it. This is because interface removal may fail. If we
     // automatically removed interfaces in the destructor, you wouldn't know if it failed.
     virtual ~Network();
 
-    virtual std::string getTypeString() const = 0;
+    virtual Type getType() const = 0;
     unsigned getNetId() const;
 
     bool hasInterface(const std::string& interface) const;
     const std::set<std::string>& getInterfaces() const;
 
     // These return 0 on success or negative errno on failure.
-    [[nodiscard]] virtual int addInterface(const std::string&) { return -EINVAL; }
-    [[nodiscard]] virtual int removeInterface(const std::string&) { return -EINVAL; }
+    [[nodiscard]] virtual int addInterface(const std::string& interface) = 0;
+    [[nodiscard]] virtual int removeInterface(const std::string& interface) = 0;
     [[nodiscard]] int clearInterfaces();
 
     std::string toString() const;
-    std::string uidRangesToString() const;
-    bool appliesToUser(uid_t uid, uint32_t* subPriority) const;
-    [[nodiscard]] virtual int addUsers(const UidRanges&, uint32_t /*subPriority*/) {
-        return -EINVAL;
-    };
-    [[nodiscard]] virtual int removeUsers(const UidRanges&, uint32_t /*subPriority*/) {
-        return -EINVAL;
-    };
-    bool isSecure() const;
-    virtual bool isPhysical() { return false; }
-    virtual bool isUnreachable() { return false; }
-    virtual bool isVirtual() { return false; }
-    virtual bool canAddUsers() { return false; }
-    virtual bool isValidSubPriority(uint32_t /*priority*/) { return false; }
-    virtual void addToUidRangeMap(const UidRanges& uidRanges, uint32_t subPriority);
-    virtual void removeFromUidRangeMap(const UidRanges& uidRanges, uint32_t subPriority);
 
 protected:
-    explicit Network(unsigned netId, bool mSecure = false);
-    bool canAddUidRanges(const UidRanges& uidRanges, uint32_t subPriority) const;
+    explicit Network(unsigned netId);
 
     const unsigned mNetId;
     std::set<std::string> mInterfaces;
-    // Each subsidiary priority maps to a set of UID ranges of a feature.
-    std::map<uint32_t, UidRanges> mUidRangeMap;
-    const bool mSecure;
-
-private:
-    enum Action {
-        REMOVE,
-        ADD,
-    };
 };
 
 }  // namespace android::net
diff --git a/server/NetworkController.cpp b/server/NetworkController.cpp
index 602639c..20ae44b 100644
--- a/server/NetworkController.cpp
+++ b/server/NetworkController.cpp
@@ -39,7 +39,6 @@
 #include "OffloadUtils.h"
 #include "PhysicalNetwork.h"
 #include "RouteController.h"
-#include "UnreachableNetwork.h"
 #include "VirtualNetwork.h"
 #include "netdutils/DumpWriter.h"
 #include "netid_client.h"
@@ -129,7 +128,7 @@
 int NetworkController::DelegateImpl::modifyFallthrough(const std::string& physicalInterface,
                                                        Permission permission, bool add) {
     for (const auto& entry : mNetworkController->mNetworks) {
-        if (entry.second->isVirtual()) {
+        if (entry.second->getType() == Network::VIRTUAL) {
             if (int ret = modifyFallthrough(entry.first, physicalInterface, permission, add)) {
                 return ret;
             }
@@ -141,25 +140,24 @@
 NetworkController::NetworkController() :
         mDelegateImpl(new NetworkController::DelegateImpl(this)), mDefaultNetId(NETID_UNSET),
         mProtectableUsers({AID_VPN}) {
-    gLog.info("enter NetworkController ctor");
     mNetworks[LOCAL_NET_ID] = new LocalNetwork(LOCAL_NET_ID);
     mNetworks[DUMMY_NET_ID] = new DummyNetwork(DUMMY_NET_ID);
-    mNetworks[UNREACHABLE_NET_ID] = new UnreachableNetwork(UNREACHABLE_NET_ID);
 
     // Clear all clsact stubs on all interfaces.
     // TODO: perhaps only remove the clsact on the interface which is added by
     // RouteController::addInterfaceToPhysicalNetwork. Currently, the netd only
     // attach the clsact to the interface for the physical network.
-    const auto& ifaces = InterfaceController::getIfaceNames();
-    if (isOk(ifaces)) {
-        for (const std::string& iface : ifaces.value()) {
-            if (int ifIndex = if_nametoindex(iface.c_str())) {
-                // Ignore the error because the interface might not have a clsact.
-                tcQdiscDelDevClsact(ifIndex);
+    if (bpf::isBpfSupported()) {
+        const auto& ifaces = InterfaceController::getIfaceNames();
+        if (isOk(ifaces)) {
+            for (const std::string& iface : ifaces.value()) {
+                if (int ifIndex = if_nametoindex(iface.c_str())) {
+                    // Ignore the error because the interface might not have a clsact.
+                    tcQdiscDelDevClsact(ifIndex);
+                }
             }
         }
     }
-    gLog.info("leave NetworkController ctor");
 }
 
 unsigned NetworkController::getDefaultNetwork() const {
@@ -180,7 +178,7 @@
             ALOGE("no such netId %u", netId);
             return -ENONET;
         }
-        if (!network->isPhysical()) {
+        if (network->getType() != Network::PHYSICAL) {
             ALOGE("cannot set default to non-physical network with netId %u", netId);
             return -EINVAL;
         }
@@ -191,7 +189,7 @@
 
     if (mDefaultNetId != NETID_UNSET) {
         Network* network = getNetworkLocked(mDefaultNetId);
-        if (!network || !network->isPhysical()) {
+        if (!network || network->getType() != Network::PHYSICAL) {
             ALOGE("cannot find previously set default network with netId %u", mDefaultNetId);
             return -ESRCH;
         }
@@ -209,16 +207,13 @@
     fwmark.protectedFromVpn = true;
     fwmark.permission = PERMISSION_SYSTEM;
 
-    Network* appDefaultNetwork = getPhysicalOrUnreachableNetworkForUserLocked(uid);
-    unsigned defaultNetId = appDefaultNetwork ? appDefaultNetwork->getNetId() : mDefaultNetId;
-
     // Common case: there is no VPN that applies to the user, and the query did not specify a netId.
     // Therefore, it is safe to set the explicit bit on this query and skip all the complex logic
     // below. While this looks like a special case, it is actually the one that handles the vast
     // majority of DNS queries.
     // TODO: untangle this code.
     if (*netId == NETID_UNSET && getVirtualNetworkForUserLocked(uid) == nullptr) {
-        *netId = defaultNetId;
+        *netId = mDefaultNetId;
         fwmark.netId = *netId;
         fwmark.explicitlySelected = true;
         return fwmark.intValue;
@@ -234,8 +229,8 @@
         // servers (through the default network). Otherwise, the query is guaranteed to fail.
         // http://b/29498052
         Network *network = getNetworkLocked(*netId);
-        if (network && network->isVirtual() && !resolv_has_nameservers(*netId)) {
-            *netId = defaultNetId;
+        if (network && network->getType() == Network::VIRTUAL && !resolv_has_nameservers(*netId)) {
+            *netId = mDefaultNetId;
         }
     } else {
         // If the user is subject to a VPN and the VPN provides DNS servers, use those servers
@@ -248,7 +243,7 @@
         } else {
             // TODO: return an error instead of silently doing the DNS lookup on the wrong network.
             // http://b/27560555
-            *netId = defaultNetId;
+            *netId = mDefaultNetId;
         }
     }
     fwmark.netId = *netId;
@@ -256,22 +251,17 @@
 }
 
 // Returns the NetId that a given UID would use if no network is explicitly selected. Specifically,
-// the VPN that applies to the UID if any; Otherwise, the default network for UID; Otherwise the
-// unreachable network that applies to the UID; lastly, the default network.
+// the VPN that applies to the UID if any; otherwise, the default network.
 unsigned NetworkController::getNetworkForUser(uid_t uid) const {
     ScopedRLock lock(mRWLock);
     if (VirtualNetwork* virtualNetwork = getVirtualNetworkForUserLocked(uid)) {
         return virtualNetwork->getNetId();
     }
-    if (Network* network = getPhysicalOrUnreachableNetworkForUserLocked(uid)) {
-        return network->getNetId();
-    }
     return mDefaultNetId;
 }
 
 // Returns the NetId that will be set when a socket connect()s. This is the bypassable VPN that
-// applies to the user if any; otherwise, the default network that applies to user if any; lastly,
-// the default network.
+// applies to the user if any; otherwise, the default network.
 //
 // In general, we prefer to always set the default network's NetId in connect(), so that if the VPN
 // is a split-tunnel and disappears later, the socket continues working (since the default network's
@@ -284,21 +274,11 @@
 // traffic to the default network. But it does mean that if the bypassable VPN goes away (and thus
 // the fallthrough rules also go away), the socket that used to fallthrough to the default network
 // will stop working.
-//
-// Per-app physical default networks behave the same as bypassable VPNs: when a socket is connected
-// on one of these networks, we mark the socket with the netId of the network. This ensures that if
-// the per-app default network changes, sockets established on the previous network are still
-// routed to that network, assuming the network's UID ranges still apply to the UID. While this
-// means that fallthrough to the default network does not work, physical networks not expected
-// ever to be split tunnels.
 unsigned NetworkController::getNetworkForConnectLocked(uid_t uid) const {
     VirtualNetwork* virtualNetwork = getVirtualNetworkForUserLocked(uid);
     if (virtualNetwork && !virtualNetwork->isSecure()) {
         return virtualNetwork->getNetId();
     }
-    if (Network* network = getPhysicalOrUnreachableNetworkForUserLocked(uid)) {
-        return network->getNetId();
-    }
     return mDefaultNetId;
 }
 
@@ -377,7 +357,7 @@
 
 bool NetworkController::isVirtualNetworkLocked(unsigned netId) const {
     Network* network = getNetworkLocked(netId);
-    return network && network->isVirtual();
+    return network && network->getType() == Network::VIRTUAL;
 }
 
 int NetworkController::createPhysicalNetworkLocked(unsigned netId, Permission permission) {
@@ -437,7 +417,7 @@
     return ret;
 }
 
-int NetworkController::createVirtualNetwork(unsigned netId, bool secure, NativeVpnType vpnType) {
+int NetworkController::createVirtualNetwork(unsigned netId, bool secure) {
     ScopedWLock lock(mRWLock);
 
     if (!(MIN_NET_ID <= netId && netId <= MAX_NET_ID)) {
@@ -450,11 +430,6 @@
         return -EEXIST;
     }
 
-    if (vpnType < NativeVpnType::SERVICE || NativeVpnType::OEM < vpnType) {
-        ALOGE("invalid vpnType %d", static_cast<int>(vpnType));
-        return -EINVAL;
-    }
-
     if (int ret = modifyFallthroughLocked(netId, true)) {
         return ret;
     }
@@ -465,8 +440,8 @@
 int NetworkController::destroyNetwork(unsigned netId) {
     ScopedWLock lock(mRWLock);
 
-    if (netId == LOCAL_NET_ID || netId == UNREACHABLE_NET_ID) {
-        ALOGE("cannot destroy local or unreachable network");
+    if (netId == LOCAL_NET_ID) {
+        ALOGE("cannot destroy local network");
         return -EINVAL;
     }
     if (!isValidNetworkLocked(netId)) {
@@ -491,7 +466,7 @@
             }
         }
         mDefaultNetId = NETID_UNSET;
-    } else if (network->isVirtual()) {
+    } else if (network->getType() == Network::VIRTUAL) {
         if (int err = modifyFallthroughLocked(netId, false)) {
             if (!ret) {
                 ret = err;
@@ -587,7 +562,7 @@
             ALOGE("no such netId %u", netId);
             return -ENONET;
         }
-        if (!network->isPhysical()) {
+        if (network->getType() != Network::PHYSICAL) {
             ALOGE("cannot set permissions on non-physical network with netId %u", netId);
             return -EINVAL;
         }
@@ -599,41 +574,39 @@
     return 0;
 }
 
-namespace {
-
-int isWrongNetworkForUidRanges(unsigned netId, Network* network) {
+int NetworkController::addUsersToNetwork(unsigned netId, const UidRanges& uidRanges) {
+    ScopedWLock lock(mRWLock);
+    Network* network = getNetworkLocked(netId);
     if (!network) {
         ALOGE("no such netId %u", netId);
         return -ENONET;
     }
-    if (!network->canAddUsers()) {
-        ALOGE("cannot add/remove users to/from %s network %u", network->getTypeString().c_str(),
-              netId);
+    if (network->getType() != Network::VIRTUAL) {
+        ALOGE("cannot add users to non-virtual network with netId %u", netId);
         return -EINVAL;
     }
+    if (int ret = static_cast<VirtualNetwork*>(network)->addUsers(uidRanges, mProtectableUsers)) {
+        return ret;
+    }
     return 0;
 }
 
-}  // namespace
-
-int NetworkController::addUsersToNetwork(unsigned netId, const UidRanges& uidRanges,
-                                         uint32_t subPriority) {
+int NetworkController::removeUsersFromNetwork(unsigned netId, const UidRanges& uidRanges) {
     ScopedWLock lock(mRWLock);
     Network* network = getNetworkLocked(netId);
-    if (int ret = isWrongNetworkForUidRanges(netId, network)) {
+    if (!network) {
+        ALOGE("no such netId %u", netId);
+        return -ENONET;
+    }
+    if (network->getType() != Network::VIRTUAL) {
+        ALOGE("cannot remove users from non-virtual network with netId %u", netId);
+        return -EINVAL;
+    }
+    if (int ret = static_cast<VirtualNetwork*>(network)->removeUsers(uidRanges,
+                                                                     mProtectableUsers)) {
         return ret;
     }
-    return network->addUsers(uidRanges, subPriority);
-}
-
-int NetworkController::removeUsersFromNetwork(unsigned netId, const UidRanges& uidRanges,
-                                              uint32_t subPriority) {
-    ScopedWLock lock(mRWLock);
-    Network* network = getNetworkLocked(netId);
-    if (int ret = isWrongNetworkForUidRanges(netId, network)) {
-        return ret;
-    }
-    return network->removeUsers(uidRanges, subPriority);
+    return 0;
 }
 
 int NetworkController::addRoute(unsigned netId, const char* interface, const char* destination,
@@ -736,17 +709,12 @@
     for (const auto& i : mNetworks) {
         Network* network = i.second;
         dw.println(network->toString());
-        if (network->isPhysical()) {
+        if (network->getType() == Network::PHYSICAL) {
             dw.incIndent();
             Permission permission = reinterpret_cast<PhysicalNetwork*>(network)->getPermission();
             dw.println("Required permission: %s", permissionToName(permission));
             dw.decIndent();
         }
-        if (const auto& str = network->uidRangesToString(); !str.empty()) {
-            dw.incIndent();
-            dw.println(str);
-            dw.decIndent();
-        }
         dw.blankline();
     }
     dw.decIndent();
@@ -783,34 +751,17 @@
 }
 
 VirtualNetwork* NetworkController::getVirtualNetworkForUserLocked(uid_t uid) const {
-    uint32_t subPriority;
-    for (const auto& [_, network] : mNetworks) {
-        if (network->isVirtual() && network->appliesToUser(uid, &subPriority)) {
-            return static_cast<VirtualNetwork*>(network);
+    for (const auto& entry : mNetworks) {
+        if (entry.second->getType() == Network::VIRTUAL) {
+            VirtualNetwork* virtualNetwork = static_cast<VirtualNetwork*>(entry.second);
+            if (virtualNetwork->appliesToUser(uid)) {
+                return virtualNetwork;
+            }
         }
     }
     return nullptr;
 }
 
-// Returns a network with the highest subsidiary priority among physical and unreachable networks
-// that applies to uid. For a single subsidiary priority, an uid should belong to only one network.
-// If the uid apply to different network with the same priority at the same time, the behavior is
-// undefined. That is a configuration error.
-Network* NetworkController::getPhysicalOrUnreachableNetworkForUserLocked(uid_t uid) const {
-    Network* bestNetwork = nullptr;
-    unsigned bestSubPriority = UidRanges::LOWEST_SUB_PRIORITY + 1;
-    for (const auto& [netId, network] : mNetworks) {
-        uint32_t subPriority;
-        if (!network->isPhysical() && !network->isUnreachable()) continue;
-        if (!network->appliesToUser(uid, &subPriority)) continue;
-        if (subPriority < bestSubPriority) {
-            bestNetwork = network;
-            bestSubPriority = subPriority;
-        }
-    }
-    return bestNetwork;
-}
-
 Permission NetworkController::getPermissionForUserLocked(uid_t uid) const {
     auto iter = mUsers.find(uid);
     if (iter != mUsers.end()) {
@@ -830,35 +781,18 @@
     if (uid == INVALID_UID) {
         return -EREMOTEIO;
     }
-    // If the UID has PERMISSION_SYSTEM, it can use whatever network it wants.
     Permission userPermission = getPermissionForUserLocked(uid);
     if ((userPermission & PERMISSION_SYSTEM) == PERMISSION_SYSTEM) {
         return 0;
     }
-    // If the UID wants to use a VPN, it can do so if and only if the VPN applies to the UID.
-    uint32_t subPriority;
-    if (network->isVirtual()) {
-        return network->appliesToUser(uid, &subPriority) ? 0 : -EPERM;
+    if (network->getType() == Network::VIRTUAL) {
+        return static_cast<VirtualNetwork*>(network)->appliesToUser(uid) ? 0 : -EPERM;
     }
-    // If a VPN applies to the UID, and the VPN is secure (i.e., not bypassable), then the UID can
-    // only select a different network if it has the ability to protect its sockets.
     VirtualNetwork* virtualNetwork = getVirtualNetworkForUserLocked(uid);
     if (virtualNetwork && virtualNetwork->isSecure() &&
             mProtectableUsers.find(uid) == mProtectableUsers.end()) {
         return -EPERM;
     }
-    // If the UID wants to use a physical network and it has a UID range that includes the UID, the
-    // UID has permission to use it regardless of whether the permission bits match.
-    if (network->isPhysical() && network->appliesToUser(uid, &subPriority)) {
-        return 0;
-    }
-    // Only apps that are configured as "no default network" can use the unreachable network.
-    if (network->isUnreachable()) {
-        return network->appliesToUser(uid, &subPriority) ? 0 : -EPERM;
-    }
-    // Check whether the UID's permission bits are sufficient to use the network.
-    // Because the permission of the system default network is PERMISSION_NONE(0x0), apps can always
-    // pass the check here when using the system default network.
     Permission networkPermission = static_cast<PhysicalNetwork*>(network)->getPermission();
     return ((userPermission & networkPermission) == networkPermission) ? 0 : -EACCES;
 }
@@ -915,7 +849,7 @@
         ALOGE("cannot find previously set default network with netId %u", mDefaultNetId);
         return -ESRCH;
     }
-    if (!network->isPhysical()) {
+    if (network->getType() != Network::PHYSICAL) {
         ALOGE("inconceivable! default network must be a physical network");
         return -EINVAL;
     }
@@ -933,7 +867,7 @@
     bool physicalNetworkExists = false;
     for (const auto& entry : mNetworks) {
         const auto& network = entry.second;
-        if (network->isPhysical() && network->getNetId() >= MIN_NET_ID) {
+        if (network->getType() == Network::PHYSICAL && network->getNetId() >= MIN_NET_ID) {
             physicalNetworkExists = true;
             break;
         }
diff --git a/server/NetworkController.h b/server/NetworkController.h
index a61ac39..ff49c02 100644
--- a/server/NetworkController.h
+++ b/server/NetworkController.h
@@ -22,8 +22,6 @@
 
 #include "NetdConstants.h"
 #include "Permission.h"
-#include "PhysicalNetwork.h"
-#include "UnreachableNetwork.h"
 #include "android/net/INetd.h"
 #include "netdutils/DumpWriter.h"
 
@@ -84,12 +82,11 @@
  */
 class NetworkController {
 public:
-    // NetIds 53..98 are reserved for future use.
+    // NetIds 52..98 are reserved for future use.
     static constexpr int MIN_OEM_ID = 1;
     static constexpr int MAX_OEM_ID = 50;
     static constexpr int LOCAL_NET_ID = INetd::LOCAL_NET_ID;
-    static constexpr int DUMMY_NET_ID = INetd::DUMMY_NET_ID;
-    static constexpr int UNREACHABLE_NET_ID = INetd::UNREACHABLE_NET_ID;
+    static constexpr int DUMMY_NET_ID = 51;
 
     // Route mode for modify route
     enum RouteOperation { ROUTE_ADD, ROUTE_UPDATE, ROUTE_REMOVE };
@@ -107,7 +104,7 @@
 
     [[nodiscard]] int createPhysicalNetwork(unsigned netId, Permission permission);
     [[nodiscard]] int createPhysicalOemNetwork(Permission permission, unsigned* netId);
-    [[nodiscard]] int createVirtualNetwork(unsigned netId, bool secure, NativeVpnType vpnType);
+    [[nodiscard]] int createVirtualNetwork(unsigned netId, bool secure);
     [[nodiscard]] int destroyNetwork(unsigned netId);
 
     [[nodiscard]] int addInterfaceToNetwork(unsigned netId, const char* interface);
@@ -119,10 +116,8 @@
     [[nodiscard]] int setPermissionForNetworks(Permission permission,
                                                const std::vector<unsigned>& netIds);
 
-    [[nodiscard]] int addUsersToNetwork(unsigned netId, const UidRanges& uidRanges,
-                                        uint32_t subPriority);
-    [[nodiscard]] int removeUsersFromNetwork(unsigned netId, const UidRanges& uidRanges,
-                                             uint32_t subPriority);
+    [[nodiscard]] int addUsersToNetwork(unsigned netId, const UidRanges& uidRanges);
+    [[nodiscard]] int removeUsersFromNetwork(unsigned netId, const UidRanges& uidRanges);
 
     // |nexthop| can be NULL (to indicate a directly-connected route), "unreachable" (to indicate a
     // route that's blocked), "throw" (to indicate the lack of a match), or a regular IP address.
@@ -156,12 +151,13 @@
     // set to a non-NETID_UNSET value if the user already has indicated a preference. Returns the
     // fwmark value to set on the socket when performing the DNS request.
     uint32_t getNetworkForDnsLocked(unsigned* netId, uid_t uid) const;
+
+    unsigned getNetworkForUserLocked(uid_t uid) const;
     unsigned getNetworkForConnectLocked(uid_t uid) const;
     unsigned getNetworkForInterfaceLocked(const char* interface) const;
     bool canProtectLocked(uid_t uid) const;
     bool isVirtualNetworkLocked(unsigned netId) const;
     VirtualNetwork* getVirtualNetworkForUserLocked(uid_t uid) const;
-    Network* getPhysicalOrUnreachableNetworkForUserLocked(uid_t uid) const;
     Permission getPermissionForUserLocked(uid_t uid) const;
     int checkUserNetworkAccessLocked(uid_t uid, unsigned netId) const;
     [[nodiscard]] int createPhysicalNetworkLocked(unsigned netId, Permission permission);
diff --git a/server/OemNetdListener.cpp b/server/OemNetdListener.cpp
index 9c44cf2..fb07a80 100644
--- a/server/OemNetdListener.cpp
+++ b/server/OemNetdListener.cpp
@@ -24,10 +24,16 @@
 namespace net {
 
 ::android::sp<::android::IBinder> OemNetdListener::getListener() {
-    // Thread-safe initialization.
-    static ::android::sp<OemNetdListener> listener = ::android::sp<OemNetdListener>::make();
-    static ::android::sp<::android::IBinder> sIBinder = ::android::IInterface::asBinder(listener);
-    return sIBinder;
+    static OemNetdListener listener;
+    return listener.getIBinder();
+}
+
+::android::sp<::android::IBinder> OemNetdListener::getIBinder() {
+    std::lock_guard lock(mMutex);
+    if (mIBinder == nullptr) {
+        mIBinder = ::android::IInterface::asBinder(this);
+    }
+    return mIBinder;
 }
 
 ::android::binder::Status OemNetdListener::isAlive(bool* alive) {
diff --git a/server/OemNetdListener.h b/server/OemNetdListener.h
index b94827b..4fb4fb7 100644
--- a/server/OemNetdListener.h
+++ b/server/OemNetdListener.h
@@ -43,10 +43,14 @@
             const ::android::sp<IOemNetdUnsolicitedEventListener>& listener) override;
 
   private:
+    std::mutex mMutex;
     std::mutex mOemUnsolicitedMutex;
 
+    ::android::sp<::android::IBinder> mIBinder GUARDED_BY(mMutex);
     OemUnsolListenerMap mOemUnsolListenerMap GUARDED_BY(mOemUnsolicitedMutex);
 
+    ::android::sp<::android::IBinder> getIBinder() EXCLUDES(mMutex);
+
     void registerOemUnsolicitedEventListenerInternal(
             const ::android::sp<IOemNetdUnsolicitedEventListener>& listener)
             EXCLUDES(mOemUnsolicitedMutex);
@@ -60,4 +64,4 @@
 }  // namespace android
 }  // namespace com
 
-#endif  // NETD_SERVER_OEM_NETD_LISTENER_H
+#endif  // NETD_SERVER_OEM_NETD_LISTENER_H
\ No newline at end of file
diff --git a/server/OffloadUtils.cpp b/server/OffloadUtils.cpp
index 0d9869f..a743458 100644
--- a/server/OffloadUtils.cpp
+++ b/server/OffloadUtils.cpp
@@ -38,7 +38,7 @@
 
 using std::max;
 
-static int doSIOCGIF(const std::string& interface, int opt) {
+int hardwareAddressType(const std::string& interface) {
     base::unique_fd ufd(socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0));
 
     if (ufd < 0) {
@@ -56,19 +56,9 @@
     // match a truncated interface if one were to exist.
     strncpy(ifr.ifr_name, interface.c_str(), sizeof(ifr.ifr_name));
 
-    if (ioctl(ufd, opt, &ifr, sizeof(ifr))) return -errno;
+    if (ioctl(ufd, SIOCGIFHWADDR, &ifr, sizeof(ifr))) return -errno;
 
-    if (opt == SIOCGIFHWADDR) return ifr.ifr_hwaddr.sa_family;
-    if (opt == SIOCGIFMTU) return ifr.ifr_mtu;
-    return -EINVAL;
-}
-
-int hardwareAddressType(const std::string& interface) {
-    return doSIOCGIF(interface, SIOCGIFHWADDR);
-}
-
-int deviceMTU(const std::string& interface) {
-    return doSIOCGIF(interface, SIOCGIFMTU);
+    return ifr.ifr_hwaddr.sa_family;
 }
 
 base::Result<bool> isEthernet(const std::string& interface) {
@@ -204,12 +194,10 @@
     return sendAndProcessNetlinkResponse(&req, sizeof(req));
 }
 
-// The priority of clat hook - must be after tethering.
-constexpr uint16_t PRIO_CLAT = 4;
-
-// tc filter add dev .. in/egress prio 4 protocol ipv6/ip bpf object-pinned /sys/fs/bpf/...
+// tc filter add dev .. in/egress prio 1 protocol ipv6/ip bpf object-pinned /sys/fs/bpf/...
 // direct-action
-int tcFilterAddDevBpf(int ifIndex, bool ingress, uint16_t proto, int bpfFd, bool ethernet) {
+int tcFilterAddDevBpf(int ifIndex, bool ingress, uint16_t prio, uint16_t proto, int bpfFd,
+                      bool ethernet) {
     // This is the name of the filter we're attaching (ie. this is the 'bpf'
     // packet classifier enabled by kernel config option CONFIG_NET_CLS_BPF.
     //
@@ -225,28 +213,40 @@
 #define FSOBJ_SUFFIX ":[*fsobj]"
 
     // This macro expands (from header files) to:
-    //   prog_clatd_schedcls_ingress6_clat_rawip:[*fsobj]
+    //   prog_clatd_schedcls_ingress_clat_rawip:[*fsobj]
     // and is the name of the pinned ingress ebpf program for ARPHRD_RAWIP interfaces.
     // (also compatible with anything that has 0 size L2 header)
-    static constexpr char name_clat_rx_rawip[] = CLAT_INGRESS6_PROG_RAWIP_NAME FSOBJ_SUFFIX;
+    static constexpr char name_clat_rx_rawip[] = CLAT_INGRESS_PROG_RAWIP_NAME FSOBJ_SUFFIX;
 
     // This macro expands (from header files) to:
-    //   prog_clatd_schedcls_ingress6_clat_ether:[*fsobj]
+    //   prog_clatd_schedcls_ingress_clat_ether:[*fsobj]
     // and is the name of the pinned ingress ebpf program for ARPHRD_ETHER interfaces.
     // (also compatible with anything that has standard ethernet header)
-    static constexpr char name_clat_rx_ether[] = CLAT_INGRESS6_PROG_ETHER_NAME FSOBJ_SUFFIX;
+    static constexpr char name_clat_rx_ether[] = CLAT_INGRESS_PROG_ETHER_NAME FSOBJ_SUFFIX;
 
     // This macro expands (from header files) to:
-    //   prog_clatd_schedcls_egress4_clat_rawip:[*fsobj]
+    //   prog_clatd_schedcls_egress_clat_rawip:[*fsobj]
     // and is the name of the pinned egress ebpf program for ARPHRD_RAWIP interfaces.
     // (also compatible with anything that has 0 size L2 header)
-    static constexpr char name_clat_tx_rawip[] = CLAT_EGRESS4_PROG_RAWIP_NAME FSOBJ_SUFFIX;
+    static constexpr char name_clat_tx_rawip[] = CLAT_EGRESS_PROG_RAWIP_NAME FSOBJ_SUFFIX;
 
     // This macro expands (from header files) to:
-    //   prog_clatd_schedcls_egress4_clat_ether:[*fsobj]
+    //   prog_clatd_schedcls_egress_clat_ether:[*fsobj]
     // and is the name of the pinned egress ebpf program for ARPHRD_ETHER interfaces.
     // (also compatible with anything that has standard ethernet header)
-    static constexpr char name_clat_tx_ether[] = CLAT_EGRESS4_PROG_ETHER_NAME FSOBJ_SUFFIX;
+    static constexpr char name_clat_tx_ether[] = CLAT_EGRESS_PROG_ETHER_NAME FSOBJ_SUFFIX;
+
+    // This macro expands (from header files) to:
+    //   prog_offload_schedcls_ingress_tether_rawip:[*fsobj]
+    // and is the name of the pinned ingress ebpf program for ARPHRD_RAWIP interfaces.
+    // (also compatible with anything that has 0 size L2 header)
+    static constexpr char name_tether_rawip[] = TETHER_INGRESS_PROG_RAWIP_NAME FSOBJ_SUFFIX;
+
+    // This macro expands (from header files) to:
+    //   prog_offload_schedcls_ingress_tether_ether:[*fsobj]
+    // and is the name of the pinned ingress ebpf program for ARPHRD_ETHER interfaces.
+    // (also compatible with anything that has standard ethernet header)
+    static constexpr char name_tether_ether[] = TETHER_INGRESS_PROG_ETHER_NAME FSOBJ_SUFFIX;
 
 #undef FSOBJ_SUFFIX
 
@@ -259,12 +259,16 @@
             sizeof(name_clat_rx_ether),
             sizeof(name_clat_tx_rawip),
             sizeof(name_clat_tx_ether),
+            sizeof(name_tether_rawip),
+            sizeof(name_tether_ether),
     });
 
     // These are not compile time constants: 'name' is used in strncpy below
     const char* const name_clat_rx = ethernet ? name_clat_rx_ether : name_clat_rx_rawip;
     const char* const name_clat_tx = ethernet ? name_clat_tx_ether : name_clat_tx_rawip;
-    const char* const name = ingress ? name_clat_rx : name_clat_tx;
+    const char* const name_clat = ingress ? name_clat_rx : name_clat_tx;
+    const char* const name_tether = ethernet ? name_tether_ether : name_tether_rawip;
+    const char* const name = (prio == PRIO_TETHER) ? name_tether : name_clat;
 
     struct {
         nlmsghdr n;
@@ -302,7 +306,7 @@
                             .tcm_handle = TC_H_UNSPEC,
                             .tcm_parent = TC_H_MAKE(TC_H_CLSACT,
                                                     ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
-                            .tcm_info = static_cast<__u32>((PRIO_CLAT << 16) | htons(proto)),
+                            .tcm_info = static_cast<__u32>((prio << 16) | htons(proto)),
                     },
             .kind =
                     {
@@ -318,7 +322,7 @@
                             .attr =
                                     {
                                             .nla_len = sizeof(req.options),
-                                            .nla_type = NLA_F_NESTED | TCA_OPTIONS,
+                                            .nla_type = TCA_OPTIONS,
                                     },
                             .fd =
                                     {
@@ -358,8 +362,8 @@
     return sendAndProcessNetlinkResponse(&req, sizeof(req));
 }
 
-// tc filter del dev .. in/egress prio 4 protocol ..
-int tcFilterDelDev(int ifIndex, bool ingress, uint16_t proto) {
+// tc filter del dev .. in/egress prio .. protocol ..
+int tcFilterDelDev(int ifIndex, bool ingress, uint16_t prio, uint16_t proto) {
     const struct {
         nlmsghdr n;
         tcmsg t;
@@ -377,7 +381,7 @@
                             .tcm_handle = TC_H_UNSPEC,
                             .tcm_parent = TC_H_MAKE(TC_H_CLSACT,
                                                     ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
-                            .tcm_info = static_cast<__u32>((PRIO_CLAT << 16) | htons(proto)),
+                            .tcm_info = static_cast<__u32>((prio << 16) | htons(proto)),
                     },
     };
 
diff --git a/server/OffloadUtils.h b/server/OffloadUtils.h
index 684ffb3..818fd39 100644
--- a/server/OffloadUtils.h
+++ b/server/OffloadUtils.h
@@ -19,7 +19,6 @@
 #include <android-base/result.h>
 #include <errno.h>
 #include <linux/if_ether.h>
-#include <linux/if_link.h>
 #include <linux/rtnetlink.h>
 
 #include <string>
@@ -39,33 +38,55 @@
 constexpr bool EGRESS = false;
 constexpr bool INGRESS = true;
 
+// The priority of clat/tether hooks - smaller is higher priority.
+constexpr uint16_t PRIO_CLAT = 1;
+constexpr uint16_t PRIO_TETHER = 2;
+
 // this returns an ARPHRD_* constant or a -errno
 int hardwareAddressType(const std::string& interface);
 
-// return MTU or -errno
-int deviceMTU(const std::string& interface);
-
 base::Result<bool> isEthernet(const std::string& interface);
 
-inline int getClatEgress4MapFd(void) {
-    const int fd = bpf::mapRetrieveRW(CLAT_EGRESS4_MAP_PATH);
+inline int getClatEgressMapFd(void) {
+    const int fd = bpf::mapRetrieveRW(CLAT_EGRESS_MAP_PATH);
     return (fd == -1) ? -errno : fd;
 }
 
-inline int getClatEgress4ProgFd(bool with_ethernet_header) {
-    const int fd = bpf::retrieveProgram(with_ethernet_header ? CLAT_EGRESS4_PROG_ETHER_PATH
-                                                             : CLAT_EGRESS4_PROG_RAWIP_PATH);
+inline int getClatEgressProgFd(bool with_ethernet_header) {
+    const int fd = bpf::retrieveProgram(with_ethernet_header ? CLAT_EGRESS_PROG_ETHER_PATH
+                                                             : CLAT_EGRESS_PROG_RAWIP_PATH);
     return (fd == -1) ? -errno : fd;
 }
 
-inline int getClatIngress6MapFd(void) {
-    const int fd = bpf::mapRetrieveRW(CLAT_INGRESS6_MAP_PATH);
+inline int getClatIngressMapFd(void) {
+    const int fd = bpf::mapRetrieveRW(CLAT_INGRESS_MAP_PATH);
     return (fd == -1) ? -errno : fd;
 }
 
-inline int getClatIngress6ProgFd(bool with_ethernet_header) {
-    const int fd = bpf::retrieveProgram(with_ethernet_header ? CLAT_INGRESS6_PROG_ETHER_PATH
-                                                             : CLAT_INGRESS6_PROG_RAWIP_PATH);
+inline int getClatIngressProgFd(bool with_ethernet_header) {
+    const int fd = bpf::retrieveProgram(with_ethernet_header ? CLAT_INGRESS_PROG_ETHER_PATH
+                                                             : CLAT_INGRESS_PROG_RAWIP_PATH);
+    return (fd == -1) ? -errno : fd;
+}
+
+inline int getTetherIngressMapFd(void) {
+    const int fd = bpf::mapRetrieveRW(TETHER_INGRESS_MAP_PATH);
+    return (fd == -1) ? -errno : fd;
+}
+
+inline int getTetherIngressProgFd(bool with_ethernet_header) {
+    const int fd = bpf::retrieveProgram(with_ethernet_header ? TETHER_INGRESS_PROG_ETHER_PATH
+                                                             : TETHER_INGRESS_PROG_RAWIP_PATH);
+    return (fd == -1) ? -errno : fd;
+}
+
+inline int getTetherStatsMapFd(void) {
+    const int fd = bpf::mapRetrieveRW(TETHER_STATS_MAP_PATH);
+    return (fd == -1) ? -errno : fd;
+}
+
+inline int getTetherLimitMapFd(void) {
+    const int fd = bpf::mapRetrieveRW(TETHER_LIMIT_MAP_PATH);
     return (fd == -1) ? -errno : fd;
 }
 
@@ -83,31 +104,42 @@
     return doTcQdiscClsact(ifIndex, RTM_DELQDISC, 0);
 }
 
-// tc filter add dev .. in/egress prio 4 protocol ipv6/ip bpf object-pinned /sys/fs/bpf/...
+// tc filter add dev .. in/egress prio 1 protocol ipv6/ip bpf object-pinned /sys/fs/bpf/...
 // direct-action
-int tcFilterAddDevBpf(int ifIndex, bool ingress, uint16_t proto, int bpfFd, bool ethernet);
+int tcFilterAddDevBpf(int ifIndex, bool ingress, uint16_t prio, uint16_t proto, int bpfFd,
+                      bool ethernet);
 
-// tc filter add dev .. ingress prio 4 protocol ipv6 bpf object-pinned /sys/fs/bpf/... direct-action
+// tc filter add dev .. ingress prio 1 protocol ipv6 bpf object-pinned /sys/fs/bpf/... direct-action
 inline int tcFilterAddDevIngressClatIpv6(int ifIndex, int bpfFd, bool ethernet) {
-    return tcFilterAddDevBpf(ifIndex, INGRESS, ETH_P_IPV6, bpfFd, ethernet);
+    return tcFilterAddDevBpf(ifIndex, INGRESS, PRIO_CLAT, ETH_P_IPV6, bpfFd, ethernet);
 }
 
-// tc filter add dev .. egress prio 4 protocol ip bpf object-pinned /sys/fs/bpf/... direct-action
+// tc filter add dev .. egress prio 1 protocol ip bpf object-pinned /sys/fs/bpf/... direct-action
 inline int tcFilterAddDevEgressClatIpv4(int ifIndex, int bpfFd, bool ethernet) {
-    return tcFilterAddDevBpf(ifIndex, EGRESS, ETH_P_IP, bpfFd, ethernet);
+    return tcFilterAddDevBpf(ifIndex, EGRESS, PRIO_CLAT, ETH_P_IP, bpfFd, ethernet);
+}
+
+// tc filter add dev .. ingress prio 2 protocol ipv6 bpf object-pinned /sys/fs/bpf/... direct-action
+inline int tcFilterAddDevIngressTether(int ifIndex, int bpfFd, bool ethernet) {
+    return tcFilterAddDevBpf(ifIndex, INGRESS, PRIO_TETHER, ETH_P_IPV6, bpfFd, ethernet);
 }
 
 // tc filter del dev .. in/egress prio .. protocol ..
-int tcFilterDelDev(int ifIndex, bool ingress, uint16_t proto);
+int tcFilterDelDev(int ifIndex, bool ingress, uint16_t prio, uint16_t proto);
 
-// tc filter del dev .. ingress prio 4 protocol ipv6
+// tc filter del dev .. ingress prio 1 protocol ipv6
 inline int tcFilterDelDevIngressClatIpv6(int ifIndex) {
-    return tcFilterDelDev(ifIndex, INGRESS, ETH_P_IPV6);
+    return tcFilterDelDev(ifIndex, INGRESS, PRIO_CLAT, ETH_P_IPV6);
 }
 
-// tc filter del dev .. egress prio 4 protocol ip
+// tc filter del dev .. egress prio 1 protocol ip
 inline int tcFilterDelDevEgressClatIpv4(int ifIndex) {
-    return tcFilterDelDev(ifIndex, EGRESS, ETH_P_IP);
+    return tcFilterDelDev(ifIndex, EGRESS, PRIO_CLAT, ETH_P_IP);
+}
+
+// tc filter del dev .. ingress prio 2 protocol ipv6
+inline int tcFilterDelDevIngressTether(int ifIndex) {
+    return tcFilterDelDev(ifIndex, INGRESS, PRIO_TETHER, ETH_P_IPV6);
 }
 
 }  // namespace net
diff --git a/server/OffloadUtilsTest.cpp b/server/OffloadUtilsTest.cpp
index 16c108b..1760c1d 100644
--- a/server/OffloadUtilsTest.cpp
+++ b/server/OffloadUtilsTest.cpp
@@ -98,60 +98,160 @@
     ASSERT_FALSE(res.value());
 }
 
-TEST_F(OffloadUtilsTest, DeviceMTUOfNonExistingIf) {
-    ASSERT_EQ(-ENODEV, deviceMTU("not_existing_if"));
-}
+TEST_F(OffloadUtilsTest, GetClatEgressMapFd) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
 
-TEST_F(OffloadUtilsTest, DeviceMTUofLoopback) {
-    ASSERT_EQ(65536, deviceMTU("lo"));
-}
-
-TEST_F(OffloadUtilsTest, GetClatEgress4MapFd) {
-    int fd = getClatEgress4MapFd();
+    int fd = getClatEgressMapFd();
     ASSERT_GE(fd, 3);  // 0,1,2 - stdin/out/err, thus fd >= 3
     EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
     close(fd);
 }
 
-TEST_F(OffloadUtilsTest, GetClatEgress4RawIpProgFd) {
-    int fd = getClatEgress4ProgFd(RAWIP);
+TEST_F(OffloadUtilsTest, GetClatEgressRawIpProgFd) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    int fd = getClatEgressProgFd(RAWIP);
     ASSERT_GE(fd, 3);
     EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
     close(fd);
 }
 
-TEST_F(OffloadUtilsTest, GetClatEgress4EtherProgFd) {
-    int fd = getClatEgress4ProgFd(ETHER);
+TEST_F(OffloadUtilsTest, GetClatEgressEtherProgFd) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    int fd = getClatEgressProgFd(ETHER);
     ASSERT_GE(fd, 3);
     EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
     close(fd);
 }
 
-TEST_F(OffloadUtilsTest, GetClatIngress6MapFd) {
-    int fd = getClatIngress6MapFd();
+TEST_F(OffloadUtilsTest, GetClatIngressMapFd) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    int fd = getClatIngressMapFd();
     ASSERT_GE(fd, 3);  // 0,1,2 - stdin/out/err, thus fd >= 3
     EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
     close(fd);
 }
 
-TEST_F(OffloadUtilsTest, GetClatIngress6RawIpProgFd) {
-    int fd = getClatIngress6ProgFd(RAWIP);
+TEST_F(OffloadUtilsTest, GetClatIngressRawIpProgFd) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    int fd = getClatIngressProgFd(RAWIP);
     ASSERT_GE(fd, 3);
     EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
     close(fd);
 }
 
-TEST_F(OffloadUtilsTest, GetClatIngress6EtherProgFd) {
-    int fd = getClatIngress6ProgFd(ETHER);
+TEST_F(OffloadUtilsTest, GetClatIngressEtherProgFd) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    int fd = getClatIngressProgFd(ETHER);
     ASSERT_GE(fd, 3);
     EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
     close(fd);
 }
 
+TEST_F(OffloadUtilsTest, GetTetherIngressMapFd) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    int fd = getTetherIngressMapFd();
+    ASSERT_GE(fd, 3);  // 0,1,2 - stdin/out/err, thus fd >= 3
+    EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+    close(fd);
+}
+
+TEST_F(OffloadUtilsTest, GetTetherIngressRawIpProgFd) {
+    // Currently only implementing downstream direction offload.
+    // RX Rawip -> TX Ether requires header adjustments and thus 4.14.
+    SKIP_IF_EXTENDED_BPF_NOT_SUPPORTED;
+
+    int fd = getTetherIngressProgFd(RAWIP);
+    ASSERT_GE(fd, 3);
+    EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+    close(fd);
+}
+
+TEST_F(OffloadUtilsTest, GetTetherIngressEtherProgFd) {
+    // Currently only implementing downstream direction offload.
+    // RX Ether -> TX Ether does not require header adjustments
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    int fd = getTetherIngressProgFd(ETHER);
+    ASSERT_GE(fd, 3);
+    EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+    close(fd);
+}
+
+TEST_F(OffloadUtilsTest, GetTetherStatsMapFd) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    int fd = getTetherStatsMapFd();
+    ASSERT_GE(fd, 3);  // 0,1,2 - stdin/out/err, thus fd >= 3
+    EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+    close(fd);
+}
+
+TEST_F(OffloadUtilsTest, GetTetherLimitMapFd) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    int fd = getTetherLimitMapFd();
+    ASSERT_GE(fd, 3);  // 0,1,2 - stdin/out/err, thus fd >= 3
+    EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+    close(fd);
+}
+
+// The SKIP_IF_BPF_NOT_SUPPORTED macro is effectively a check for 4.9+ kernel
+// combined with a launched on P device.  Ie. it's a test for 4.9-P or better.
+
+// NET_SCH_INGRESS is only enabled starting with 4.9-Q and as such we need
+// a separate way to test for this...
+int doKernelSupportsNetSchIngress(void) {
+    // NOLINTNEXTLINE(cert-env33-c)
+    return system("zcat /proc/config.gz | egrep -q '^CONFIG_NET_SCH_INGRESS=[my]$'");
+}
+
+// NET_CLS_BPF is only enabled starting with 4.9-Q...
+int doKernelSupportsNetClsBpf(void) {
+    // NOLINTNEXTLINE(cert-env33-c)
+    return system("zcat /proc/config.gz | egrep -q '^CONFIG_NET_CLS_BPF=[my]$'");
+}
+
+// Make sure the above functions actually execute correctly rather than failing
+// due to missing binary or execution failure...
+TEST_F(OffloadUtilsTest, KernelSupportsNetFuncs) {
+    // Make sure the file is present and readable and decompressable.
+    // NOLINTNEXTLINE(cert-env33-c)
+    ASSERT_EQ(W_EXITCODE(0, 0), system("zcat /proc/config.gz > /dev/null"));
+
+    int v = doKernelSupportsNetSchIngress();
+    int w = doKernelSupportsNetClsBpf();
+
+    // They should always either return 0 (match) or 1 (no match),
+    // anything else is some sort of exec/environment/etc failure.
+    if (v != W_EXITCODE(1, 0)) ASSERT_EQ(v, W_EXITCODE(0, 0));
+    if (w != W_EXITCODE(1, 0)) ASSERT_EQ(w, W_EXITCODE(0, 0));
+}
+
+// True iff CONFIG_NET_SCH_INGRESS is enabled in /proc/config.gz
+bool kernelSupportsNetSchIngress(void) {
+    return doKernelSupportsNetSchIngress() == W_EXITCODE(0, 0);
+}
+
+// True iff CONFIG_NET_CLS_BPF is enabled in /proc/config.gz
+bool kernelSupportsNetClsBpf(void) {
+    return doKernelSupportsNetClsBpf() == W_EXITCODE(0, 0);
+}
+
 // See Linux kernel source in include/net/flow.h
 #define LOOPBACK_IFINDEX 1
 
 TEST_F(OffloadUtilsTest, AttachReplaceDetachClsactLo) {
+    // Technically does not depend on ebpf, but does depend on clsact,
+    // and we do not really care if it works on pre-4.9-Q anyway.
+    SKIP_IF_BPF_NOT_SUPPORTED;
+    if (!kernelSupportsNetSchIngress()) return;
+
     // This attaches and detaches a configuration-less and thus no-op clsact
     // qdisc to loopback interface (and it takes fractions of a second)
     EXPECT_EQ(0, tcQdiscAddDevClsact(LOOPBACK_IFINDEX));
@@ -161,12 +261,27 @@
 }
 
 static void checkAttachDetachBpfFilterClsactLo(const bool ingress, const bool ethernet) {
-    // Older kernels return EINVAL instead of ENOENT due to lacking proper error propagation...
-    const int errNOENT = android::bpf::isAtLeastKernelVersion(4, 19, 0) ? ENOENT : EINVAL;
+    // This test requires kernel 4.9-Q or better
+    SKIP_IF_BPF_NOT_SUPPORTED;
+    if (!kernelSupportsNetSchIngress()) return;
+    if (!kernelSupportsNetClsBpf()) return;
 
-    int clatBpfFd = ingress ? getClatIngress6ProgFd(ethernet) : getClatEgress4ProgFd(ethernet);
+    const bool extended =
+            (android::bpf::getBpfSupportLevel() >= android::bpf::BpfLevel::EXTENDED_4_14);
+    // Older kernels return EINVAL instead of ENOENT due to lacking proper error propagation...
+    const int errNOENT =
+            (android::bpf::getBpfSupportLevel() >= android::bpf::BpfLevel::EXTENDED_4_19) ? ENOENT
+                                                                                          : EINVAL;
+
+    int clatBpfFd = ingress ? getClatIngressProgFd(ethernet) : getClatEgressProgFd(ethernet);
     ASSERT_GE(clatBpfFd, 3);
 
+    int tetherBpfFd = -1;
+    if (extended && ingress) {
+        tetherBpfFd = getTetherIngressProgFd(ethernet);
+        ASSERT_GE(tetherBpfFd, 3);
+    }
+
     // This attaches and detaches a clsact plus ebpf program to loopback
     // interface, but it should not affect traffic by virtue of us not
     // actually populating the ebpf control map.
@@ -178,6 +293,10 @@
     EXPECT_EQ(-errNOENT, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
     if (ingress) {
         EXPECT_EQ(0, tcFilterAddDevIngressClatIpv6(LOOPBACK_IFINDEX, clatBpfFd, ethernet));
+        if (extended) {
+            EXPECT_EQ(0, tcFilterAddDevIngressTether(LOOPBACK_IFINDEX, tetherBpfFd, ethernet));
+            EXPECT_EQ(0, tcFilterDelDevIngressTether(LOOPBACK_IFINDEX));
+        }
         EXPECT_EQ(0, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
     } else {
         EXPECT_EQ(0, tcFilterAddDevEgressClatIpv4(LOOPBACK_IFINDEX, clatBpfFd, ethernet));
@@ -189,6 +308,7 @@
     EXPECT_EQ(-EINVAL, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
     EXPECT_EQ(-EINVAL, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
 
+    if (tetherBpfFd != -1) close(tetherBpfFd);
     close(clatBpfFd);
 }
 
diff --git a/server/PhysicalNetwork.cpp b/server/PhysicalNetwork.cpp
index 7b9a19a..2808fbe 100644
--- a/server/PhysicalNetwork.cpp
+++ b/server/PhysicalNetwork.cpp
@@ -158,36 +158,8 @@
     return 0;
 }
 
-int PhysicalNetwork::addUsers(const UidRanges& uidRanges, uint32_t subPriority) {
-    if (!isValidSubPriority(subPriority) || !canAddUidRanges(uidRanges, subPriority)) {
-        return -EINVAL;
-    }
-
-    for (const std::string& interface : mInterfaces) {
-        int ret = RouteController::addUsersToPhysicalNetwork(mNetId, interface.c_str(),
-                                                             {{subPriority, uidRanges}});
-        if (ret) {
-            ALOGE("failed to add users on interface %s of netId %u", interface.c_str(), mNetId);
-            return ret;
-        }
-    }
-    addToUidRangeMap(uidRanges, subPriority);
-    return 0;
-}
-
-int PhysicalNetwork::removeUsers(const UidRanges& uidRanges, uint32_t subPriority) {
-    if (!isValidSubPriority(subPriority)) return -EINVAL;
-
-    for (const std::string& interface : mInterfaces) {
-        int ret = RouteController::removeUsersFromPhysicalNetwork(mNetId, interface.c_str(),
-                                                                  {{subPriority, uidRanges}});
-        if (ret) {
-            ALOGE("failed to remove users on interface %s of netId %u", interface.c_str(), mNetId);
-            return ret;
-        }
-    }
-    removeFromUidRangeMap(uidRanges, subPriority);
-    return 0;
+Network::Type PhysicalNetwork::getType() const {
+    return PHYSICAL;
 }
 
 int PhysicalNetwork::addInterface(const std::string& interface) {
@@ -195,7 +167,7 @@
         return 0;
     }
     if (int ret = RouteController::addInterfaceToPhysicalNetwork(mNetId, interface.c_str(),
-                                                                 mPermission, mUidRangeMap)) {
+                                                                 mPermission)) {
         ALOGE("failed to add interface %s to netId %u", interface.c_str(), mNetId);
         return ret;
     }
@@ -222,7 +194,7 @@
     // to find the interface index in the cache in cases where the interface is already gone
     // (e.g. bt-pan).
     if (int ret = RouteController::removeInterfaceFromPhysicalNetwork(mNetId, interface.c_str(),
-                                                                      mPermission, mUidRangeMap)) {
+                                                                      mPermission)) {
         ALOGE("failed to remove interface %s from netId %u", interface.c_str(), mNetId);
         return ret;
     }
@@ -230,9 +202,4 @@
     return 0;
 }
 
-bool PhysicalNetwork::isValidSubPriority(uint32_t priority) {
-    return priority >= UidRanges::DEFAULT_SUB_PRIORITY &&
-           priority <= UidRanges::LOWEST_SUB_PRIORITY;
-}
-
 }  // namespace android::net
diff --git a/server/PhysicalNetwork.h b/server/PhysicalNetwork.h
index d9461b2..0720824 100644
--- a/server/PhysicalNetwork.h
+++ b/server/PhysicalNetwork.h
@@ -42,18 +42,13 @@
 
     [[nodiscard]] int addAsDefault();
     [[nodiscard]] int removeAsDefault();
-    [[nodiscard]] int addUsers(const UidRanges& uidRanges, uint32_t subPriority) override;
-    [[nodiscard]] int removeUsers(const UidRanges& uidRanges, uint32_t subPriority) override;
-    bool isPhysical() override { return true; }
-    bool canAddUsers() override { return true; }
 
   private:
-    std::string getTypeString() const override { return "PHYSICAL"; };
+    Type getType() const override;
     [[nodiscard]] int addInterface(const std::string& interface) override;
     [[nodiscard]] int removeInterface(const std::string& interface) override;
     int destroySocketsLackingPermission(Permission permission);
     void invalidateRouteCache(const std::string& interface);
-    bool isValidSubPriority(uint32_t priority) override;
 
     Delegate* const mDelegate;
     Permission mPermission;
diff --git a/server/RouteController.cpp b/server/RouteController.cpp
index ba305e6..134bbca 100644
--- a/server/RouteController.cpp
+++ b/server/RouteController.cpp
@@ -27,11 +27,14 @@
 
 #include <map>
 
+#define LOG_TAG "Netd"
+
 #include "DummyNetwork.h"
 #include "Fwmark.h"
 #include "NetdConstants.h"
 #include "NetlinkCommands.h"
 #include "OffloadUtils.h"
+#include "UidRanges.h"
 
 #include <android-base/file.h>
 #include <android-base/stringprintf.h>
@@ -51,6 +54,23 @@
 
 // BEGIN CONSTANTS --------------------------------------------------------------------------------
 
+const uint32_t RULE_PRIORITY_VPN_OVERRIDE_SYSTEM = 10000;
+const uint32_t RULE_PRIORITY_VPN_OVERRIDE_OIF    = 10500;
+const uint32_t RULE_PRIORITY_VPN_OUTPUT_TO_LOCAL = 11000;
+const uint32_t RULE_PRIORITY_SECURE_VPN          = 12000;
+const uint32_t RULE_PRIORITY_PROHIBIT_NON_VPN    = 12500;
+const uint32_t RULE_PRIORITY_EXPLICIT_NETWORK    = 13000;
+const uint32_t RULE_PRIORITY_OUTPUT_INTERFACE    = 14000;
+const uint32_t RULE_PRIORITY_LEGACY_SYSTEM       = 15000;
+const uint32_t RULE_PRIORITY_LEGACY_NETWORK      = 16000;
+const uint32_t RULE_PRIORITY_LOCAL_NETWORK       = 17000;
+const uint32_t RULE_PRIORITY_TETHERING           = 18000;
+const uint32_t RULE_PRIORITY_IMPLICIT_NETWORK    = 19000;
+const uint32_t RULE_PRIORITY_BYPASSABLE_VPN      = 20000;
+const uint32_t RULE_PRIORITY_VPN_FALLTHROUGH     = 21000;
+const uint32_t RULE_PRIORITY_DEFAULT_NETWORK     = 22000;
+const uint32_t RULE_PRIORITY_UNREACHABLE         = 32000;
+
 const uint32_t ROUTE_TABLE_LOCAL_NETWORK  = 97;
 const uint32_t ROUTE_TABLE_LEGACY_NETWORK = 98;
 const uint32_t ROUTE_TABLE_LEGACY_SYSTEM  = 99;
@@ -111,9 +131,6 @@
 
 uint8_t PADDING_BUFFER[RTA_ALIGNTO] = {0, 0, 0, 0};
 
-constexpr bool EXPLICIT = true;
-constexpr bool IMPLICIT = false;
-
 // END CONSTANTS ----------------------------------------------------------------------------------
 
 static const char* actionName(uint16_t action) {
@@ -129,8 +146,6 @@
     }
 }
 
-static void maybeModifyQdiscClsact(const char* interface, bool add);
-
 // Caller must hold sInterfaceToTableLock.
 uint32_t RouteController::getRouteTableForInterfaceLocked(const char* interface) {
     // If we already know the routing table for this interface name, use it.
@@ -492,7 +507,7 @@
 // have, if they are subject to this VPN, their traffic has to go through it. Allows the traffic to
 // bypass the VPN if the protectedFromVpn bit is set.
 [[nodiscard]] static int modifyVpnUidRangeRule(uint32_t table, uid_t uidStart, uid_t uidEnd,
-                                               uint32_t subPriority, bool secure, bool add) {
+                                               bool secure, bool add) {
     Fwmark fwmark;
     Fwmark mask;
 
@@ -510,8 +525,8 @@
         mask.explicitlySelected = true;
     }
 
-    return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, priority + subPriority, table,
-                        fwmark.intValue, mask.intValue, IIF_LOOPBACK, OIF_NONE, uidStart, uidEnd);
+    return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, priority, table, fwmark.intValue,
+                        mask.intValue, IIF_LOOPBACK, OIF_NONE, uidStart, uidEnd);
 }
 
 // A rule to allow system apps to send traffic over this VPN even if they are not part of the target
@@ -545,7 +560,7 @@
 // modifyNetworkPermission().
 [[nodiscard]] static int modifyExplicitNetworkRule(unsigned netId, uint32_t table,
                                                    Permission permission, uid_t uidStart,
-                                                   uid_t uidEnd, uint32_t subPriority, bool add) {
+                                                   uid_t uidEnd, bool add) {
     Fwmark fwmark;
     Fwmark mask;
 
@@ -558,9 +573,8 @@
     fwmark.permission = permission;
     mask.permission = permission;
 
-    return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE,
-                        RULE_PRIORITY_EXPLICIT_NETWORK + subPriority, table, fwmark.intValue,
-                        mask.intValue, IIF_LOOPBACK, OIF_NONE, uidStart, uidEnd);
+    return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_EXPLICIT_NETWORK, table,
+                        fwmark.intValue, mask.intValue, IIF_LOOPBACK, OIF_NONE, uidStart, uidEnd);
 }
 
 // A rule to route traffic based on a chosen outgoing interface.
@@ -569,7 +583,7 @@
 // the outgoing interface (typically for link-local communications).
 [[nodiscard]] static int modifyOutputInterfaceRules(const char* interface, uint32_t table,
                                                     Permission permission, uid_t uidStart,
-                                                    uid_t uidEnd, uint32_t subPriority, bool add) {
+                                                    uid_t uidEnd, bool add) {
     Fwmark fwmark;
     Fwmark mask;
 
@@ -587,9 +601,8 @@
         }
     }
 
-    return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE,
-                        RULE_PRIORITY_OUTPUT_INTERFACE + subPriority, table, fwmark.intValue,
-                        mask.intValue, IIF_LOOPBACK, interface, uidStart, uidEnd);
+    return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_OUTPUT_INTERFACE, table,
+                        fwmark.intValue, mask.intValue, IIF_LOOPBACK, interface, uidStart, uidEnd);
 }
 
 // A rule to route traffic based on the chosen network.
@@ -669,8 +682,7 @@
 // Add rules to lookup the local network when specified explicitly or otherwise.
 [[nodiscard]] static int addLocalNetworkRules(unsigned localNetId) {
     if (int ret = modifyExplicitNetworkRule(localNetId, ROUTE_TABLE_LOCAL_NETWORK, PERMISSION_NONE,
-                                            INVALID_UID, INVALID_UID,
-                                            UidRanges::DEFAULT_SUB_PRIORITY, ACTION_ADD)) {
+                                            INVALID_UID, INVALID_UID, ACTION_ADD)) {
         return ret;
     }
 
@@ -701,9 +713,8 @@
         return -errno;
     }
 
-    if ((ret = modifyOutputInterfaceRules(interface, table, PERMISSION_NONE, INVALID_UID,
-                                          INVALID_UID, UidRanges::DEFAULT_SUB_PRIORITY,
-                                          ACTION_ADD))) {
+    if ((ret = modifyOutputInterfaceRules(interface, table, PERMISSION_NONE,
+                                          INVALID_UID, INVALID_UID, ACTION_ADD))) {
         ALOGE("Can't create oif rules for %s: %s", interface, strerror(-ret));
         return ret;
     }
@@ -734,102 +745,27 @@
     if (int ret = modifyIncomingPacketMark(netId, interface, PERMISSION_NONE, add)) {
         return ret;
     }
-    maybeModifyQdiscClsact(interface, add);
     return modifyOutputInterfaceRules(interface, ROUTE_TABLE_LOCAL_NETWORK, PERMISSION_NONE,
-                                      INVALID_UID, INVALID_UID, UidRanges::DEFAULT_SUB_PRIORITY,
-                                      add);
-}
-
-[[nodiscard]] static int modifyUidNetworkRule(unsigned netId, uint32_t table, uid_t uidStart,
-                                              uid_t uidEnd, uint32_t subPriority, bool add,
-                                              bool explicitSelect) {
-    if ((uidStart == INVALID_UID) || (uidEnd == INVALID_UID)) {
-        ALOGE("modifyUidNetworkRule, invalid UIDs (%u, %u)", uidStart, uidEnd);
-        return -EUSERS;
-    }
-
-    Fwmark fwmark;
-    Fwmark mask;
-
-    fwmark.netId = netId;
-    mask.netId = FWMARK_NET_ID_MASK;
-
-    fwmark.explicitlySelected = explicitSelect;
-    mask.explicitlySelected = true;
-
-    // Access to this network is controlled by UID rules, not permission bits.
-    fwmark.permission = PERMISSION_NONE;
-    mask.permission = PERMISSION_NONE;
-
-    return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE,
-                        explicitSelect ? (RULE_PRIORITY_UID_EXPLICIT_NETWORK + subPriority)
-                                       : (RULE_PRIORITY_UID_IMPLICIT_NETWORK + subPriority),
-                        table, fwmark.intValue, mask.intValue, IIF_LOOPBACK, OIF_NONE, uidStart,
-                        uidEnd);
-}
-
-[[nodiscard]] static int modifyUidDefaultNetworkRule(uint32_t table, uid_t uidStart, uid_t uidEnd,
-                                                     uint32_t subPriority, bool add) {
-    if ((uidStart == INVALID_UID) || (uidEnd == INVALID_UID)) {
-        ALOGE("modifyUidDefaultNetworkRule, invalid UIDs (%u, %u)", uidStart, uidEnd);
-        return -EUSERS;
-    }
-
-    Fwmark fwmark;
-    Fwmark mask;
-
-    fwmark.netId = NETID_UNSET;
-    mask.netId = FWMARK_NET_ID_MASK;
-
-    // Access to this network is controlled by UID rules, not permission bits.
-    fwmark.permission = PERMISSION_NONE;
-    mask.permission = PERMISSION_NONE;
-
-    return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE,
-                        RULE_PRIORITY_UID_DEFAULT_NETWORK + subPriority, table, fwmark.intValue,
-                        mask.intValue, IIF_LOOPBACK, OIF_NONE, uidStart, uidEnd);
+                                      INVALID_UID, INVALID_UID, add);
 }
 
 /* static */
 int RouteController::modifyPhysicalNetwork(unsigned netId, const char* interface,
-                                           const UidRangeMap& uidRangeMap, Permission permission,
-                                           bool add, bool modifyNonUidBasedRules) {
+                                           Permission permission, bool add) {
     uint32_t table = getRouteTableForInterface(interface);
     if (table == RT_TABLE_UNSPEC) {
         return -ESRCH;
     }
 
-    for (const auto& [subPriority, uidRanges] : uidRangeMap) {
-        for (const UidRangeParcel& range : uidRanges.getRanges()) {
-            if (int ret = modifyUidNetworkRule(netId, table, range.start, range.stop, subPriority,
-                                               add, EXPLICIT)) {
-                return ret;
-            }
-            if (int ret = modifyUidNetworkRule(netId, table, range.start, range.stop, subPriority,
-                                               add, IMPLICIT)) {
-                return ret;
-            }
-            if (int ret = modifyUidDefaultNetworkRule(table, range.start, range.stop, subPriority,
-                                                      add)) {
-                return ret;
-            }
-        }
-    }
-
-    if (!modifyNonUidBasedRules) {
-        // we are done.
-        return 0;
-    }
-
     if (int ret = modifyIncomingPacketMark(netId, interface, permission, add)) {
         return ret;
     }
     if (int ret = modifyExplicitNetworkRule(netId, table, permission, INVALID_UID, INVALID_UID,
-                                            UidRanges::DEFAULT_SUB_PRIORITY, add)) {
+                                            add)) {
         return ret;
     }
     if (int ret = modifyOutputInterfaceRules(interface, table, permission, INVALID_UID, INVALID_UID,
-                                             UidRanges::DEFAULT_SUB_PRIORITY, add)) {
+                                            add)) {
         return ret;
     }
 
@@ -859,79 +795,6 @@
     return 0;
 }
 
-[[nodiscard]] static int modifyUidUnreachableRule(unsigned netId, uid_t uidStart, uid_t uidEnd,
-                                                  uint32_t subPriority, bool add,
-                                                  bool explicitSelect) {
-    if ((uidStart == INVALID_UID) || (uidEnd == INVALID_UID)) {
-        ALOGE("modifyUidUnreachableRule, invalid UIDs (%u, %u)", uidStart, uidEnd);
-        return -EUSERS;
-    }
-
-    Fwmark fwmark;
-    Fwmark mask;
-
-    fwmark.netId = netId;
-    mask.netId = FWMARK_NET_ID_MASK;
-
-    fwmark.explicitlySelected = explicitSelect;
-    mask.explicitlySelected = true;
-
-    // Access to this network is controlled by UID rules, not permission bits.
-    fwmark.permission = PERMISSION_NONE;
-    mask.permission = PERMISSION_NONE;
-
-    return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE,
-                        explicitSelect ? (RULE_PRIORITY_UID_EXPLICIT_NETWORK + subPriority)
-                                       : (RULE_PRIORITY_UID_IMPLICIT_NETWORK + subPriority),
-                        FR_ACT_UNREACHABLE, RT_TABLE_UNSPEC, fwmark.intValue, mask.intValue,
-                        IIF_LOOPBACK, OIF_NONE, uidStart, uidEnd);
-}
-
-[[nodiscard]] static int modifyUidDefaultUnreachableRule(uid_t uidStart, uid_t uidEnd,
-                                                         uint32_t subPriority, bool add) {
-    if ((uidStart == INVALID_UID) || (uidEnd == INVALID_UID)) {
-        ALOGE("modifyUidDefaultUnreachableRule, invalid UIDs (%u, %u)", uidStart, uidEnd);
-        return -EUSERS;
-    }
-
-    Fwmark fwmark;
-    Fwmark mask;
-
-    fwmark.netId = NETID_UNSET;
-    mask.netId = FWMARK_NET_ID_MASK;
-
-    // Access to this network is controlled by UID rules, not permission bits.
-    fwmark.permission = PERMISSION_NONE;
-    mask.permission = PERMISSION_NONE;
-
-    return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE,
-                        RULE_PRIORITY_UID_DEFAULT_UNREACHABLE + subPriority, FR_ACT_UNREACHABLE,
-                        RT_TABLE_UNSPEC, fwmark.intValue, mask.intValue, IIF_LOOPBACK, OIF_NONE,
-                        uidStart, uidEnd);
-}
-
-int RouteController::modifyUnreachableNetwork(unsigned netId, const UidRangeMap& uidRangeMap,
-                                              bool add) {
-    for (const auto& [subPriority, uidRanges] : uidRangeMap) {
-        for (const UidRangeParcel& range : uidRanges.getRanges()) {
-            if (int ret = modifyUidUnreachableRule(netId, range.start, range.stop, subPriority, add,
-                                                   EXPLICIT)) {
-                return ret;
-            }
-            if (int ret = modifyUidUnreachableRule(netId, range.start, range.stop, subPriority, add,
-                                                   IMPLICIT)) {
-                return ret;
-            }
-            if (int ret = modifyUidDefaultUnreachableRule(range.start, range.stop, subPriority,
-                                                          add)) {
-                return ret;
-            }
-        }
-    }
-
-    return 0;
-}
-
 [[nodiscard]] static int modifyRejectNonSecureNetworkRule(const UidRanges& uidRanges, bool add) {
     Fwmark fwmark;
     Fwmark mask;
@@ -950,27 +813,24 @@
 }
 
 int RouteController::modifyVirtualNetwork(unsigned netId, const char* interface,
-                                          const UidRangeMap& uidRangeMap, bool secure, bool add,
+                                          const UidRanges& uidRanges, bool secure, bool add,
                                           bool modifyNonUidBasedRules) {
     uint32_t table = getRouteTableForInterface(interface);
     if (table == RT_TABLE_UNSPEC) {
         return -ESRCH;
     }
 
-    for (const auto& [subPriority, uidRanges] : uidRangeMap) {
-        for (const UidRangeParcel& range : uidRanges.getRanges()) {
-            if (int ret = modifyVpnUidRangeRule(table, range.start, range.stop, subPriority, secure,
-                                                add)) {
-                return ret;
-            }
-            if (int ret = modifyExplicitNetworkRule(netId, table, PERMISSION_NONE, range.start,
-                                                    range.stop, subPriority, add)) {
-                return ret;
-            }
-            if (int ret = modifyOutputInterfaceRules(interface, table, PERMISSION_NONE, range.start,
-                                                     range.stop, subPriority, add)) {
-                return ret;
-            }
+    for (const UidRangeParcel& range : uidRanges.getRanges()) {
+        if (int ret = modifyVpnUidRangeRule(table, range.start, range.stop, secure, add)) {
+            return ret;
+        }
+        if (int ret = modifyExplicitNetworkRule(netId, table, PERMISSION_NONE, range.start,
+                                                range.stop, add)) {
+            return ret;
+        }
+        if (int ret = modifyOutputInterfaceRules(interface, table, PERMISSION_NONE, range.start,
+                                                 range.stop, add)) {
+            return ret;
         }
     }
 
@@ -984,8 +844,7 @@
         if (int ret = modifyVpnSystemPermissionRule(netId, table, secure, add)) {
             return ret;
         }
-        return modifyExplicitNetworkRule(netId, table, PERMISSION_NONE, UID_ROOT, UID_ROOT,
-                                         UidRanges::DEFAULT_SUB_PRIORITY, add);
+        return modifyExplicitNetworkRule(netId, table, PERMISSION_NONE, UID_ROOT, UID_ROOT, add);
     }
 
     return 0;
@@ -1059,7 +918,9 @@
     return 0;
 }
 
-static void maybeModifyQdiscClsact(const char* interface, bool add) {
+void maybeModifyQdiscClsact(const char* interface, bool add) {
+    if (!bpf::isBpfSupported()) return;
+
     // The clsact attaching of v4- tun interface is triggered by ClatdController::maybeStartBpf
     // because the clat is started before the v4- interface is added to the network and the
     // clat startup needs to add {in, e}gress filters.
@@ -1186,10 +1047,8 @@
 }
 
 int RouteController::addInterfaceToPhysicalNetwork(unsigned netId, const char* interface,
-                                                   Permission permission,
-                                                   const UidRangeMap& uidRangeMap) {
-    if (int ret = modifyPhysicalNetwork(netId, interface, uidRangeMap, permission, ACTION_ADD,
-                                        MODIFY_NON_UID_BASED_RULES)) {
+                                                   Permission permission) {
+    if (int ret = modifyPhysicalNetwork(netId, interface, permission, ACTION_ADD)) {
         return ret;
     }
     maybeModifyQdiscClsact(interface, ACTION_ADD);
@@ -1198,10 +1057,8 @@
 }
 
 int RouteController::removeInterfaceFromPhysicalNetwork(unsigned netId, const char* interface,
-                                                        Permission permission,
-                                                        const UidRangeMap& uidRangeMap) {
-    if (int ret = modifyPhysicalNetwork(netId, interface, uidRangeMap, permission, ACTION_DEL,
-                                        MODIFY_NON_UID_BASED_RULES)) {
+                                                        Permission permission) {
+    if (int ret = modifyPhysicalNetwork(netId, interface, permission, ACTION_DEL)) {
         return ret;
     }
     if (int ret = flushRoutes(interface)) {
@@ -1216,8 +1073,8 @@
 }
 
 int RouteController::addInterfaceToVirtualNetwork(unsigned netId, const char* interface,
-                                                  bool secure, const UidRangeMap& uidRangeMap) {
-    if (int ret = modifyVirtualNetwork(netId, interface, uidRangeMap, secure, ACTION_ADD,
+                                                  bool secure, const UidRanges& uidRanges) {
+    if (int ret = modifyVirtualNetwork(netId, interface, uidRanges, secure, ACTION_ADD,
                                        MODIFY_NON_UID_BASED_RULES)) {
         return ret;
     }
@@ -1226,9 +1083,8 @@
 }
 
 int RouteController::removeInterfaceFromVirtualNetwork(unsigned netId, const char* interface,
-                                                       bool secure,
-                                                       const UidRangeMap& uidRangeMap) {
-    if (int ret = modifyVirtualNetwork(netId, interface, uidRangeMap, secure, ACTION_DEL,
+                                                       bool secure, const UidRanges& uidRanges) {
+    if (int ret = modifyVirtualNetwork(netId, interface, uidRanges, secure, ACTION_DEL,
                                        MODIFY_NON_UID_BASED_RULES)) {
         return ret;
     }
@@ -1242,16 +1098,11 @@
 int RouteController::modifyPhysicalNetworkPermission(unsigned netId, const char* interface,
                                                      Permission oldPermission,
                                                      Permission newPermission) {
-    // Physical network rules either use permission bits or UIDs, but not both.
-    // So permission changes don't affect any UID-based rules.
-    UidRangeMap emptyUidRangeMap;
     // Add the new rules before deleting the old ones, to avoid race conditions.
-    if (int ret = modifyPhysicalNetwork(netId, interface, emptyUidRangeMap, newPermission,
-                                        ACTION_ADD, MODIFY_NON_UID_BASED_RULES)) {
+    if (int ret = modifyPhysicalNetwork(netId, interface, newPermission, ACTION_ADD)) {
         return ret;
     }
-    return modifyPhysicalNetwork(netId, interface, emptyUidRangeMap, oldPermission, ACTION_DEL,
-                                 MODIFY_NON_UID_BASED_RULES);
+    return modifyPhysicalNetwork(netId, interface, oldPermission, ACTION_DEL);
 }
 
 int RouteController::addUsersToRejectNonSecureNetworkRule(const UidRanges& uidRanges) {
@@ -1263,14 +1114,14 @@
 }
 
 int RouteController::addUsersToVirtualNetwork(unsigned netId, const char* interface, bool secure,
-                                              const UidRangeMap& uidRangeMap) {
-    return modifyVirtualNetwork(netId, interface, uidRangeMap, secure, ACTION_ADD,
+                                              const UidRanges& uidRanges) {
+    return modifyVirtualNetwork(netId, interface, uidRanges, secure, ACTION_ADD,
                                 !MODIFY_NON_UID_BASED_RULES);
 }
 
 int RouteController::removeUsersFromVirtualNetwork(unsigned netId, const char* interface,
-                                                   bool secure, const UidRangeMap& uidRangeMap) {
-    return modifyVirtualNetwork(netId, interface, uidRangeMap, secure, ACTION_DEL,
+                                                   bool secure, const UidRanges& uidRanges) {
+    return modifyVirtualNetwork(netId, interface, uidRanges, secure, ACTION_DEL,
                                 !MODIFY_NON_UID_BASED_RULES);
 }
 
@@ -1320,27 +1171,6 @@
     return modifyVpnFallthroughRule(RTM_DELRULE, vpnNetId, physicalInterface, permission);
 }
 
-int RouteController::addUsersToPhysicalNetwork(unsigned netId, const char* interface,
-                                               const UidRangeMap& uidRangeMap) {
-    return modifyPhysicalNetwork(netId, interface, uidRangeMap, PERMISSION_NONE, ACTION_ADD,
-                                 !MODIFY_NON_UID_BASED_RULES);
-}
-
-int RouteController::removeUsersFromPhysicalNetwork(unsigned netId, const char* interface,
-                                                    const UidRangeMap& uidRangeMap) {
-    return modifyPhysicalNetwork(netId, interface, uidRangeMap, PERMISSION_NONE, ACTION_DEL,
-                                 !MODIFY_NON_UID_BASED_RULES);
-}
-
-int RouteController::addUsersToUnreachableNetwork(unsigned netId, const UidRangeMap& uidRangeMap) {
-    return modifyUnreachableNetwork(netId, uidRangeMap, ACTION_ADD);
-}
-
-int RouteController::removeUsersFromUnreachableNetwork(unsigned netId,
-                                                       const UidRangeMap& uidRangeMap) {
-    return modifyUnreachableNetwork(netId, uidRangeMap, ACTION_DEL);
-}
-
 // Protects sInterfaceToTable.
 std::mutex RouteController::sInterfaceToTableLock;
 std::map<std::string, uint32_t> RouteController::sInterfaceToTable;
diff --git a/server/RouteController.h b/server/RouteController.h
index 38d2d62..656fc21 100644
--- a/server/RouteController.h
+++ b/server/RouteController.h
@@ -17,7 +17,6 @@
 #pragma once
 
 #include "NetdConstants.h"  // IptablesTarget
-#include "Network.h"        // UidRangeMap
 #include "Permission.h"
 
 #include <android-base/thread_annotations.h>
@@ -29,53 +28,6 @@
 
 namespace android::net {
 
-// clang-format off
-const uint32_t RULE_PRIORITY_VPN_OVERRIDE_SYSTEM     = 10000;
-const uint32_t RULE_PRIORITY_VPN_OVERRIDE_OIF        = 11000;
-const uint32_t RULE_PRIORITY_VPN_OUTPUT_TO_LOCAL     = 12000;
-const uint32_t RULE_PRIORITY_SECURE_VPN              = 13000;
-const uint32_t RULE_PRIORITY_PROHIBIT_NON_VPN        = 14000;
-// Rules used when applications explicitly select a network that they have permission to use only
-// because they are in the list of UID ranges for that network.
-//
-// Sockets from these UIDs will not match RULE_PRIORITY_EXPLICIT_NETWORK rules because they will
-// not have the necessary permission bits in the fwmark. We cannot just give any socket on any of
-// these networks the permission bits, because if the UID that created the socket loses access to
-// the network, then the socket must not match any rule that selects that network.
-const uint32_t RULE_PRIORITY_UID_EXPLICIT_NETWORK    = 15000;
-const uint32_t RULE_PRIORITY_EXPLICIT_NETWORK        = 16000;
-const uint32_t RULE_PRIORITY_OUTPUT_INTERFACE        = 17000;
-const uint32_t RULE_PRIORITY_LEGACY_SYSTEM           = 18000;
-const uint32_t RULE_PRIORITY_LEGACY_NETWORK          = 19000;
-const uint32_t RULE_PRIORITY_LOCAL_NETWORK           = 20000;
-const uint32_t RULE_PRIORITY_TETHERING               = 21000;
-// Implicit rules for sockets that connected on a given network because the network was the default
-// network for the UID.
-const uint32_t RULE_PRIORITY_UID_IMPLICIT_NETWORK    = 22000;
-const uint32_t RULE_PRIORITY_IMPLICIT_NETWORK        = 23000;
-const uint32_t RULE_PRIORITY_BYPASSABLE_VPN          = 24000;
-// reserved for RULE_PRIORITY_UID_VPN_FALLTHROUGH    = 25000;
-const uint32_t RULE_PRIORITY_VPN_FALLTHROUGH         = 26000;
-const uint32_t RULE_PRIORITY_UID_DEFAULT_NETWORK     = 27000;
-// Rule used when framework wants to disable default network from specified applications. There will
-// be a small interval the same uid range exists in both UID_DEFAULT_UNREACHABLE and
-// UID_DEFAULT_NETWORK when framework is switching user preferences.
-//
-// framework --> netd
-// step 1: set uid to unreachable network
-// step 2: remove uid from OEM-paid network list
-// or
-// step 1: add uid to OEM-paid network list
-// step 2: remove uid from unreachable network
-//
-// The priority is lower than UID_DEFAULT_NETWORK. Otherwise, the app will be told by
-// ConnectivityService that it has a network in step 1 of the second case. But if it tries to use
-// the network, it will not work. That will potentially cause a user-visible error.
-const uint32_t RULE_PRIORITY_UID_DEFAULT_UNREACHABLE = 28000;
-const uint32_t RULE_PRIORITY_DEFAULT_NETWORK         = 29000;
-const uint32_t RULE_PRIORITY_UNREACHABLE             = 32000;
-// clang-format on
-
 class UidRanges;
 
 class RouteController {
@@ -107,29 +59,25 @@
     [[nodiscard]] static int removeInterfaceFromLocalNetwork(unsigned netId, const char* interface);
 
     [[nodiscard]] static int addInterfaceToPhysicalNetwork(unsigned netId, const char* interface,
-                                                           Permission permission,
-                                                           const UidRangeMap& uidRangeMap);
+                                                           Permission permission);
     [[nodiscard]] static int removeInterfaceFromPhysicalNetwork(unsigned netId,
                                                                 const char* interface,
-                                                                Permission permission,
-                                                                const UidRangeMap& uidRangeMap);
+                                                                Permission permission);
 
     [[nodiscard]] static int addInterfaceToVirtualNetwork(unsigned netId, const char* interface,
-                                                          bool secure,
-                                                          const UidRangeMap& uidRangeMap);
+                                                          bool secure, const UidRanges& uidRanges);
     [[nodiscard]] static int removeInterfaceFromVirtualNetwork(unsigned netId,
                                                                const char* interface, bool secure,
-                                                               const UidRangeMap& uidRangeMap);
+                                                               const UidRanges& uidRanges);
 
     [[nodiscard]] static int modifyPhysicalNetworkPermission(unsigned netId, const char* interface,
                                                              Permission oldPermission,
                                                              Permission newPermission);
 
     [[nodiscard]] static int addUsersToVirtualNetwork(unsigned netId, const char* interface,
-                                                      bool secure, const UidRangeMap& uidRangeMap);
+                                                      bool secure, const UidRanges& uidRanges);
     [[nodiscard]] static int removeUsersFromVirtualNetwork(unsigned netId, const char* interface,
-                                                           bool secure,
-                                                           const UidRangeMap& uidRangeMap);
+                                                           bool secure, const UidRanges& uidRanges);
 
     [[nodiscard]] static int addUsersToRejectNonSecureNetworkRule(const UidRanges& uidRanges);
     [[nodiscard]] static int removeUsersFromRejectNonSecureNetworkRule(const UidRanges& uidRanges);
@@ -160,18 +108,6 @@
                                                              const char* physicalInterface,
                                                              Permission permission);
 
-    [[nodiscard]] static int addUsersToPhysicalNetwork(unsigned netId, const char* interface,
-                                                       const UidRangeMap& uidRangeMap);
-
-    [[nodiscard]] static int removeUsersFromPhysicalNetwork(unsigned netId, const char* interface,
-                                                            const UidRangeMap& uidRangeMap);
-
-    [[nodiscard]] static int addUsersToUnreachableNetwork(unsigned netId,
-                                                          const UidRangeMap& uidRangeMap);
-
-    [[nodiscard]] static int removeUsersFromUnreachableNetwork(unsigned netId,
-                                                               const UidRangeMap& uidRangeMap);
-
     // For testing.
     static int (*iptablesRestoreCommandFunction)(IptablesTarget, const std::string&,
                                                  const std::string&, std::string *);
@@ -189,10 +125,8 @@
             REQUIRES(sInterfaceToTableLock);
     static uint32_t getRouteTableForInterface(const char *interface) EXCLUDES(sInterfaceToTableLock);
     static int modifyDefaultNetwork(uint16_t action, const char* interface, Permission permission);
-    static int modifyPhysicalNetwork(unsigned netId, const char* interface,
-                                     const UidRangeMap& uidRangeMap, Permission permission,
-                                     bool add, bool modifyNonUidBasedRules);
-    static int modifyUnreachableNetwork(unsigned netId, const UidRangeMap& uidRangeMap, bool add);
+    static int modifyPhysicalNetwork(unsigned netId, const char* interface, Permission permission,
+                                     bool add);
     static int modifyRoute(uint16_t action, uint16_t flags, const char* interface,
                            const char* destination, const char* nexthop, TableType tableType,
                            int mtu);
@@ -201,7 +135,7 @@
     static int modifyVpnFallthroughRule(uint16_t action, unsigned vpnNetId,
                                         const char* physicalInterface, Permission permission);
     static int modifyVirtualNetwork(unsigned netId, const char* interface,
-                                    const UidRangeMap& uidRangeMap, bool secure, bool add,
+                                    const UidRanges& uidRanges, bool secure, bool add,
                                     bool modifyNonUidBasedRules);
     static void updateTableNamesFile() EXCLUDES(sInterfaceToTableLock);
 };
diff --git a/server/RouteControllerTest.cpp b/server/RouteControllerTest.cpp
index e85a83c..fed15a3 100644
--- a/server/RouteControllerTest.cpp
+++ b/server/RouteControllerTest.cpp
@@ -45,10 +45,10 @@
     // Expect a rule dump for these two families to contain at least the following priorities.
     for (int family : {AF_INET, AF_INET6 }) {
         std::set<uint32_t> expectedPriorities = {
-                0,
-                RULE_PRIORITY_LEGACY_SYSTEM,
-                RULE_PRIORITY_LEGACY_NETWORK,
-                RULE_PRIORITY_UNREACHABLE,
+            0,
+            15000,  // RULE_PRIORITY_LEGACY_SYSTEM
+            16000,  // RULE_PRIORITY_LEGACY_NETWORK
+            32000,  // RULE_PRIORITY_UNREACHABLE
         };
 
         NetlinkDumpCallback callback = [&expectedPriorities] (const nlmsghdr *nlh) {
diff --git a/server/SockDiag.cpp b/server/SockDiag.cpp
index b3d9150..44bda3b 100644
--- a/server/SockDiag.cpp
+++ b/server/SockDiag.cpp
@@ -31,8 +31,6 @@
 
 #include <cinttypes>
 
-#include <android-base/properties.h>
-#include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <log/log.h>
 #include <netdutils/InternetAddresses.h>
@@ -48,22 +46,12 @@
 
 namespace android {
 
-using android::base::StringPrintf;
 using netdutils::ScopedAddrinfo;
 using netdutils::Stopwatch;
 
 namespace net {
 namespace {
 
-int getAdbPort() {
-    return android::base::GetIntProperty("service.adb.tcp.port", 0);
-}
-
-bool isAdbSocket(const inet_diag_msg *msg, int adbPort) {
-    return adbPort > 0 && msg->id.idiag_sport == htons(adbPort) &&
-        (msg->idiag_uid == AID_ROOT || msg->idiag_uid == AID_SHELL);
-}
-
 int checkError(int fd) {
     struct {
         nlmsghdr h;
@@ -306,7 +294,7 @@
     return ret;
 }
 
-int SockDiag::destroySockets(uint8_t proto, int family, const char* addrstr, int ifindex) {
+int SockDiag::destroySockets(uint8_t proto, int family, const char *addrstr) {
     if (!hasSocks()) {
         return -EBADFD;
     }
@@ -315,33 +303,28 @@
         return ret;
     }
 
-    auto destroyAll = [ifindex](uint8_t, const inet_diag_msg* msg) {
-        return ifindex == 0 || ifindex == (int)msg->id.idiag_if;
-    };
+    auto destroyAll = [] (uint8_t, const inet_diag_msg*) { return true; };
 
     return readDiagMsg(proto, destroyAll);
 }
 
-int SockDiag::destroySockets(const char* addrstr, int ifindex) {
+int SockDiag::destroySockets(const char *addrstr) {
     Stopwatch s;
     mSocketsDestroyed = 0;
 
-    std::string where = addrstr;
-    if (ifindex) where += StringPrintf(" ifindex %d", ifindex);
-
-    if (!strchr(addrstr, ':')) {  // inet_ntop never returns something like ::ffff:192.0.2.1
-        if (int ret = destroySockets(IPPROTO_TCP, AF_INET, addrstr, ifindex)) {
-            ALOGE("Failed to destroy IPv4 sockets on %s: %s", where.c_str(), strerror(-ret));
+    if (!strchr(addrstr, ':')) {
+        if (int ret = destroySockets(IPPROTO_TCP, AF_INET, addrstr)) {
+            ALOGE("Failed to destroy IPv4 sockets on %s: %s", addrstr, strerror(-ret));
             return ret;
         }
     }
-    if (int ret = destroySockets(IPPROTO_TCP, AF_INET6, addrstr, ifindex)) {
-        ALOGE("Failed to destroy IPv6 sockets on %s: %s", where.c_str(), strerror(-ret));
+    if (int ret = destroySockets(IPPROTO_TCP, AF_INET6, addrstr)) {
+        ALOGE("Failed to destroy IPv6 sockets on %s: %s", addrstr, strerror(-ret));
         return ret;
     }
 
     if (mSocketsDestroyed > 0) {
-        ALOGI("Destroyed %d sockets on %s in %" PRId64 "us", mSocketsDestroyed, where.c_str(),
+        ALOGI("Destroyed %d sockets on %s in %" PRId64 "us", mSocketsDestroyed, addrstr,
               s.timeTakenUs());
     }
 
@@ -431,8 +414,7 @@
         return msg != nullptr &&
                uidRanges.hasUid(msg->idiag_uid) &&
                skipUids.find(msg->idiag_uid) == skipUids.end() &&
-               !(excludeLoopback && isLoopbackSocket(msg)) &&
-               !isAdbSocket(msg, getAdbPort());
+               !(excludeLoopback && isLoopbackSocket(msg));
     };
 
     iovec iov[] = {
diff --git a/server/SockDiag.h b/server/SockDiag.h
index 240e4e5..745c09e 100644
--- a/server/SockDiag.h
+++ b/server/SockDiag.h
@@ -70,7 +70,7 @@
 
     int sockDestroy(uint8_t proto, const inet_diag_msg *);
     // Destroys all sockets on the given IPv4 or IPv6 address.
-    int destroySockets(const char* addrstr, int ifindex);
+    int destroySockets(const char *addrstr);
     // Destroys all sockets for the given protocol and UID.
     int destroySockets(uint8_t proto, uid_t uid, bool excludeLoopback);
     // Destroys all "live" (CONNECTED, SYN_SENT, SYN_RECV) TCP sockets for the given UID ranges.
@@ -91,7 +91,7 @@
     int mSocketsDestroyed;
     int sendDumpRequest(uint8_t proto, uint8_t family, uint8_t extensions, uint32_t states,
                         iovec *iov, int iovcnt);
-    int destroySockets(uint8_t proto, int family, const char* addrstr, int ifindex);
+    int destroySockets(uint8_t proto, int family, const char *addrstr);
     int destroyLiveSockets(const DestroyFilter& destroy, const char *what, iovec *iov, int iovcnt);
     bool hasSocks() { return mSock != -1 && mWriteSock != -1; }
     void closeSocks() { close(mSock); close(mWriteSock); mSock = mWriteSock = -1; }
diff --git a/server/SockDiagTest.cpp b/server/SockDiagTest.cpp
index 49601aa..b79471a 100644
--- a/server/SockDiagTest.cpp
+++ b/server/SockDiagTest.cpp
@@ -370,7 +370,7 @@
         int ret;
         switch (mode) {
             case ADDRESS:
-                ret = mSd.destroySockets("::1", 0 /* ifindex */);
+                ret = mSd.destroySockets("::1");
                 EXPECT_LE(0, ret) << ": Failed to destroy sockets on ::1: " << strerror(-ret);
                 break;
             case UID:
diff --git a/server/TetherController.cpp b/server/TetherController.cpp
index 325fc41..2445fb7 100644
--- a/server/TetherController.cpp
+++ b/server/TetherController.cpp
@@ -60,11 +60,14 @@
 namespace android {
 namespace net {
 
+using android::base::Error;
 using android::base::Join;
 using android::base::Pipe;
 using android::base::Result;
+using android::base::StringAppendF;
 using android::base::StringPrintf;
 using android::base::unique_fd;
+using android::net::TetherOffloadRuleParcel;
 using android::netdutils::DumpWriter;
 using android::netdutils::ScopedIndent;
 using android::netdutils::statusFromErrno;
@@ -81,6 +84,10 @@
 // Chosen to match AID_DNS_TETHER, as made "friendly" by fs_config_generator.py.
 constexpr const char kDnsmasqUsername[] = "dns_tether";
 
+// A value used by interface quota indicates there is no limit.
+// Sync from frameworks/base/core/java/android/net/netstats/provider/NetworkStatsProvider.java
+constexpr int64_t QUOTA_UNLIMITED = -1;
+
 bool writeToFile(const char* filename, const char* value) {
     int fd = open(filename, O_WRONLY | O_CLOEXEC);
     if (fd < 0) {
@@ -157,13 +164,12 @@
 }
 
 TetherController::TetherController() {
-    gLog.info("enter TetherController ctor");
     if (inBpToolsMode()) {
         enableForwarding(BP_TOOLS_MODE);
     } else {
         setIpFwdEnabled();
     }
-    gLog.info("leave TetherController ctor");
+    maybeInitMaps();
 }
 
 bool TetherController::setIpFwdEnabled() {
@@ -196,6 +202,27 @@
     return setIpFwdEnabled();
 }
 
+void TetherController::maybeInitMaps() {
+    if (!bpf::isBpfSupported()) return;
+
+    // Open BPF maps, ignoring errors because the device might not support BPF offload.
+    int fd = getTetherIngressMapFd();
+    if (fd >= 0) {
+        mBpfIngressMap.reset(fd);
+        mBpfIngressMap.clear();
+    }
+    fd = getTetherStatsMapFd();
+    if (fd >= 0) {
+        mBpfStatsMap.reset(fd);
+        mBpfStatsMap.clear();
+    }
+    fd = getTetherLimitMapFd();
+    if (fd >= 0) {
+        mBpfLimitMap.reset(fd);
+        mBpfLimitMap.clear();
+    }
+}
+
 const std::set<std::string>& TetherController::getIpfwdRequesterList() const {
     return mForwardingRequests;
 }
@@ -579,7 +606,8 @@
     }
 
     // add this if we are the first enabled nat for this upstream
-    if (!isAnyForwardingEnabledOnUpstream(extIface)) {
+    bool firstDownstreamForThisUpstream = !isAnyForwardingEnabledOnUpstream(extIface);
+    if (firstDownstreamForThisUpstream) {
         std::vector<std::string> v4Cmds = {
             "*nat",
             StringPrintf("-A %s -o %s -j MASQUERADE", LOCAL_NAT_POSTROUTING, extIface),
@@ -605,6 +633,7 @@
         return -ENODEV;
     }
 
+    if (firstDownstreamForThisUpstream) maybeStartBpf(extIface);
     return 0;
 }
 
@@ -788,10 +817,82 @@
     }
 
     setForwardRules(false, intIface, extIface);
+    if (!isAnyForwardingEnabledOnUpstream(extIface)) maybeStopBpf(extIface);
     if (!isAnyForwardingPairEnabled()) setDefaults();
     return 0;
 }
 
+namespace {
+Result<void> validateOffloadRule(const TetherOffloadRuleParcel& rule) {
+    struct ethhdr hdr;
+
+    if (rule.inputInterfaceIndex <= 0) {
+        return Error(ENODEV) << "Invalid input interface " << rule.inputInterfaceIndex;
+    }
+    if (rule.outputInterfaceIndex <= 0) {
+        return Error(ENODEV) << "Invalid output interface " << rule.inputInterfaceIndex;
+    }
+    if (rule.prefixLength != 128) {
+        return Error(EINVAL) << "Prefix length must be 128, not " << rule.prefixLength;
+    }
+    if (rule.destination.size() != sizeof(in6_addr)) {
+        return Error(EAFNOSUPPORT) << "Invalid IP address length " << rule.destination.size();
+    }
+    if (rule.srcL2Address.size() != sizeof(hdr.h_source)) {
+        return Error(ENXIO) << "Invalid L2 src address length " << rule.srcL2Address.size();
+    }
+    if (rule.dstL2Address.size() != sizeof(hdr.h_dest)) {
+        return Error(ENXIO) << "Invalid L2 dst address length " << rule.dstL2Address.size();
+    }
+    if (rule.pmtu < IPV6_MIN_MTU || rule.pmtu > 0xFFFF) {
+        return Error(EINVAL) << "Invalid IPv6 path mtu " << rule.pmtu;
+    }
+    return Result<void>();
+}
+}  // namespace
+
+Result<void> TetherController::addOffloadRule(const TetherOffloadRuleParcel& rule) {
+    Result<void> res = validateOffloadRule(rule);
+    if (!res.ok()) return res;
+
+    ethhdr hdr = {
+            .h_proto = htons(ETH_P_IPV6),
+    };
+    memcpy(&hdr.h_dest, rule.dstL2Address.data(), sizeof(hdr.h_dest));
+    memcpy(&hdr.h_source, rule.srcL2Address.data(), sizeof(hdr.h_source));
+
+    // Only downstream supported for now.
+    TetherIngressKey key = {
+            .iif = static_cast<uint32_t>(rule.inputInterfaceIndex),
+            .neigh6 = *(const in6_addr*)rule.destination.data(),
+    };
+
+    TetherIngressValue value = {
+            .oif = static_cast<uint32_t>(rule.outputInterfaceIndex),
+            .macHeader = hdr,
+            .pmtu = static_cast<uint16_t>(rule.pmtu),
+    };
+
+    return mBpfIngressMap.writeValue(key, value, BPF_ANY);
+}
+
+Result<void> TetherController::removeOffloadRule(const TetherOffloadRuleParcel& rule) {
+    Result<void> res = validateOffloadRule(rule);
+    if (!res.ok()) return res;
+
+    TetherIngressKey key = {
+            .iif = static_cast<uint32_t>(rule.inputInterfaceIndex),
+            .neigh6 = *(const in6_addr*)rule.destination.data(),
+    };
+
+    Result<void> ret = mBpfIngressMap.deleteValue(key);
+
+    // Silently return success if the rule did not exist.
+    if (!ret.ok() && ret.error().code() == ENOENT) return {};
+
+    return ret;
+}
+
 void TetherController::addStats(TetherStatsList& statsList, const TetherStats& stats) {
     for (TetherStats& existing : statsList) {
         if (existing.addStatsIfMatch(stats)) {
@@ -927,6 +1028,180 @@
     return statsList;
 }
 
+StatusOr<TetherController::TetherOffloadStatsList> TetherController::getTetherOffloadStats() {
+    TetherOffloadStatsList statsList;
+
+    const auto processTetherStats = [&statsList](const uint32_t& key, const TetherStatsValue& value,
+                                                 const BpfMap<uint32_t, TetherStatsValue>&) {
+        statsList.push_back({.ifIndex = static_cast<int>(key),
+                             .rxBytes = static_cast<int64_t>(value.rxBytes),
+                             .rxPackets = static_cast<int64_t>(value.rxPackets),
+                             .txBytes = static_cast<int64_t>(value.txBytes),
+                             .txPackets = static_cast<int64_t>(value.txPackets)});
+        return Result<void>();
+    };
+
+    auto ret = mBpfStatsMap.iterateWithValue(processTetherStats);
+    if (!ret.ok()) {
+        // Ignore error to return the remaining tether stats result.
+        ALOGE("Error processing tether stats from BPF maps: %s", ret.error().message().c_str());
+    }
+
+    return statsList;
+}
+
+// Use UINT64_MAX (~0uLL) for unlimited.
+Result<void> TetherController::setBpfLimit(uint32_t ifIndex, uint64_t limit) {
+    // The common case is an update, where the stats already exist,
+    // hence we read first, even though writing with BPF_NOEXIST
+    // first would make the code simpler.
+    uint64_t rxBytes, txBytes;
+    auto statsEntry = mBpfStatsMap.readValue(ifIndex);
+
+    if (statsEntry.ok()) {
+        // Ok, there was a stats entry.
+        rxBytes = statsEntry.value().rxBytes;
+        txBytes = statsEntry.value().txBytes;
+    } else if (statsEntry.error().code() == ENOENT) {
+        // No stats entry - create one with zeroes.
+        TetherStatsValue stats = {};
+        // This function is the *only* thing that can create entries.
+        auto ret = mBpfStatsMap.writeValue(ifIndex, stats, BPF_NOEXIST);
+        if (!ret.ok()) {
+            ALOGE("mBpfStatsMap.writeValue failure: %s", strerror(ret.error().code()));
+            return ret;
+        }
+        rxBytes = 0;
+        txBytes = 0;
+    } else {
+        // Other error while trying to get stats entry.
+        return statsEntry.error();
+    }
+
+    // rxBytes + txBytes won't overflow even at 5gbps for ~936 years.
+    uint64_t newLimit = rxBytes + txBytes + limit;
+
+    // if adding limit (e.g., if limit is UINT64_MAX) caused overflow: clamp to 'infinity'
+    if (newLimit < rxBytes + txBytes) newLimit = ~0uLL;
+
+    auto ret = mBpfLimitMap.writeValue(ifIndex, newLimit, BPF_ANY);
+    if (!ret.ok()) {
+        ALOGE("mBpfLimitMap.writeValue failure: %s", strerror(ret.error().code()));
+        return ret;
+    }
+
+    return {};
+}
+
+void TetherController::maybeStartBpf(const char* extIface) {
+    if (!bpf::isBpfSupported()) return;
+
+    // TODO: perhaps ignore IPv4-only interface because IPv4 traffic downstream is not supported.
+    int ifIndex = if_nametoindex(extIface);
+    if (!ifIndex) {
+        ALOGE("Fail to get index for interface %s", extIface);
+        return;
+    }
+
+    auto isEthernet = android::net::isEthernet(extIface);
+    if (!isEthernet.ok()) {
+        ALOGE("isEthernet(%s[%d]) failure: %s", extIface, ifIndex,
+              isEthernet.error().message().c_str());
+        return;
+    }
+
+    int rv = getTetherIngressProgFd(isEthernet.value());
+    if (rv < 0) {
+        ALOGE("getTetherIngressProgFd(%d) failure: %s", isEthernet.value(), strerror(-rv));
+        return;
+    }
+    unique_fd tetherProgFd(rv);
+
+    rv = tcFilterAddDevIngressTether(ifIndex, tetherProgFd, isEthernet.value());
+    if (rv) {
+        ALOGE("tcFilterAddDevIngressTether(%d[%s], %d) failure: %s", ifIndex, extIface,
+              isEthernet.value(), strerror(-rv));
+        return;
+    }
+}
+
+void TetherController::maybeStopBpf(const char* extIface) {
+    if (!bpf::isBpfSupported()) return;
+
+    // TODO: perhaps ignore IPv4-only interface because IPv4 traffic downstream is not supported.
+    int ifIndex = if_nametoindex(extIface);
+    if (!ifIndex) {
+        ALOGE("Fail to get index for interface %s", extIface);
+        return;
+    }
+
+    int rv = tcFilterDelDevIngressTether(ifIndex);
+    if (rv < 0) {
+        ALOGE("tcFilterDelDevIngressTether(%d[%s]) failure: %s", ifIndex, extIface, strerror(-rv));
+    }
+}
+
+int TetherController::setTetherOffloadInterfaceQuota(int ifIndex, int64_t maxBytes) {
+    if (!mBpfStatsMap.isValid() || !mBpfLimitMap.isValid()) return -ENOTSUP;
+
+    if (ifIndex <= 0) return -ENODEV;
+
+    if (maxBytes < QUOTA_UNLIMITED) {
+        ALOGE("Invalid bytes value. Must be -1 (unlimited) or 0..max_int64.");
+        return -ERANGE;
+    }
+
+    // Note that a value of unlimited quota (-1) indicates simply max_uint64.
+    const auto res = setBpfLimit(static_cast<uint32_t>(ifIndex), static_cast<uint64_t>(maxBytes));
+    if (!res.ok()) {
+        ALOGE("Fail to set quota %" PRId64 " for interface index %d: %s", maxBytes, ifIndex,
+              strerror(res.error().code()));
+        return -res.error().code();
+    }
+
+    return 0;
+}
+
+Result<TetherController::TetherOffloadStats> TetherController::getAndClearTetherOffloadStats(
+        int ifIndex) {
+    if (!mBpfStatsMap.isValid() || !mBpfLimitMap.isValid()) return Error(ENOTSUP);
+
+    if (ifIndex <= 0) {
+        return Error(ENODEV) << "Invalid interface " << ifIndex;
+    }
+
+    // getAndClearTetherOffloadStats is called after all offload rules have already been deleted
+    // for the given upstream interface. Before starting to do cleanup stuff in this function, use
+    // synchronizeKernelRCU to make sure that all the current running eBPF programs are finished
+    // on all CPUs, especially the unfinished packet processing. After synchronizeKernelRCU
+    // returned, we can safely read or delete on the stats map or the limit map.
+    if (int res = bpf::synchronizeKernelRCU()) {
+        // Error log but don't return error. Do as much cleanup as possible.
+        ALOGE("synchronize_rcu() failed: %s", strerror(-res));
+    }
+
+    const auto stats = mBpfStatsMap.readValue(ifIndex);
+    if (!stats.ok()) {
+        return Error(stats.error().code()) << "Fail to get stats for interface index " << ifIndex;
+    }
+
+    auto res = mBpfStatsMap.deleteValue(ifIndex);
+    if (!res.ok()) {
+        return Error(res.error().code()) << "Fail to delete stats for interface index " << ifIndex;
+    }
+
+    res = mBpfLimitMap.deleteValue(ifIndex);
+    if (!res.ok()) {
+        return Error(res.error().code()) << "Fail to delete limit for interface index " << ifIndex;
+    }
+
+    return TetherOffloadStats{.ifIndex = static_cast<int>(ifIndex),
+                              .rxBytes = static_cast<int64_t>(stats.value().rxBytes),
+                              .rxPackets = static_cast<int64_t>(stats.value().rxPackets),
+                              .txBytes = static_cast<int64_t>(stats.value().txBytes),
+                              .txPackets = static_cast<int64_t>(stats.value().txPackets)};
+}
+
 void TetherController::dumpIfaces(DumpWriter& dw) {
     dw.println("Interface pairs:");
 
@@ -937,6 +1212,90 @@
     }
 }
 
+namespace {
+
+std::string l2ToString(const uint8_t* addr, size_t len) {
+    std::string str;
+
+    if (len == 0) return str;
+
+    StringAppendF(&str, "%02x", addr[0]);
+    for (size_t i = 1; i < len; i++) {
+        StringAppendF(&str, ":%02x", addr[i]);
+    }
+
+    return str;
+}
+
+}  // namespace
+
+void TetherController::dumpBpf(DumpWriter& dw) {
+    if (!mBpfIngressMap.isValid() || !mBpfStatsMap.isValid() || !mBpfLimitMap.isValid()) {
+        dw.println("BPF not supported");
+        return;
+    }
+
+    dw.println("BPF ingress map: iif(iface) v6addr -> oif(iface) srcmac dstmac ethertype [pmtu]");
+    const auto printIngressMap = [&dw](const TetherIngressKey& key, const TetherIngressValue& value,
+                                       const BpfMap<TetherIngressKey, TetherIngressValue>&) {
+        char addr[INET6_ADDRSTRLEN];
+        std::string src = l2ToString(value.macHeader.h_source, sizeof(value.macHeader.h_source));
+        std::string dst = l2ToString(value.macHeader.h_dest, sizeof(value.macHeader.h_dest));
+        inet_ntop(AF_INET6, &key.neigh6, addr, sizeof(addr));
+
+        char iifStr[IFNAMSIZ] = "?";
+        char oifStr[IFNAMSIZ] = "?";
+        if_indextoname(key.iif, iifStr);
+        if_indextoname(value.oif, oifStr);
+        dw.println("%u(%s) %s -> %u(%s) %s %s %04x [%u]", key.iif, iifStr, addr, value.oif, oifStr,
+                   src.c_str(), dst.c_str(), ntohs(value.macHeader.h_proto), value.pmtu);
+
+        return Result<void>();
+    };
+
+    dw.incIndent();
+    auto ret = mBpfIngressMap.iterateWithValue(printIngressMap);
+    if (!ret.ok()) {
+        dw.println("Error printing BPF ingress map: %s", ret.error().message().c_str());
+    }
+    dw.decIndent();
+
+    dw.println("BPF stats (downlink): iif(iface) -> packets bytes errors");
+    const auto printStatsMap = [&dw](const uint32_t& key, const TetherStatsValue& value,
+                                     const BpfMap<uint32_t, TetherStatsValue>&) {
+        char iifStr[IFNAMSIZ] = "?";
+        if_indextoname(key, iifStr);
+        dw.println("%u(%s) -> %" PRIu64 " %" PRIu64 " %" PRIu64, key, iifStr, value.rxPackets,
+                   value.rxBytes, value.rxErrors);
+
+        return Result<void>();
+    };
+
+    dw.incIndent();
+    ret = mBpfStatsMap.iterateWithValue(printStatsMap);
+    if (!ret.ok()) {
+        dw.println("Error printing BPF stats map: %s", ret.error().message().c_str());
+    }
+    dw.decIndent();
+
+    dw.println("BPF limit: iif(iface) -> bytes");
+    const auto printLimitMap = [&dw](const uint32_t& key, const uint64_t& value,
+                                     const BpfMap<uint32_t, uint64_t>&) {
+        char iifStr[IFNAMSIZ] = "?";
+        if_indextoname(key, iifStr);
+        dw.println("%u(%s) -> %" PRIu64, key, iifStr, value);
+
+        return Result<void>();
+    };
+
+    dw.incIndent();
+    ret = mBpfLimitMap.iterateWithValue(printLimitMap);
+    if (!ret.ok()) {
+        dw.println("Error printing BPF limit map: %s", ret.error().message().c_str());
+    }
+    dw.decIndent();
+}
+
 void TetherController::dump(DumpWriter& dw) {
     std::lock_guard guard(lock);
 
@@ -954,6 +1313,8 @@
     }
 
     dumpIfaces(dw);
+    dw.println("");
+    dumpBpf(dw);
 }
 
 }  // namespace net
diff --git a/server/TetherController.h b/server/TetherController.h
index 585686a..bcd2ee6 100644
--- a/server/TetherController.h
+++ b/server/TetherController.h
@@ -72,6 +72,11 @@
         int sendAllState(int daemonFd) const;
     } mDnsmasqState{};
 
+    // BPF maps, initialized by maybeInitMaps.
+    bpf::BpfMap<TetherIngressKey, TetherIngressValue> mBpfIngressMap;
+    bpf::BpfMap<uint32_t, TetherStatsValue> mBpfStatsMap;
+    bpf::BpfMap<uint32_t, uint64_t> mBpfLimitMap;
+
   public:
     TetherController();
     ~TetherController() = default;
@@ -100,6 +105,11 @@
     int disableNat(const char* intIface, const char* extIface);
     int setupIptablesHooks();
 
+    base::Result<void> addOffloadRule(const TetherOffloadRuleParcel& rule);
+    base::Result<void> removeOffloadRule(const TetherOffloadRuleParcel& rule);
+
+    int setTetherOffloadInterfaceQuota(int ifIndex, int64_t maxBytes);
+
     class TetherStats {
       public:
         TetherStats() = default;
@@ -128,9 +138,20 @@
         }
     };
 
+    struct TetherOffloadStats {
+        int ifIndex;
+        int64_t rxBytes;
+        int64_t rxPackets;
+        int64_t txBytes;
+        int64_t txPackets;
+    };
+
     typedef std::vector<TetherStats> TetherStatsList;
+    typedef std::vector<TetherOffloadStats> TetherOffloadStatsList;
 
     netdutils::StatusOr<TetherStatsList> getTetherStats();
+    netdutils::StatusOr<TetherOffloadStatsList> getTetherOffloadStats();
+    base::Result<TetherOffloadStats> getAndClearTetherOffloadStats(int ifIndex);
 
     /*
      * extraProcessingInfo: contains raw parsed data, and error info.
@@ -152,6 +173,7 @@
 
     void dump(netdutils::DumpWriter& dw);
     void dumpIfaces(netdutils::DumpWriter& dw);
+    void dumpBpf(netdutils::DumpWriter& dw);
 
   private:
     bool setIpFwdEnabled();
@@ -173,6 +195,11 @@
     int setForwardRules(bool set, const char *intIface, const char *extIface);
     int setTetherCountingRules(bool add, const char *intIface, const char *extIface);
 
+    base::Result<void> setBpfLimit(uint32_t ifIndex, uint64_t limit);
+    void maybeInitMaps();
+    void maybeStartBpf(const char* extIface);
+    void maybeStopBpf(const char* extIface);
+
     static void addStats(TetherStatsList& statsList, const TetherStats& stats);
 
     // For testing.
diff --git a/server/TetherControllerTest.cpp b/server/TetherControllerTest.cpp
index e700f60..db9892f 100644
--- a/server/TetherControllerTest.cpp
+++ b/server/TetherControllerTest.cpp
@@ -38,13 +38,28 @@
 
 using android::base::Join;
 using android::base::StringPrintf;
+using android::bpf::BpfMap;
 using android::netdutils::StatusOr;
+using ::testing::Contains;
 using TetherStats = android::net::TetherController::TetherStats;
 using TetherStatsList = android::net::TetherController::TetherStatsList;
+using TetherOffloadStats = android::net::TetherController::TetherOffloadStats;
+using TetherOffloadStatsList = android::net::TetherController::TetherOffloadStatsList;
 
 namespace android {
 namespace net {
 
+constexpr int TEST_MAP_SIZE = 10;
+
+// Comparison for TetherOffloadStats. Need to override operator== because class TetherOffloadStats
+// doesn't have one.
+// TODO: once C++20 is used, use default operator== in TetherOffloadStats and remove the overriding
+// here.
+bool operator==(const TetherOffloadStats& lhs, const TetherOffloadStats& rhs) {
+    return lhs.ifIndex == rhs.ifIndex && lhs.rxBytes == rhs.rxBytes && lhs.txBytes == rhs.txBytes &&
+           lhs.rxPackets == rhs.rxPackets && lhs.txPackets == rhs.txPackets;
+}
+
 class TetherControllerTest : public IptablesBaseTest {
 public:
     TetherControllerTest() {
@@ -53,6 +68,38 @@
 
 protected:
     TetherController mTetherCtrl;
+    BpfMap<uint32_t, TetherStatsValue> mFakeTetherStatsMap{BPF_MAP_TYPE_HASH, TEST_MAP_SIZE};
+    BpfMap<uint32_t, uint64_t> mFakeTetherLimitMap{BPF_MAP_TYPE_HASH, TEST_MAP_SIZE};
+
+    void SetUp() {
+        SKIP_IF_BPF_NOT_SUPPORTED;
+
+        ASSERT_TRUE(mFakeTetherStatsMap.isValid());
+        ASSERT_TRUE(mFakeTetherLimitMap.isValid());
+
+        mTetherCtrl.mBpfStatsMap = mFakeTetherStatsMap;
+        ASSERT_TRUE(mTetherCtrl.mBpfStatsMap.isValid());
+        mTetherCtrl.mBpfLimitMap = mFakeTetherLimitMap;
+        ASSERT_TRUE(mTetherCtrl.mBpfLimitMap.isValid());
+    }
+
+    std::string toString(const TetherOffloadStatsList& statsList) {
+        std::string result;
+        for (const auto& stats : statsList) {
+            result += StringPrintf("%d, %" PRId64 ", %" PRId64 ", %" PRId64 ", %" PRId64 "\n",
+                                   stats.ifIndex, stats.rxBytes, stats.rxPackets, stats.txBytes,
+                                   stats.txPackets);
+        }
+        return result;
+    }
+
+    void updateMaps(uint32_t ifaceIndex, uint64_t rxBytes, uint64_t rxPackets, uint64_t txBytes,
+                    uint64_t txPackets) {
+        // {rx, tx}Errors in |tetherStats| are set zero because getTetherStats doesn't use them.
+        const TetherStatsValue tetherStats = {rxPackets, rxBytes, 0 /*unused*/,
+                                              txPackets, txBytes, 0 /*unused*/};
+        ASSERT_RESULT_OK(mFakeTetherStatsMap.writeValue(ifaceIndex, tetherStats, BPF_ANY));
+    };
 
     int setDefaults() {
         return mTetherCtrl.setDefaults();
@@ -438,5 +485,64 @@
     EXPECT_TRUE(std::equal(expectedError.rbegin(), expectedError.rend(), err.rbegin()));
 }
 
+TEST_F(TetherControllerTest, TestTetherOffloadGetStats) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    updateMaps(101, 100, 10, 200, 20);
+    updateMaps(102, 300, 30, 400, 40);
+    const TetherOffloadStats expected0{101, 100, 10, 200, 20};
+    const TetherOffloadStats expected1{102, 300, 30, 400, 40};
+
+    const StatusOr<TetherOffloadStatsList> result = mTetherCtrl.getTetherOffloadStats();
+    ASSERT_OK(result);
+    const TetherOffloadStatsList& actual = result.value();
+    ASSERT_EQ(2U, actual.size());
+    EXPECT_THAT(actual, Contains(expected0)) << toString(actual);
+    EXPECT_THAT(actual, Contains(expected1)) << toString(actual);
+    clearIptablesRestoreOutput();
+}
+
+TEST_F(TetherControllerTest, TestTetherOffloadSetQuota) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    const uint32_t ifindex = 100;
+    const uint64_t minQuota = 0;
+    const uint64_t maxQuota = std::numeric_limits<int64_t>::max();
+    const uint64_t infinityQuota = std::numeric_limits<uint64_t>::max();
+
+    // Create a stats entry with zeroes in the first time set limit.
+    ASSERT_EQ(0, mTetherCtrl.setTetherOffloadInterfaceQuota(ifindex, minQuota));
+    const StatusOr<TetherOffloadStatsList> result = mTetherCtrl.getTetherOffloadStats();
+    ASSERT_OK(result);
+    const TetherOffloadStatsList& actual = result.value();
+    ASSERT_EQ(1U, actual.size());
+    EXPECT_THAT(actual, Contains(TetherOffloadStats{ifindex, 0, 0, 0, 0})) << toString(actual);
+
+    // Verify the quota with the boundary {min, max, infinity}.
+    const uint64_t rxBytes = 1000;
+    const uint64_t txBytes = 2000;
+    updateMaps(ifindex, rxBytes, 0 /*unused*/, txBytes, 0 /*unused*/);
+
+    for (const uint64_t quota : {minQuota, maxQuota, infinityQuota}) {
+        ASSERT_EQ(0, mTetherCtrl.setTetherOffloadInterfaceQuota(ifindex, quota));
+        base::Result<uint64_t> result = mFakeTetherLimitMap.readValue(ifindex);
+        ASSERT_RESULT_OK(result);
+
+        const uint64_t expectedQuota =
+                (quota == infinityQuota) ? infinityQuota : quota + rxBytes + txBytes;
+        EXPECT_EQ(expectedQuota, result.value());
+    }
+
+    // The valid range of interface index is 1..max_int64.
+    const uint32_t invalidIfindex = 0;
+    int ret = mTetherCtrl.setTetherOffloadInterfaceQuota(invalidIfindex /*bad*/, infinityQuota);
+    ASSERT_EQ(-ENODEV, ret);
+
+    // The valid range of quota is 0..max_int64 or -1 (unlimited).
+    const uint64_t invalidQuota = std::numeric_limits<int64_t>::min();
+    ret = mTetherCtrl.setTetherOffloadInterfaceQuota(ifindex, invalidQuota /*bad*/);
+    ASSERT_EQ(-ERANGE, ret);
+}
+
 }  // namespace net
 }  // namespace android
diff --git a/server/TrafficController.cpp b/server/TrafficController.cpp
index 1f678cb..3839962 100644
--- a/server/TrafficController.cpp
+++ b/server/TrafficController.cpp
@@ -52,16 +52,13 @@
 #include "netdutils/DumpWriter.h"
 #include "qtaguid/qtaguid.h"
 
+using namespace android::bpf;  // NOLINT(google-build-using-namespace): grandfathered
+
 namespace android {
 namespace net {
 
 using base::StringPrintf;
 using base::unique_fd;
-using bpf::getSocketCookie;
-using bpf::NONEXISTENT_COOKIE;
-using bpf::OVERFLOW_COUNTERSET;
-using bpf::retrieveProgram;
-using bpf::synchronizeKernelRCU;
 using netdutils::DumpWriter;
 using netdutils::extract;
 using netdutils::ScopedIndent;
@@ -103,7 +100,6 @@
     FLAG_MSG_TRANS(matchType, DOZABLE_MATCH, match);
     FLAG_MSG_TRANS(matchType, STANDBY_MATCH, match);
     FLAG_MSG_TRANS(matchType, POWERSAVE_MATCH, match);
-    FLAG_MSG_TRANS(matchType, RESTRICTED_MATCH, match);
     FLAG_MSG_TRANS(matchType, IIF_MATCH, match);
     if (match) {
         return StringPrintf("Unknown match: %u", match);
@@ -171,11 +167,14 @@
 }
 
 TrafficController::TrafficController()
-    : mPerUidStatsEntriesLimit(PER_UID_STATS_ENTRIES_LIMIT),
+    : mBpfEnabled(isBpfSupported()),
+      mPerUidStatsEntriesLimit(PER_UID_STATS_ENTRIES_LIMIT),
       mTotalUidStatsEntriesLimit(TOTAL_UID_STATS_ENTRIES_LIMIT) {}
 
 TrafficController::TrafficController(uint32_t perUidLimit, uint32_t totalLimit)
-    : mPerUidStatsEntriesLimit(perUidLimit), mTotalUidStatsEntriesLimit(totalLimit) {}
+    : mBpfEnabled(isBpfSupported()),
+      mPerUidStatsEntriesLimit(perUidLimit),
+      mTotalUidStatsEntriesLimit(totalLimit) {}
 
 Status TrafficController::initMaps() {
     std::lock_guard guard(mMutex);
@@ -248,6 +247,10 @@
 }
 
 Status TrafficController::start() {
+    if (!mBpfEnabled) {
+        return netdutils::status::ok;
+    }
+
     /* When netd restarts from a crash without total system reboot, the program
      * is still attached to the cgroup, detach it so the program can be freed
      * and we can load and attach new program into the target cgroup.
@@ -311,6 +314,11 @@
         return -EPERM;
     }
 
+    if (!mBpfEnabled) {
+        if (legacy_tagSocket(sockFd, tag, uid)) return -errno;
+        return 0;
+    }
+
     uint64_t sock_cookie = getSocketCookie(sockFd);
     if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
     UidTagValue newKey = {.uid = (uint32_t)uid, .tag = tag};
@@ -374,6 +382,10 @@
 
 int TrafficController::untagSocket(int sockFd) {
     std::lock_guard guard(mMutex);
+    if (!mBpfEnabled) {
+        if (legacy_untagSocket(sockFd)) return -errno;
+        return 0;
+    }
     uint64_t sock_cookie = getSocketCookie(sockFd);
 
     if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
@@ -391,6 +403,11 @@
     std::lock_guard guard(mMutex);
     if (!hasUpdateDeviceStatsPermission(callingUid)) return -EPERM;
 
+    if (!mBpfEnabled) {
+        if (legacy_setCounterSet(counterSetNum, uid)) return -errno;
+        return 0;
+    }
+
     // The default counter set for all uid is 0, so deleting the current counterset for that uid
     // will automatically set it to 0.
     if (counterSetNum == 0) {
@@ -419,6 +436,11 @@
     std::lock_guard guard(mMutex);
     if (!hasUpdateDeviceStatsPermission(callingUid)) return -EPERM;
 
+    if (!mBpfEnabled) {
+        if (legacy_deleteTagData(tag, uid)) return -errno;
+        return 0;
+    }
+
     // First we go through the cookieTagMap to delete the target uid tag combination. Or delete all
     // the tags related to the uid if the tag is 0.
     const auto deleteMatchedCookieEntries = [uid, tag](const uint64_t& key,
@@ -479,6 +501,8 @@
 }
 
 int TrafficController::addInterface(const char* name, uint32_t ifaceIndex) {
+    if (!mBpfEnabled) return 0;
+
     IfaceValue iface;
     if (ifaceIndex == 0) {
         ALOGE("Unknown interface %s(%d)", name, ifaceIndex);
@@ -497,10 +521,10 @@
 Status TrafficController::updateOwnerMapEntry(UidOwnerMatchType match, uid_t uid, FirewallRule rule,
                                               FirewallType type) {
     std::lock_guard guard(mMutex);
-    if ((rule == ALLOW && type == ALLOWLIST) || (rule == DENY && type == DENYLIST)) {
-        RETURN_IF_NOT_OK(addRule(uid, match));
-    } else if ((rule == ALLOW && type == DENYLIST) || (rule == DENY && type == ALLOWLIST)) {
-        RETURN_IF_NOT_OK(removeRule(uid, match));
+    if ((rule == ALLOW && type == WHITELIST) || (rule == DENY && type == BLACKLIST)) {
+        RETURN_IF_NOT_OK(addRule(mUidOwnerMap, uid, match));
+    } else if ((rule == ALLOW && type == BLACKLIST) || (rule == DENY && type == WHITELIST)) {
+        RETURN_IF_NOT_OK(removeRule(mUidOwnerMap, uid, match));
     } else {
         //Cannot happen.
         return statusFromErrno(EINVAL, "");
@@ -508,17 +532,29 @@
     return netdutils::status::ok;
 }
 
-Status TrafficController::removeRule(uint32_t uid, UidOwnerMatchType match) {
-    auto oldMatch = mUidOwnerMap.readValue(uid);
+UidOwnerMatchType TrafficController::jumpOpToMatch(BandwidthController::IptJumpOp jumpHandling) {
+    switch (jumpHandling) {
+        case BandwidthController::IptJumpReject:
+            return PENALTY_BOX_MATCH;
+        case BandwidthController::IptJumpReturn:
+            return HAPPY_BOX_MATCH;
+        case BandwidthController::IptJumpNoAdd:
+            return NO_MATCH;
+    }
+}
+
+Status TrafficController::removeRule(BpfMap<uint32_t, UidOwnerValue>& map, uint32_t uid,
+                                     UidOwnerMatchType match) {
+    auto oldMatch = map.readValue(uid);
     if (oldMatch.ok()) {
         UidOwnerValue newMatch = {
                 .iif = (match == IIF_MATCH) ? 0 : oldMatch.value().iif,
                 .rule = static_cast<uint8_t>(oldMatch.value().rule & ~match),
         };
         if (newMatch.rule == 0) {
-            RETURN_IF_NOT_OK(mUidOwnerMap.deleteValue(uid));
+            RETURN_IF_NOT_OK(map.deleteValue(uid));
         } else {
-            RETURN_IF_NOT_OK(mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY));
+            RETURN_IF_NOT_OK(map.writeValue(uid, newMatch, BPF_ANY));
         }
     } else {
         return statusFromErrno(ENOENT, StringPrintf("uid: %u does not exist in map", uid));
@@ -526,42 +562,55 @@
     return netdutils::status::ok;
 }
 
-Status TrafficController::addRule(uint32_t uid, UidOwnerMatchType match, uint32_t iif) {
+Status TrafficController::addRule(BpfMap<uint32_t, UidOwnerValue>& map, uint32_t uid,
+                                  UidOwnerMatchType match, uint32_t iif) {
     // iif should be non-zero if and only if match == MATCH_IIF
     if (match == IIF_MATCH && iif == 0) {
         return statusFromErrno(EINVAL, "Interface match must have nonzero interface index");
     } else if (match != IIF_MATCH && iif != 0) {
         return statusFromErrno(EINVAL, "Non-interface match must have zero interface index");
     }
-    auto oldMatch = mUidOwnerMap.readValue(uid);
+    auto oldMatch = map.readValue(uid);
     if (oldMatch.ok()) {
         UidOwnerValue newMatch = {
                 .iif = iif ? iif : oldMatch.value().iif,
                 .rule = static_cast<uint8_t>(oldMatch.value().rule | match),
         };
-        RETURN_IF_NOT_OK(mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY));
+        RETURN_IF_NOT_OK(map.writeValue(uid, newMatch, BPF_ANY));
     } else {
         UidOwnerValue newMatch = {
                 .iif = iif,
                 .rule = static_cast<uint8_t>(match),
         };
-        RETURN_IF_NOT_OK(mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY));
+        RETURN_IF_NOT_OK(map.writeValue(uid, newMatch, BPF_ANY));
     }
     return netdutils::status::ok;
 }
 
-Status TrafficController::updateUidOwnerMap(const std::vector<uint32_t>& appUids,
-                                            UidOwnerMatchType matchType,
+Status TrafficController::updateUidOwnerMap(const std::vector<std::string>& appStrUids,
+                                            BandwidthController::IptJumpOp jumpHandling,
                                             BandwidthController::IptOp op) {
     std::lock_guard guard(mMutex);
-    for (uint32_t uid : appUids) {
+    UidOwnerMatchType match = jumpOpToMatch(jumpHandling);
+    if (match == NO_MATCH) {
+        return statusFromErrno(
+                EINVAL, StringPrintf("invalid IptJumpOp: %d, command: %d", jumpHandling, match));
+    }
+    for (const auto& appStrUid : appStrUids) {
+        char* endPtr;
+        long uid = strtol(appStrUid.c_str(), &endPtr, 10);
+        if ((errno == ERANGE && (uid == LONG_MAX || uid == LONG_MIN)) ||
+            (endPtr == appStrUid.c_str()) || (*endPtr != '\0')) {
+               return statusFromErrno(errno, "invalid uid string:" + appStrUid);
+        }
+
         if (op == BandwidthController::IptOpDelete) {
-            RETURN_IF_NOT_OK(removeRule(uid, matchType));
+            RETURN_IF_NOT_OK(removeRule(mUidOwnerMap, uid, match));
         } else if (op == BandwidthController::IptOpInsert) {
-            RETURN_IF_NOT_OK(addRule(uid, matchType));
+            RETURN_IF_NOT_OK(addRule(mUidOwnerMap, uid, match));
         } else {
             // Cannot happen.
-            return statusFromErrno(EINVAL, StringPrintf("invalid IptOp: %d, %d", op, matchType));
+            return statusFromErrno(EINVAL, StringPrintf("invalid IptOp: %d, %d", op, match));
         }
     }
     return netdutils::status::ok;
@@ -569,6 +618,10 @@
 
 int TrafficController::changeUidOwnerRule(ChildChain chain, uid_t uid, FirewallRule rule,
                                           FirewallType type) {
+    if (!mBpfEnabled) {
+        ALOGE("bpf is not set up, should use iptables rule");
+        return -ENOSYS;
+    }
     Status res;
     switch (chain) {
         case DOZABLE:
@@ -580,9 +633,6 @@
         case POWERSAVE:
             res = updateOwnerMapEntry(POWERSAVE_MATCH, uid, rule, type);
             break;
-        case RESTRICTED:
-            res = updateOwnerMapEntry(RESTRICTED_MATCH, uid, rule, type);
-            break;
         case NONE:
         default:
             return -EINVAL;
@@ -610,24 +660,28 @@
     RETURN_IF_NOT_OK(mUidOwnerMap.iterate(getUidsToDelete));
 
     for(auto uid : uidsToDelete) {
-        RETURN_IF_NOT_OK(removeRule(uid, match));
+        RETURN_IF_NOT_OK(removeRule(mUidOwnerMap, uid, match));
     }
 
     for (auto uid : uids) {
-        RETURN_IF_NOT_OK(addRule(uid, match));
+        RETURN_IF_NOT_OK(addRule(mUidOwnerMap, uid, match));
     }
     return netdutils::status::ok;
 }
 
 Status TrafficController::addUidInterfaceRules(const int iif,
                                                const std::vector<int32_t>& uidsToAdd) {
+    if (!mBpfEnabled) {
+        ALOGW("UID ingress interface filtering not possible without BPF owner match");
+        return statusFromErrno(EOPNOTSUPP, "eBPF not supported");
+    }
     if (!iif) {
         return statusFromErrno(EINVAL, "Interface rule must specify interface");
     }
     std::lock_guard guard(mMutex);
 
     for (auto uid : uidsToAdd) {
-        netdutils::Status result = addRule(uid, IIF_MATCH, iif);
+        netdutils::Status result = addRule(mUidOwnerMap, uid, IIF_MATCH, iif);
         if (!isOk(result)) {
             ALOGW("addRule failed(%d): uid=%d iif=%d", result.code(), uid, iif);
         }
@@ -636,10 +690,14 @@
 }
 
 Status TrafficController::removeUidInterfaceRules(const std::vector<int32_t>& uidsToDelete) {
+    if (!mBpfEnabled) {
+        ALOGW("UID ingress interface filtering not possible without BPF owner match");
+        return statusFromErrno(EOPNOTSUPP, "eBPF not supported");
+    }
     std::lock_guard guard(mMutex);
 
     for (auto uid : uidsToDelete) {
-        netdutils::Status result = removeRule(uid, IIF_MATCH);
+        netdutils::Status result = removeRule(mUidOwnerMap, uid, IIF_MATCH);
         if (!isOk(result)) {
             ALOGW("removeRule failed(%d): uid=%d", result.code(), uid);
         }
@@ -647,10 +705,10 @@
     return netdutils::status::ok;
 }
 
-int TrafficController::replaceUidOwnerMap(const std::string& name, bool isAllowlist __unused,
+int TrafficController::replaceUidOwnerMap(const std::string& name, bool isWhitelist __unused,
                                           const std::vector<int32_t>& uids) {
-    // FirewallRule rule = isAllowlist ? ALLOW : DENY;
-    // FirewallType type = isAllowlist ? ALLOWLIST : DENYLIST;
+    // FirewallRule rule = isWhitelist ? ALLOW : DENY;
+    // FirewallType type = isWhitelist ? WHITELIST : BLACKLIST;
     Status res;
     if (!name.compare(FirewallController::LOCAL_DOZABLE)) {
         res = replaceRulesInMap(DOZABLE_MATCH, uids);
@@ -658,8 +716,6 @@
         res = replaceRulesInMap(STANDBY_MATCH, uids);
     } else if (!name.compare(FirewallController::LOCAL_POWERSAVE)) {
         res = replaceRulesInMap(POWERSAVE_MATCH, uids);
-    } else if (!name.compare(FirewallController::LOCAL_RESTRICTED)) {
-        res = replaceRulesInMap(RESTRICTED_MATCH, uids);
     } else {
         ALOGE("unknown chain name: %s", name.c_str());
         return -EINVAL;
@@ -693,9 +749,6 @@
         case POWERSAVE:
             match = POWERSAVE_MATCH;
             break;
-        case RESTRICTED:
-            match = RESTRICTED_MATCH;
-            break;
         default:
             return -EINVAL;
     }
@@ -708,9 +761,17 @@
     return -res.code();
 }
 
+bool TrafficController::getBpfEnabled() {
+    return mBpfEnabled;
+}
+
 Status TrafficController::swapActiveStatsMap() {
     std::lock_guard guard(mMutex);
 
+    if (!mBpfEnabled) {
+        return statusFromErrno(EOPNOTSUPP, "This device doesn't have eBPF support");
+    }
+
     uint32_t key = CURRENT_STATS_MAP_CONFIGURATION_KEY;
     auto oldConfiguration = mConfigurationMap.readValue(key);
     if (!oldConfiguration.ok()) {
@@ -753,9 +814,12 @@
             // Clean up all permission information for the related uid if all the
             // packages related to it are uninstalled.
             mPrivilegedUser.erase(uid);
-            Status ret = mUidPermissionMap.deleteValue(uid);
-            if (!isOk(ret) && ret.code() != ENOENT) {
-                ALOGE("Failed to clean up the permission for %u: %s", uid, strerror(ret.code()));
+            if (mBpfEnabled) {
+                Status ret = mUidPermissionMap.deleteValue(uid);
+                if (!isOk(ret) && ret.code() != ENOENT) {
+                    ALOGE("Failed to clean up the permission for %u: %s", uid,
+                          strerror(ret.code()));
+                }
             }
         }
         return;
@@ -770,6 +834,10 @@
             mPrivilegedUser.erase(uid);
         }
 
+        // Skip the bpf map operation if not supported.
+        if (!mBpfEnabled) {
+            continue;
+        }
         // The map stores all the permissions that the UID has, except if the only permission
         // the UID has is the INTERNET permission, then the UID should not appear in the map.
         if (permission != INetd::PERMISSION_INTERNET) {
@@ -825,6 +893,12 @@
     dw.println("TrafficController");
 
     ScopedIndent indentPreBpfModule(dw);
+    dw.println("BPF module status: %s", mBpfEnabled ? "enabled" : "disabled");
+    dw.println("BPF support level: %s", BpfLevelToString(getBpfSupportLevel()).c_str());
+
+    if (!mBpfEnabled) {
+        return;
+    }
 
     dw.blankline();
     dw.println("mCookieTagMap status: %s",
@@ -854,10 +928,10 @@
                getProgramStatus(XT_BPF_INGRESS_PROG_PATH).c_str());
     dw.println("xt_bpf egress program status: %s",
                getProgramStatus(XT_BPF_EGRESS_PROG_PATH).c_str());
-    dw.println("xt_bpf bandwidth allowlist program status: %s",
-               getProgramStatus(XT_BPF_ALLOWLIST_PROG_PATH).c_str());
-    dw.println("xt_bpf bandwidth denylist program status: %s",
-               getProgramStatus(XT_BPF_DENYLIST_PROG_PATH).c_str());
+    dw.println("xt_bpf bandwidth whitelist program status: %s",
+               getProgramStatus(XT_BPF_WHITELIST_PROG_PATH).c_str());
+    dw.println("xt_bpf bandwidth blacklist program status: %s",
+               getProgramStatus(XT_BPF_BLACKLIST_PROG_PATH).c_str());
 
     if (!verbose) {
         return;
diff --git a/server/TrafficController.h b/server/TrafficController.h
index 2e79959..a2539a9 100644
--- a/server/TrafficController.h
+++ b/server/TrafficController.h
@@ -78,6 +78,12 @@
     int deleteTagData(uint32_t tag, uid_t uid, uid_t callingUid) EXCLUDES(mMutex);
 
     /*
+     * Check if the current device have the bpf traffic stats accounting service
+     * running.
+     */
+    bool getBpfEnabled();
+
+    /*
      * Swap the stats map config from current active stats map to the idle one.
      */
     netdutils::Status swapActiveStatsMap() EXCLUDES(mMutex);
@@ -91,7 +97,7 @@
 
     int removeUidOwnerRule(const uid_t uid);
 
-    int replaceUidOwnerMap(const std::string& name, bool isAllowlist,
+    int replaceUidOwnerMap(const std::string& name, bool isWhitelist,
                            const std::vector<int32_t>& uids);
 
     netdutils::Status updateOwnerMapEntry(UidOwnerMatchType match, uid_t uid, FirewallRule rule,
@@ -106,9 +112,9 @@
             EXCLUDES(mMutex);
     netdutils::Status removeUidInterfaceRules(const std::vector<int32_t>& uids) EXCLUDES(mMutex);
 
-    netdutils::Status updateUidOwnerMap(const std::vector<uint32_t>& appStrUids,
-                                        UidOwnerMatchType matchType, BandwidthController::IptOp op)
-            EXCLUDES(mMutex);
+    netdutils::Status updateUidOwnerMap(const std::vector<std::string>& appStrUids,
+                                        BandwidthController::IptJumpOp jumpHandling,
+                                        BandwidthController::IptOp op) EXCLUDES(mMutex);
     static const String16 DUMP_KEYWORD;
 
     int toggleUidOwnerMap(ChildChain chain, bool enable) EXCLUDES(mMutex);
@@ -177,7 +183,7 @@
      * the map right now:
      * - Entry with UID_RULES_CONFIGURATION_KEY:
      *    Store the configuration for the current uid rules. It indicates the device
-     *    is in doze/powersave/standby/restricted mode.
+     *    is in doze/powersave/standby mode.
      * - Entry with CURRENT_STATS_MAP_CONFIGURATION_KEY:
      *    Stores the current live stats map that kernel program is writing to.
      *    Userspace can do scraping and cleaning job on the other one depending on the
@@ -197,10 +203,13 @@
 
     std::unique_ptr<NetlinkListenerInterface> mSkDestroyListener;
 
-    netdutils::Status removeRule(uint32_t uid, UidOwnerMatchType match) REQUIRES(mMutex);
+    netdutils::Status removeRule(BpfMap<uint32_t, UidOwnerValue>& map, uint32_t uid,
+                                 UidOwnerMatchType match) REQUIRES(mMutex);
 
-    netdutils::Status addRule(uint32_t uid, UidOwnerMatchType match, uint32_t iif = 0)
-            REQUIRES(mMutex);
+    netdutils::Status addRule(BpfMap<uint32_t, UidOwnerValue>& map, uint32_t uid,
+                              UidOwnerMatchType match, uint32_t iif = 0) REQUIRES(mMutex);
+
+    bool mBpfEnabled;
 
     // mMutex guards all accesses to mConfigurationMap, mUidOwnerMap, mUidPermissionMap,
     // mStatsMapA, mStatsMapB and mPrivilegedUser. It is designed to solve the following
diff --git a/server/TrafficControllerTest.cpp b/server/TrafficControllerTest.cpp
index 159fb08..3cde9be 100644
--- a/server/TrafficControllerTest.cpp
+++ b/server/TrafficControllerTest.cpp
@@ -16,7 +16,6 @@
  * TrafficControllerTest.cpp - unit tests for TrafficController.cpp
  */
 
-#include <cstdint>
 #include <string>
 #include <vector>
 
@@ -75,6 +74,7 @@
 
     void SetUp() {
         std::lock_guard guard(mTc.mMutex);
+        SKIP_IF_BPF_NOT_SUPPORTED;
         ASSERT_EQ(0, setrlimitForTest());
 
         mFakeCookieTagMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint64_t), sizeof(UidTagValue),
@@ -165,30 +165,30 @@
 
     void checkUidOwnerRuleForChain(ChildChain chain, UidOwnerMatchType match) {
         uint32_t uid = TEST_UID;
-        EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, DENY, DENYLIST));
+        EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, DENY, BLACKLIST));
         Result<UidOwnerValue> value = mFakeUidOwnerMap.readValue(uid);
         EXPECT_RESULT_OK(value);
         EXPECT_TRUE(value.value().rule & match);
 
         uid = TEST_UID2;
-        EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, ALLOW, ALLOWLIST));
+        EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, ALLOW, WHITELIST));
         value = mFakeUidOwnerMap.readValue(uid);
         EXPECT_RESULT_OK(value);
         EXPECT_TRUE(value.value().rule & match);
 
-        EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, DENY, ALLOWLIST));
+        EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, DENY, WHITELIST));
         value = mFakeUidOwnerMap.readValue(uid);
         EXPECT_FALSE(value.ok());
         EXPECT_EQ(ENOENT, value.error().code());
 
         uid = TEST_UID;
-        EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, ALLOW, DENYLIST));
+        EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, ALLOW, BLACKLIST));
         value = mFakeUidOwnerMap.readValue(uid);
         EXPECT_FALSE(value.ok());
         EXPECT_EQ(ENOENT, value.error().code());
 
         uid = TEST_UID3;
-        EXPECT_EQ(-ENOENT, mTc.changeUidOwnerRule(chain, uid, ALLOW, DENYLIST));
+        EXPECT_EQ(-ENOENT, mTc.changeUidOwnerRule(chain, uid, ALLOW, BLACKLIST));
         value = mFakeUidOwnerMap.readValue(uid);
         EXPECT_FALSE(value.ok());
         EXPECT_EQ(ENOENT, value.error().code());
@@ -211,18 +211,18 @@
 
     void checkUidMapReplace(const std::string& name, const std::vector<int32_t>& uids,
                             UidOwnerMatchType match) {
-        bool isAllowlist = true;
-        EXPECT_EQ(0, mTc.replaceUidOwnerMap(name, isAllowlist, uids));
+        bool isWhitelist = true;
+        EXPECT_EQ(0, mTc.replaceUidOwnerMap(name, isWhitelist, uids));
         checkEachUidValue(uids, match);
 
-        isAllowlist = false;
-        EXPECT_EQ(0, mTc.replaceUidOwnerMap(name, isAllowlist, uids));
+        isWhitelist = false;
+        EXPECT_EQ(0, mTc.replaceUidOwnerMap(name, isWhitelist, uids));
         checkEachUidValue(uids, match);
     }
-
-    void expectUidOwnerMapValues(const std::vector<uint32_t>& appUids, uint8_t expectedRule,
+    void expectUidOwnerMapValues(const std::vector<std::string>& appStrUids, uint8_t expectedRule,
                                  uint32_t expectedIif) {
-        for (uint32_t uid : appUids) {
+        for (const std::string& strUid : appStrUids) {
+            uint32_t uid = stoi(strUid);
             Result<UidOwnerValue> value = mFakeUidOwnerMap.readValue(uid);
             EXPECT_RESULT_OK(value);
             EXPECT_EQ(expectedRule, value.value().rule)
@@ -315,6 +315,8 @@
 };
 
 TEST_F(TrafficControllerTest, TestTagSocketV4) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     uint64_t sockCookie;
     int v4socket = setUpSocketAndTag(AF_INET, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
     expectUidTag(sockCookie, TEST_UID, TEST_TAG);
@@ -324,6 +326,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestReTagSocket) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     uint64_t sockCookie;
     int v4socket = setUpSocketAndTag(AF_INET, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
     expectUidTag(sockCookie, TEST_UID, TEST_TAG);
@@ -332,6 +336,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestTagTwoSockets) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     uint64_t sockCookie1;
     uint64_t sockCookie2;
     int v4socket1 = setUpSocketAndTag(AF_INET, &sockCookie1, TEST_TAG, TEST_UID, TEST_UID);
@@ -345,6 +351,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestTagSocketV6) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     uint64_t sockCookie;
     int v6socket = setUpSocketAndTag(AF_INET6, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
     expectUidTag(sockCookie, TEST_UID, TEST_TAG);
@@ -354,12 +362,16 @@
 }
 
 TEST_F(TrafficControllerTest, TestTagInvalidSocket) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     int invalidSocket = -1;
     ASSERT_GT(0, mTc.tagSocket(invalidSocket, TEST_TAG, TEST_UID, TEST_UID));
     expectMapEmpty(mFakeCookieTagMap);
 }
 
 TEST_F(TrafficControllerTest, TestTagSocketWithoutPermission) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
     ASSERT_NE(-1, sock);
     ASSERT_EQ(-EPERM, mTc.tagSocket(sock, TEST_TAG, TEST_UID, TEST_UID2));
@@ -367,6 +379,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestTagSocketWithPermission) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     // Grant permission to calling uid.
     std::vector<uid_t> callingUid = {TEST_UID2};
     mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, callingUid);
@@ -385,6 +399,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestUntagInvalidSocket) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     int invalidSocket = -1;
     ASSERT_GT(0, mTc.untagSocket(invalidSocket));
     int v4socket = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
@@ -393,6 +409,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestTagSocketReachLimitFail) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     uid_t uid = TEST_UID;
     StatsKey tagStatsMapKey[4];
     for (int i = 0; i < 3; i++) {
@@ -404,6 +422,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestTagSocketReachTotalLimitFail) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     StatsKey tagStatsMapKey[4];
     for (int i = 0; i < 4; i++) {
         uint64_t cookie = TEST_COOKIE + i;
@@ -415,6 +435,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestSetCounterSet) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     uid_t callingUid = TEST_UID2;
     addPrivilegedUid(callingUid);
     ASSERT_EQ(0, mTc.setCounterSet(TEST_COUNTERSET, TEST_UID, callingUid));
@@ -428,6 +450,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestSetCounterSetWithoutPermission) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     ASSERT_EQ(-EPERM, mTc.setCounterSet(TEST_COUNTERSET, TEST_UID, TEST_UID2));
     uid_t uid = TEST_UID;
     ASSERT_FALSE(mFakeUidCounterSetMap.readValue(uid).ok());
@@ -435,6 +459,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestSetInvalidCounterSet) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     uid_t callingUid = TEST_UID2;
     addPrivilegedUid(callingUid);
     ASSERT_GT(0, mTc.setCounterSet(OVERFLOW_COUNTERSET, TEST_UID, callingUid));
@@ -444,6 +470,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestDeleteTagDataWithoutPermission) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     uint64_t cookie = 1;
     uid_t uid = TEST_UID;
     uint32_t tag = TEST_TAG;
@@ -455,6 +483,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestDeleteTagData) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     uid_t callingUid = TEST_UID2;
     addPrivilegedUid(callingUid);
     uint64_t cookie = 1;
@@ -480,6 +510,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestDeleteAllUidData) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     uid_t callingUid = TEST_UID2;
     addPrivilegedUid(callingUid);
     uint64_t cookie = 1;
@@ -497,6 +529,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestDeleteDataWithTwoTags) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     uid_t callingUid = TEST_UID2;
     addPrivilegedUid(callingUid);
     uint64_t cookie1 = 1;
@@ -525,6 +559,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestDeleteDataWithTwoUids) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     uid_t callingUid = TEST_UID2;
     addPrivilegedUid(callingUid);
     uint64_t cookie1 = 1;
@@ -566,156 +602,174 @@
 }
 
 TEST_F(TrafficControllerTest, TestUpdateOwnerMapEntry) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     uint32_t uid = TEST_UID;
-    ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(STANDBY_MATCH, uid, DENY, DENYLIST)));
+    ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(STANDBY_MATCH, uid, DENY, BLACKLIST)));
     Result<UidOwnerValue> value = mFakeUidOwnerMap.readValue(uid);
     ASSERT_RESULT_OK(value);
     ASSERT_TRUE(value.value().rule & STANDBY_MATCH);
 
-    ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(DOZABLE_MATCH, uid, ALLOW, ALLOWLIST)));
+    ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(DOZABLE_MATCH, uid, ALLOW, WHITELIST)));
     value = mFakeUidOwnerMap.readValue(uid);
     ASSERT_RESULT_OK(value);
     ASSERT_TRUE(value.value().rule & DOZABLE_MATCH);
 
-    ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(DOZABLE_MATCH, uid, DENY, ALLOWLIST)));
+    ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(DOZABLE_MATCH, uid, DENY, WHITELIST)));
     value = mFakeUidOwnerMap.readValue(uid);
     ASSERT_RESULT_OK(value);
     ASSERT_FALSE(value.value().rule & DOZABLE_MATCH);
 
-    ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(STANDBY_MATCH, uid, ALLOW, DENYLIST)));
+    ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(STANDBY_MATCH, uid, ALLOW, BLACKLIST)));
     ASSERT_FALSE(mFakeUidOwnerMap.readValue(uid).ok());
 
     uid = TEST_UID2;
-    ASSERT_FALSE(isOk(mTc.updateOwnerMapEntry(STANDBY_MATCH, uid, ALLOW, DENYLIST)));
+    ASSERT_FALSE(isOk(mTc.updateOwnerMapEntry(STANDBY_MATCH, uid, ALLOW, BLACKLIST)));
     ASSERT_FALSE(mFakeUidOwnerMap.readValue(uid).ok());
 }
 
 TEST_F(TrafficControllerTest, TestChangeUidOwnerRule) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     checkUidOwnerRuleForChain(DOZABLE, DOZABLE_MATCH);
     checkUidOwnerRuleForChain(STANDBY, STANDBY_MATCH);
     checkUidOwnerRuleForChain(POWERSAVE, POWERSAVE_MATCH);
-    checkUidOwnerRuleForChain(RESTRICTED, RESTRICTED_MATCH);
-    ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(NONE, TEST_UID, ALLOW, ALLOWLIST));
-    ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(INVALID_CHAIN, TEST_UID, ALLOW, ALLOWLIST));
+    ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(NONE, TEST_UID, ALLOW, WHITELIST));
+    ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(INVALID_CHAIN, TEST_UID, ALLOW, WHITELIST));
 }
 
 TEST_F(TrafficControllerTest, TestReplaceUidOwnerMap) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     std::vector<int32_t> uids = {TEST_UID, TEST_UID2, TEST_UID3};
     checkUidMapReplace("fw_dozable", uids, DOZABLE_MATCH);
     checkUidMapReplace("fw_standby", uids, STANDBY_MATCH);
     checkUidMapReplace("fw_powersave", uids, POWERSAVE_MATCH);
-    checkUidMapReplace("fw_restricted", uids, RESTRICTED_MATCH);
     ASSERT_EQ(-EINVAL, mTc.replaceUidOwnerMap("unknow", true, uids));
 }
 
 TEST_F(TrafficControllerTest, TestReplaceSameChain) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     std::vector<int32_t> uids = {TEST_UID, TEST_UID2, TEST_UID3};
     checkUidMapReplace("fw_dozable", uids, DOZABLE_MATCH);
     std::vector<int32_t> newUids = {TEST_UID2, TEST_UID3};
     checkUidMapReplace("fw_dozable", newUids, DOZABLE_MATCH);
 }
 
-TEST_F(TrafficControllerTest, TestDenylistUidMatch) {
-    std::vector<uint32_t> appUids = {1000, 1001, 10012};
-    ASSERT_TRUE(isOk(
-            mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH, BandwidthController::IptOpInsert)));
-    expectUidOwnerMapValues(appUids, PENALTY_BOX_MATCH, 0);
-    ASSERT_TRUE(isOk(
-            mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH, BandwidthController::IptOpDelete)));
+TEST_F(TrafficControllerTest, TestBlacklistUidMatch) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    std::vector<std::string> appStrUids = {"1000", "1001", "10012"};
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReject,
+                                           BandwidthController::IptOpInsert)));
+    expectUidOwnerMapValues(appStrUids, PENALTY_BOX_MATCH, 0);
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReject,
+                                           BandwidthController::IptOpDelete)));
     expectMapEmpty(mFakeUidOwnerMap);
 }
 
-TEST_F(TrafficControllerTest, TestAllowlistUidMatch) {
-    std::vector<uint32_t> appUids = {1000, 1001, 10012};
-    ASSERT_TRUE(isOk(
-            mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH, BandwidthController::IptOpInsert)));
-    expectUidOwnerMapValues(appUids, HAPPY_BOX_MATCH, 0);
-    ASSERT_TRUE(isOk(
-            mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH, BandwidthController::IptOpDelete)));
+TEST_F(TrafficControllerTest, TestWhitelistUidMatch) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    std::vector<std::string> appStrUids = {"1000", "1001", "10012"};
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReturn,
+                                           BandwidthController::IptOpInsert)));
+    expectUidOwnerMapValues(appStrUids, HAPPY_BOX_MATCH, 0);
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReturn,
+                                           BandwidthController::IptOpDelete)));
     expectMapEmpty(mFakeUidOwnerMap);
 }
 
 TEST_F(TrafficControllerTest, TestReplaceMatchUid) {
-    std::vector<uint32_t> appUids = {1000, 1001, 10012};
-    // Add appUids to the denylist and expect that their values are all PENALTY_BOX_MATCH.
-    ASSERT_TRUE(isOk(
-            mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH, BandwidthController::IptOpInsert)));
-    expectUidOwnerMapValues(appUids, PENALTY_BOX_MATCH, 0);
+    SKIP_IF_BPF_NOT_SUPPORTED;
 
-    // Add the same UIDs to the allowlist and expect that we get PENALTY_BOX_MATCH |
+    std::vector<std::string> appStrUids = {"1000", "1001", "10012"};
+    // Add appStrUids to the blacklist and expect that their values are all PENALTY_BOX_MATCH.
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReject,
+                                           BandwidthController::IptOpInsert)));
+    expectUidOwnerMapValues(appStrUids, PENALTY_BOX_MATCH, 0);
+
+    // Add the same UIDs to the whitelist and expect that we get PENALTY_BOX_MATCH |
     // HAPPY_BOX_MATCH.
-    ASSERT_TRUE(isOk(
-            mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH, BandwidthController::IptOpInsert)));
-    expectUidOwnerMapValues(appUids, HAPPY_BOX_MATCH | PENALTY_BOX_MATCH, 0);
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReturn,
+                                           BandwidthController::IptOpInsert)));
+    expectUidOwnerMapValues(appStrUids, HAPPY_BOX_MATCH | PENALTY_BOX_MATCH, 0);
 
-    // Remove the same UIDs from the allowlist and check the PENALTY_BOX_MATCH is still there.
-    ASSERT_TRUE(isOk(
-            mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH, BandwidthController::IptOpDelete)));
-    expectUidOwnerMapValues(appUids, PENALTY_BOX_MATCH, 0);
+    // Remove the same UIDs from the whitelist and check the PENALTY_BOX_MATCH is still there.
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReturn,
+                                           BandwidthController::IptOpDelete)));
+    expectUidOwnerMapValues(appStrUids, PENALTY_BOX_MATCH, 0);
 
-    // Remove the same UIDs from the denylist and check the map is empty.
-    ASSERT_TRUE(isOk(
-            mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH, BandwidthController::IptOpDelete)));
+    // Remove the same UIDs from the blacklist and check the map is empty.
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReject,
+                                           BandwidthController::IptOpDelete)));
     ASSERT_FALSE(mFakeUidOwnerMap.getFirstKey().ok());
 }
 
 TEST_F(TrafficControllerTest, TestDeleteWrongMatchSilentlyFails) {
-    std::vector<uint32_t> appUids = {1000, 1001, 10012};
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    std::vector<std::string> appStrUids = {"1000", "1001", "10012"};
     // If the uid does not exist in the map, trying to delete a rule about it will fail.
-    ASSERT_FALSE(isOk(
-            mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH, BandwidthController::IptOpDelete)));
+    ASSERT_FALSE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReturn,
+                                            BandwidthController::IptOpDelete)));
     expectMapEmpty(mFakeUidOwnerMap);
 
-    // Add denylist rules for appUids.
-    ASSERT_TRUE(isOk(
-            mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH, BandwidthController::IptOpInsert)));
-    expectUidOwnerMapValues(appUids, HAPPY_BOX_MATCH, 0);
+    // Add blacklist rules for appStrUids.
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReturn,
+                                           BandwidthController::IptOpInsert)));
+    expectUidOwnerMapValues(appStrUids, HAPPY_BOX_MATCH, 0);
 
-    // Delete (non-existent) denylist rules for appUids, and check that this silently does
-    // nothing if the uid is in the map but does not have denylist match. This is required because
-    // NetworkManagementService will try to remove a uid from denylist after adding it to the
-    // allowlist and if the remove fails it will not update the uid status.
-    ASSERT_TRUE(isOk(
-            mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH, BandwidthController::IptOpDelete)));
-    expectUidOwnerMapValues(appUids, HAPPY_BOX_MATCH, 0);
+    // Delete (non-existent) blacklist rules for appStrUids, and check that this silently does
+    // nothing if the uid is in the map but does not have blacklist match. This is required because
+    // NetworkManagementService will try to remove a uid from blacklist after adding it to the
+    // whitelist and if the remove fails it will not update the uid status.
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReject,
+                                           BandwidthController::IptOpDelete)));
+    expectUidOwnerMapValues(appStrUids, HAPPY_BOX_MATCH, 0);
 }
 
 TEST_F(TrafficControllerTest, TestAddUidInterfaceFilteringRules) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     int iif0 = 15;
     ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif0, {1000, 1001})));
-    expectUidOwnerMapValues({1000, 1001}, IIF_MATCH, iif0);
+    expectUidOwnerMapValues({"1000", "1001"}, IIF_MATCH, iif0);
 
     // Add some non-overlapping new uids. They should coexist with existing rules
     int iif1 = 16;
     ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif1, {2000, 2001})));
-    expectUidOwnerMapValues({1000, 1001}, IIF_MATCH, iif0);
-    expectUidOwnerMapValues({2000, 2001}, IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"1000", "1001"}, IIF_MATCH, iif0);
+    expectUidOwnerMapValues({"2000", "2001"}, IIF_MATCH, iif1);
 
     // Overwrite some existing uids
     int iif2 = 17;
     ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif2, {1000, 2000})));
-    expectUidOwnerMapValues({1001}, IIF_MATCH, iif0);
-    expectUidOwnerMapValues({2001}, IIF_MATCH, iif1);
-    expectUidOwnerMapValues({1000, 2000}, IIF_MATCH, iif2);
+    expectUidOwnerMapValues({"1001"}, IIF_MATCH, iif0);
+    expectUidOwnerMapValues({"2001"}, IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"1000", "2000"}, IIF_MATCH, iif2);
 }
 
 TEST_F(TrafficControllerTest, TestRemoveUidInterfaceFilteringRules) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     int iif0 = 15;
     int iif1 = 16;
     ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif0, {1000, 1001})));
     ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif1, {2000, 2001})));
-    expectUidOwnerMapValues({1000, 1001}, IIF_MATCH, iif0);
-    expectUidOwnerMapValues({2000, 2001}, IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"1000", "1001"}, IIF_MATCH, iif0);
+    expectUidOwnerMapValues({"2000", "2001"}, IIF_MATCH, iif1);
 
     // Rmove some uids
     ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({1001, 2001})));
-    expectUidOwnerMapValues({1000}, IIF_MATCH, iif0);
-    expectUidOwnerMapValues({2000}, IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"1000"}, IIF_MATCH, iif0);
+    expectUidOwnerMapValues({"2000"}, IIF_MATCH, iif1);
     checkEachUidValue({1000, 2000}, IIF_MATCH);  // Make sure there are only two uids remaining
 
     // Remove non-existent uids shouldn't fail
     ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({2000, 3000})));
-    expectUidOwnerMapValues({1000}, IIF_MATCH, iif0);
+    expectUidOwnerMapValues({"1000"}, IIF_MATCH, iif0);
     checkEachUidValue({1000}, IIF_MATCH);  // Make sure there are only one uid remaining
 
     // Remove everything
@@ -724,65 +778,72 @@
 }
 
 TEST_F(TrafficControllerTest, TestUidInterfaceFilteringRulesCoexistWithExistingMatches) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     // Set up existing PENALTY_BOX_MATCH rules
-    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap({1000, 1001, 10012}, PENALTY_BOX_MATCH,
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap({"1000", "1001", "10012"},
+                                           BandwidthController::IptJumpReject,
                                            BandwidthController::IptOpInsert)));
-    expectUidOwnerMapValues({1000, 1001, 10012}, PENALTY_BOX_MATCH, 0);
+    expectUidOwnerMapValues({"1000", "1001", "10012"}, PENALTY_BOX_MATCH, 0);
 
     // Add some partially-overlapping uid owner rules and check result
     int iif1 = 32;
     ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif1, {10012, 10013, 10014})));
-    expectUidOwnerMapValues({1000, 1001}, PENALTY_BOX_MATCH, 0);
-    expectUidOwnerMapValues({10012}, PENALTY_BOX_MATCH | IIF_MATCH, iif1);
-    expectUidOwnerMapValues({10013, 10014}, IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"1000", "1001"}, PENALTY_BOX_MATCH, 0);
+    expectUidOwnerMapValues({"10012"}, PENALTY_BOX_MATCH | IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"10013", "10014"}, IIF_MATCH, iif1);
 
     // Removing some PENALTY_BOX_MATCH rules should not change uid interface rule
-    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap({1001, 10012}, PENALTY_BOX_MATCH,
+    ASSERT_TRUE(isOk(mTc.updateUidOwnerMap({"1001", "10012"}, BandwidthController::IptJumpReject,
                                            BandwidthController::IptOpDelete)));
-    expectUidOwnerMapValues({1000}, PENALTY_BOX_MATCH, 0);
-    expectUidOwnerMapValues({10012, 10013, 10014}, IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"1000"}, PENALTY_BOX_MATCH, 0);
+    expectUidOwnerMapValues({"10012", "10013", "10014"}, IIF_MATCH, iif1);
 
     // Remove all uid interface rules
     ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({10012, 10013, 10014})));
-    expectUidOwnerMapValues({1000}, PENALTY_BOX_MATCH, 0);
+    expectUidOwnerMapValues({"1000"}, PENALTY_BOX_MATCH, 0);
     // Make sure these are the only uids left
     checkEachUidValue({1000}, PENALTY_BOX_MATCH);
 }
 
 TEST_F(TrafficControllerTest, TestUidInterfaceFilteringRulesCoexistWithNewMatches) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     int iif1 = 56;
     // Set up existing uid interface rules
     ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif1, {10001, 10002})));
-    expectUidOwnerMapValues({10001, 10002}, IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"10001", "10002"}, IIF_MATCH, iif1);
 
     // Add some partially-overlapping doze rules
     EXPECT_EQ(0, mTc.replaceUidOwnerMap("fw_dozable", true, {10002, 10003}));
-    expectUidOwnerMapValues({10001}, IIF_MATCH, iif1);
-    expectUidOwnerMapValues({10002}, DOZABLE_MATCH | IIF_MATCH, iif1);
-    expectUidOwnerMapValues({10003}, DOZABLE_MATCH, 0);
+    expectUidOwnerMapValues({"10001"}, IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"10002"}, DOZABLE_MATCH | IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"10003"}, DOZABLE_MATCH, 0);
 
     // Introduce a third rule type (powersave) on various existing UIDs
     EXPECT_EQ(0, mTc.replaceUidOwnerMap("fw_powersave", true, {10000, 10001, 10002, 10003}));
-    expectUidOwnerMapValues({10000}, POWERSAVE_MATCH, 0);
-    expectUidOwnerMapValues({10001}, POWERSAVE_MATCH | IIF_MATCH, iif1);
-    expectUidOwnerMapValues({10002}, POWERSAVE_MATCH | DOZABLE_MATCH | IIF_MATCH, iif1);
-    expectUidOwnerMapValues({10003}, POWERSAVE_MATCH | DOZABLE_MATCH, 0);
+    expectUidOwnerMapValues({"10000"}, POWERSAVE_MATCH, 0);
+    expectUidOwnerMapValues({"10001"}, POWERSAVE_MATCH | IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"10002"}, POWERSAVE_MATCH | DOZABLE_MATCH | IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"10003"}, POWERSAVE_MATCH | DOZABLE_MATCH, 0);
 
     // Remove all doze rules
     EXPECT_EQ(0, mTc.replaceUidOwnerMap("fw_dozable", true, {}));
-    expectUidOwnerMapValues({10000}, POWERSAVE_MATCH, 0);
-    expectUidOwnerMapValues({10001}, POWERSAVE_MATCH | IIF_MATCH, iif1);
-    expectUidOwnerMapValues({10002}, POWERSAVE_MATCH | IIF_MATCH, iif1);
-    expectUidOwnerMapValues({10003}, POWERSAVE_MATCH, 0);
+    expectUidOwnerMapValues({"10000"}, POWERSAVE_MATCH, 0);
+    expectUidOwnerMapValues({"10001"}, POWERSAVE_MATCH | IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"10002"}, POWERSAVE_MATCH | IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"10003"}, POWERSAVE_MATCH, 0);
 
     // Remove all powersave rules, expect ownerMap to only have uid interface rules left
     EXPECT_EQ(0, mTc.replaceUidOwnerMap("fw_powersave", true, {}));
-    expectUidOwnerMapValues({10001, 10002}, IIF_MATCH, iif1);
+    expectUidOwnerMapValues({"10001", "10002"}, IIF_MATCH, iif1);
     // Make sure these are the only uids left
     checkEachUidValue({10001, 10002}, IIF_MATCH);
 }
 
 TEST_F(TrafficControllerTest, TestGrantInternetPermission) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
 
     mTc.setPermissionForUids(INetd::PERMISSION_INTERNET, appUids);
@@ -791,6 +852,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestRevokeInternetPermission) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
 
     mTc.setPermissionForUids(INetd::PERMISSION_NONE, appUids);
@@ -798,6 +861,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestPermissionUninstalled) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
 
     mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, appUids);
@@ -817,6 +882,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestGrantUpdateStatsPermission) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
 
     mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, appUids);
@@ -829,6 +896,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestRevokeUpdateStatsPermission) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
 
     mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, appUids);
@@ -845,6 +914,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestGrantWrongPermission) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
 
     mTc.setPermissionForUids(INetd::PERMISSION_NONE, appUids);
@@ -853,6 +924,8 @@
 }
 
 TEST_F(TrafficControllerTest, TestGrantDuplicatePermissionSlientlyFail) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
 
     mTc.setPermissionForUids(INetd::PERMISSION_INTERNET, appUids);
diff --git a/server/UidRanges.cpp b/server/UidRanges.cpp
index 093a1e2..883a13f 100644
--- a/server/UidRanges.cpp
+++ b/server/UidRanges.cpp
@@ -127,35 +127,8 @@
     mRanges.erase(end, mRanges.end());
 }
 
-bool UidRanges::isOverlapped(const UidRangeParcel& r1, const UidRangeParcel& r2) const {
-    return (r1.stop >= r2.start) && (r1.start <= r2.stop);
-}
-
-bool UidRanges::overlapsSelf() const {
-    // Compare each element one by one
-    for (size_t i = 0; i < mRanges.size(); i++) {
-        for (size_t j = i + 1; j < mRanges.size(); j++) {
-            if (isOverlapped(mRanges[i], mRanges[j])) {
-                return true;
-            }
-        }
-    }
-    return false;
-}
-
-bool UidRanges::overlaps(const UidRanges& other) const {
-    for (const auto& thisRange : mRanges) {
-        for (const auto& inputRange : other.getRanges()) {
-            if (isOverlapped(thisRange, inputRange)) {
-                return true;
-            }
-        }
-    }
-    return false;
-}
-
 std::string UidRanges::toString() const {
-    std::string s("uids{ ");
+    std::string s("UidRanges{ ");
     for (const auto &range : mRanges) {
         if (length(range) == 0) {
             StringAppendF(&s, "<BAD: %u-%u> ", range.start, range.stop);
diff --git a/server/UidRanges.h b/server/UidRanges.h
index 99e7a99..28d3c6b 100644
--- a/server/UidRanges.h
+++ b/server/UidRanges.h
@@ -28,9 +28,6 @@
 
 class UidRanges {
 public:
-    static constexpr int DEFAULT_SUB_PRIORITY = 0;
-    static constexpr int LOWEST_SUB_PRIORITY = 999;
-
     UidRanges() {}
     UidRanges(const std::vector<android::net::UidRangeParcel>& ranges);
 
@@ -43,16 +40,7 @@
     void add(const UidRanges& other);
     void remove(const UidRanges& other);
 
-    // check if 'mRanges' has uid overlap between elements.
-    bool overlapsSelf() const;
-    // check if this object has uid overlap with the input object.
-    bool overlaps(const UidRanges& other) const;
-    bool empty() const { return mRanges.empty(); }
-
   private:
-    // a utility to check if two UidRangeParcels have uid overlap.
-    bool isOverlapped(const UidRangeParcel& r1, const UidRangeParcel& r2) const;
-
     std::vector<UidRangeParcel> mRanges;
 };
 
diff --git a/server/UnreachableNetwork.cpp b/server/UnreachableNetwork.cpp
deleted file mode 100644
index 2f801f0..0000000
--- a/server/UnreachableNetwork.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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.
- */
-
-#define LOG_TAG "Netd"
-
-#include "UnreachableNetwork.h"
-
-#include "RouteController.h"
-
-namespace android {
-namespace net {
-
-// The unreachable network is used to reject traffic. It is used for system purposes only.
-UnreachableNetwork::UnreachableNetwork(unsigned netId) : Network(netId) {}
-
-int UnreachableNetwork::addUsers(const UidRanges& uidRanges, uint32_t subPriority) {
-    if (!isValidSubPriority(subPriority) || !canAddUidRanges(uidRanges, subPriority)) {
-        return -EINVAL;
-    }
-
-    int ret = RouteController::addUsersToUnreachableNetwork(mNetId, {{subPriority, uidRanges}});
-    if (ret) {
-        ALOGE("failed to add users to unreachable network");
-        return ret;
-    }
-    addToUidRangeMap(uidRanges, subPriority);
-    return 0;
-}
-
-int UnreachableNetwork::removeUsers(const UidRanges& uidRanges, uint32_t subPriority) {
-    if (!isValidSubPriority(subPriority)) return -EINVAL;
-
-    int ret =
-            RouteController::removeUsersFromUnreachableNetwork(mNetId, {{subPriority, uidRanges}});
-    if (ret) {
-        ALOGE("failed to remove users from unreachable network");
-        return ret;
-    }
-    removeFromUidRangeMap(uidRanges, subPriority);
-    return 0;
-}
-
-bool UnreachableNetwork::isValidSubPriority(uint32_t priority) {
-    return priority >= UidRanges::DEFAULT_SUB_PRIORITY &&
-           priority <= UidRanges::LOWEST_SUB_PRIORITY;
-}
-
-}  // namespace net
-}  // namespace android
diff --git a/server/UnreachableNetwork.h b/server/UnreachableNetwork.h
deleted file mode 100644
index f1547d6..0000000
--- a/server/UnreachableNetwork.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.
- */
-
-#pragma once
-
-#include "Network.h"
-
-namespace android::net {
-
-class UnreachableNetwork : public Network {
-  public:
-    explicit UnreachableNetwork(unsigned netId);
-    [[nodiscard]] int addUsers(const UidRanges& uidRanges, uint32_t subPriority) override;
-    [[nodiscard]] int removeUsers(const UidRanges& uidRanges, uint32_t subPriority) override;
-    bool isUnreachable() override { return true; }
-    bool canAddUsers() override { return true; }
-
-  private:
-    std::string getTypeString() const override { return "UNREACHABLE"; };
-    bool isValidSubPriority(uint32_t priority) override;
-};
-
-}  // namespace android::net
\ No newline at end of file
diff --git a/server/VirtualNetwork.cpp b/server/VirtualNetwork.cpp
index 1906e20..33791c6 100644
--- a/server/VirtualNetwork.cpp
+++ b/server/VirtualNetwork.cpp
@@ -20,6 +20,7 @@
 
 #include "VirtualNetwork.h"
 
+#include "SockDiag.h"
 #include "RouteController.h"
 
 #include "log/log.h"
@@ -27,48 +28,78 @@
 namespace android {
 namespace net {
 
-VirtualNetwork::VirtualNetwork(unsigned netId, bool secure) : Network(netId, secure) {}
+VirtualNetwork::VirtualNetwork(unsigned netId, bool secure) : Network(netId), mSecure(secure) {}
 
 VirtualNetwork::~VirtualNetwork() {}
 
-int VirtualNetwork::addUsers(const UidRanges& uidRanges, uint32_t subPriority) {
-    if (!isValidSubPriority(subPriority) || !canAddUidRanges(uidRanges, subPriority)) {
-        return -EINVAL;
+bool VirtualNetwork::isSecure() const {
+    return mSecure;
+}
+
+bool VirtualNetwork::appliesToUser(uid_t uid) const {
+    return mUidRanges.hasUid(uid);
+}
+
+
+int VirtualNetwork::maybeCloseSockets(bool add, const UidRanges& uidRanges,
+                                      const std::set<uid_t>& protectableUsers) {
+    if (!mSecure) {
+        return 0;
     }
 
+    SockDiag sd;
+    if (!sd.open()) {
+        return -EBADFD;
+    }
+
+    if (int ret = sd.destroySockets(uidRanges, protectableUsers, true /* excludeLoopback */)) {
+        ALOGE("Failed to close sockets while %s %s to network %d: %s",
+              add ? "adding" : "removing", uidRanges.toString().c_str(), mNetId, strerror(-ret));
+        return ret;
+    }
+
+    return 0;
+}
+
+int VirtualNetwork::addUsers(const UidRanges& uidRanges, const std::set<uid_t>& protectableUsers) {
+    maybeCloseSockets(true, uidRanges, protectableUsers);
+
     for (const std::string& interface : mInterfaces) {
-        int ret = RouteController::addUsersToVirtualNetwork(mNetId, interface.c_str(), mSecure,
-                                                            {{subPriority, uidRanges}});
-        if (ret) {
+        if (int ret = RouteController::addUsersToVirtualNetwork(mNetId, interface.c_str(), mSecure,
+                                                                uidRanges)) {
             ALOGE("failed to add users on interface %s of netId %u", interface.c_str(), mNetId);
             return ret;
         }
     }
-    addToUidRangeMap(uidRanges, subPriority);
+    mUidRanges.add(uidRanges);
     return 0;
 }
 
-int VirtualNetwork::removeUsers(const UidRanges& uidRanges, uint32_t subPriority) {
-    if (!isValidSubPriority(subPriority)) return -EINVAL;
+int VirtualNetwork::removeUsers(const UidRanges& uidRanges,
+                                const std::set<uid_t>& protectableUsers) {
+    maybeCloseSockets(false, uidRanges, protectableUsers);
 
     for (const std::string& interface : mInterfaces) {
-        int ret = RouteController::removeUsersFromVirtualNetwork(mNetId, interface.c_str(), mSecure,
-                                                                 {{subPriority, uidRanges}});
-        if (ret) {
+        if (int ret = RouteController::removeUsersFromVirtualNetwork(mNetId, interface.c_str(),
+                                                                     mSecure, uidRanges)) {
             ALOGE("failed to remove users on interface %s of netId %u", interface.c_str(), mNetId);
             return ret;
         }
     }
-    removeFromUidRangeMap(uidRanges, subPriority);
+    mUidRanges.remove(uidRanges);
     return 0;
 }
 
+Network::Type VirtualNetwork::getType() const {
+    return VIRTUAL;
+}
+
 int VirtualNetwork::addInterface(const std::string& interface) {
     if (hasInterface(interface)) {
         return 0;
     }
     if (int ret = RouteController::addInterfaceToVirtualNetwork(mNetId, interface.c_str(), mSecure,
-                                                                mUidRangeMap)) {
+                                                                mUidRanges)) {
         ALOGE("failed to add interface %s to VPN netId %u", interface.c_str(), mNetId);
         return ret;
     }
@@ -81,7 +112,7 @@
         return 0;
     }
     if (int ret = RouteController::removeInterfaceFromVirtualNetwork(mNetId, interface.c_str(),
-                                                                     mSecure, mUidRangeMap)) {
+                                                                     mSecure, mUidRanges)) {
         ALOGE("failed to remove interface %s from VPN netId %u", interface.c_str(), mNetId);
         return ret;
     }
@@ -89,10 +120,5 @@
     return 0;
 }
 
-bool VirtualNetwork::isValidSubPriority(uint32_t priority) {
-    // Only supports default subsidiary permissions.
-    return priority == UidRanges::DEFAULT_SUB_PRIORITY;
-}
-
 }  // namespace net
 }  // namespace android
diff --git a/server/VirtualNetwork.h b/server/VirtualNetwork.h
index 20c9e2c..20c8dee 100644
--- a/server/VirtualNetwork.h
+++ b/server/VirtualNetwork.h
@@ -19,6 +19,7 @@
 #include <set>
 
 #include "Network.h"
+#include "UidRanges.h"
 
 namespace android::net {
 
@@ -33,16 +34,23 @@
 public:
     VirtualNetwork(unsigned netId, bool secure);
     virtual ~VirtualNetwork();
-    [[nodiscard]] int addUsers(const UidRanges& uidRanges, uint32_t subPriority) override;
-    [[nodiscard]] int removeUsers(const UidRanges& uidRanges, uint32_t subPriority) override;
-    bool isVirtual() override { return true; }
-    bool canAddUsers() override { return true; }
+
+    bool isSecure() const;
+    bool appliesToUser(uid_t uid) const;
+
+    [[nodiscard]] int addUsers(const UidRanges& uidRanges, const std::set<uid_t>& protectableUsers);
+    [[nodiscard]] int removeUsers(const UidRanges& uidRanges,
+                                  const std::set<uid_t>& protectableUsers);
 
   private:
-    std::string getTypeString() const override { return "VIRTUAL"; };
+    Type getType() const override;
     [[nodiscard]] int addInterface(const std::string& interface) override;
     [[nodiscard]] int removeInterface(const std::string& interface) override;
-    bool isValidSubPriority(uint32_t priority) override;
+    int maybeCloseSockets(bool add, const UidRanges& uidRanges,
+                          const std::set<uid_t>& protectableUsers);
+
+    const bool mSecure;
+    UidRanges mUidRanges;
 };
 
 }  // namespace android::net
diff --git a/server/XfrmController.cpp b/server/XfrmController.cpp
index 24168a2..33a2aa2 100644
--- a/server/XfrmController.cpp
+++ b/server/XfrmController.cpp
@@ -646,69 +646,20 @@
     return ret;
 }
 
-netdutils::Status XfrmController::ipSecMigrate(int32_t transformId, int32_t selAddrFamily,
-                                               int32_t direction,
-                                               const std::string& oldSourceAddress,
-                                               const std::string& oldDestinationAddress,
-                                               const std::string& newSourceAddress,
-                                               const std::string& newDestinationAddress,
-                                               int32_t xfrmInterfaceId) {
-    ALOGD("XfrmController:%s, line=%d", __FUNCTION__, __LINE__);
-    ALOGD("transformId=%d", transformId);
-    ALOGD("selAddrFamily=%d", selAddrFamily);
-    ALOGD("direction=%d", direction);
-    ALOGD("oldSourceAddress=%s", oldSourceAddress.c_str());
-    ALOGD("oldDestinationAddress=%s", oldDestinationAddress.c_str());
-    ALOGD("newSourceAddress=%s", newSourceAddress.c_str());
-    ALOGD("newDestinationAddress=%s", newDestinationAddress.c_str());
-    ALOGD("xfrmInterfaceId=%d", xfrmInterfaceId);
-
-    XfrmSocketImpl sock;
-    Status socketStatus = sock.open();
-    if (!socketStatus.ok()) {
-        ALOGD("Sock open failed for XFRM, line=%d", __LINE__);
-        return socketStatus;
-    }
-
-    XfrmMigrateInfo migrateInfo{};
-    Status ret =
-            fillXfrmCommonInfo(oldSourceAddress, oldDestinationAddress, 0 /* spi */, 0 /* mark */,
-                               0 /* markMask */, transformId, xfrmInterfaceId, &migrateInfo);
-
-    if (!ret.ok()) {
-        ALOGD("Failed to fill in XfrmCommonInfo, line=%d", __LINE__);
-        return ret;
-    }
-
-    migrateInfo.selAddrFamily = selAddrFamily;
-    migrateInfo.direction = static_cast<XfrmDirection>(direction);
-
-    ret = fillXfrmEndpointPair(newSourceAddress, newDestinationAddress,
-                               &migrateInfo.newEndpointInfo);
-    if (!ret.ok()) {
-        ALOGD("Failed to fill in XfrmEndpointPair, line=%d", __LINE__);
-        return ret;
-    }
-
-    ret = migrate(migrateInfo, sock);
-
-    if (!ret.ok()) {
-        ALOGD("Failed to migrate Security Association, line=%d", __LINE__);
-    }
-    return ret;
-}
-
-netdutils::Status XfrmController::fillXfrmEndpointPair(const std::string& sourceAddress,
-                                                       const std::string& destinationAddress,
-                                                       XfrmEndpointPair* endpointPair) {
+netdutils::Status XfrmController::fillXfrmCommonInfo(const std::string& sourceAddress,
+                                                     const std::string& destinationAddress,
+                                                     int32_t spi, int32_t markValue,
+                                                     int32_t markMask, int32_t transformId,
+                                                     int32_t xfrmInterfaceId,
+                                                     XfrmCommonInfo* info) {
     // Use the addresses to determine the address family and do validation
     xfrm_address_t sourceXfrmAddr{}, destXfrmAddr{};
     StatusOr<int> sourceFamily, destFamily;
     sourceFamily = convertToXfrmAddr(sourceAddress, &sourceXfrmAddr);
     destFamily = convertToXfrmAddr(destinationAddress, &destXfrmAddr);
     if (!isOk(sourceFamily) || !isOk(destFamily)) {
-        return netdutils::statusFromErrno(
-                EINVAL, "Invalid address " + sourceAddress + "/" + destinationAddress);
+        return netdutils::statusFromErrno(EINVAL, "Invalid address " + sourceAddress + "/" +
+                                                      destinationAddress);
     }
 
     if (destFamily.value() == AF_UNSPEC ||
@@ -718,24 +669,10 @@
         return netdutils::statusFromErrno(EINVAL, "Invalid or mismatched address families");
     }
 
-    endpointPair->addrFamily = destFamily.value();
+    info->addrFamily = destFamily.value();
 
-    endpointPair->dstAddr = destXfrmAddr;
-    endpointPair->srcAddr = sourceXfrmAddr;
-
-    return netdutils::status::ok;
-}
-
-netdutils::Status XfrmController::fillXfrmCommonInfo(const std::string& sourceAddress,
-                                                     const std::string& destinationAddress,
-                                                     int32_t spi, int32_t markValue,
-                                                     int32_t markMask, int32_t transformId,
-                                                     int32_t xfrmInterfaceId,
-                                                     XfrmCommonInfo* info) {
-    Status ret = fillXfrmEndpointPair(sourceAddress, destinationAddress, info);
-    if (!isOk(ret)) {
-        return ret;
-    }
+    info->dstAddr = destXfrmAddr;
+    info->srcAddr = sourceXfrmAddr;
 
     return fillXfrmCommonInfo(spi, markValue, markMask, transformId, xfrmInterfaceId, info);
 }
@@ -784,7 +721,6 @@
     }
 
     spInfo.selAddrFamily = spInfo.addrFamily;
-    spInfo.direction = static_cast<XfrmDirection>(direction);
 
     // Allow dual stack sockets. Dual stack sockets are guaranteed to never have an AF_INET source
     // address; the source address would instead be an IPv4-mapped address. Thus, disallow AF_INET
@@ -801,7 +737,7 @@
         xfrm_user_tmpl tmpl;
     } policy{};
 
-    fillUserSpInfo(spInfo, &policy.info);
+    fillUserSpInfo(spInfo, static_cast<XfrmDirection>(direction), &policy.info);
     fillUserTemplate(spInfo, &policy.tmpl);
 
     LOG_HEX("XfrmUserPolicy", reinterpret_cast<char*>(&policy), sizeof(policy));
@@ -917,18 +853,18 @@
     // separately for IPv4 and IPv6 policies, and thus only need to map a single inner address
     // family to the outer address families.
     spInfo.selAddrFamily = selAddrFamily;
-    spInfo.direction = static_cast<XfrmDirection>(direction);
 
     if (msgType == XFRM_MSG_DELPOLICY) {
         RETURN_IF_NOT_OK(fillXfrmCommonInfo(spi, markValue, markMask, transformId, xfrmInterfaceId,
                                             &spInfo));
 
-        return deleteTunnelModeSecurityPolicy(spInfo, sock);
+        return deleteTunnelModeSecurityPolicy(spInfo, sock, static_cast<XfrmDirection>(direction));
     } else {
         RETURN_IF_NOT_OK(fillXfrmCommonInfo(tmplSrcAddress, tmplDstAddress, spi, markValue,
                                             markMask, transformId, xfrmInterfaceId, &spInfo));
 
-        return updateTunnelModeSecurityPolicy(spInfo, sock, msgType);
+        return updateTunnelModeSecurityPolicy(spInfo, sock, static_cast<XfrmDirection>(direction),
+                                              msgType);
     }
 }
 
@@ -1025,7 +961,7 @@
     len = iov[MARK].iov_len = fillNlAttrXfrmMark(record, &xfrmmark);
     iov[MARK_PAD].iov_len = NLA_ALIGN(len) - len;
 
-    len = iov[OUTPUT_MARK].iov_len = fillNlAttrXfrmOutputMark(record, &xfrmoutputmark);
+    len = iov[OUTPUT_MARK].iov_len = fillNlAttrXfrmOutputMark(record.netId, &xfrmoutputmark);
     iov[OUTPUT_MARK_PAD].iov_len = NLA_ALIGN(len) - len;
 
     len = iov[ENCAP].iov_len = fillNlAttrXfrmEncapTmpl(record, &encap);
@@ -1174,24 +1110,6 @@
     return sock.sendMessage(XFRM_MSG_DELSA, NETLINK_REQUEST_FLAGS, 0, &iov);
 }
 
-netdutils::Status XfrmController::migrate(const XfrmMigrateInfo& record, const XfrmSocket& sock) {
-    xfrm_userpolicy_id xfrm_policyid{};
-    nlattr_xfrm_user_migrate xfrm_migrate{};
-
-    __kernel_size_t lenPolicyId = fillUserPolicyId(record, &xfrm_policyid);
-    __kernel_size_t lenXfrmMigrate = fillNlAttrXfrmMigrate(record, &xfrm_migrate);
-
-    std::vector<iovec> iov = {
-            {nullptr, 0},  // reserved for the eventual addition of a NLMSG_HDR
-            {&xfrm_policyid, lenPolicyId},
-            {kPadBytes, NLMSG_ALIGN(lenPolicyId) - lenPolicyId},
-            {&xfrm_migrate, lenXfrmMigrate},
-            {kPadBytes, NLMSG_ALIGN(lenXfrmMigrate) - lenXfrmMigrate},
-    };
-
-    return sock.sendMessage(XFRM_MSG_MIGRATE, NETLINK_REQUEST_FLAGS, 0, &iov);
-}
-
 netdutils::Status XfrmController::allocateSpi(const XfrmSaInfo& record, uint32_t minSpi,
                                               uint32_t maxSpi, uint32_t* outSpi,
                                               const XfrmSocket& sock) {
@@ -1242,6 +1160,7 @@
 
 netdutils::Status XfrmController::updateTunnelModeSecurityPolicy(const XfrmSpInfo& record,
                                                                  const XfrmSocket& sock,
+                                                                 XfrmDirection direction,
                                                                  uint16_t msgType) {
     xfrm_userpolicy_info userpolicy{};
     nlattr_user_tmpl usertmpl{};
@@ -1273,7 +1192,7 @@
     };
 
     int len;
-    len = iov[USERPOLICY].iov_len = fillUserSpInfo(record, &userpolicy);
+    len = iov[USERPOLICY].iov_len = fillUserSpInfo(record, direction, &userpolicy);
     iov[USERPOLICY_PAD].iov_len = NLMSG_ALIGN(len) - len;
 
     len = iov[USERTMPL].iov_len = fillNlAttrUserTemplate(record, &usertmpl);
@@ -1289,7 +1208,8 @@
 }
 
 netdutils::Status XfrmController::deleteTunnelModeSecurityPolicy(const XfrmSpInfo& record,
-                                                                 const XfrmSocket& sock) {
+                                                                 const XfrmSocket& sock,
+                                                                 XfrmDirection direction) {
     xfrm_userpolicy_id policyid{};
     nlattr_xfrm_mark xfrmmark{};
     nlattr_xfrm_interface_id xfrm_if_id{};
@@ -1314,7 +1234,7 @@
             {kPadBytes, 0},    // up to NLATTR_ALIGNTO pad bytes
     };
 
-    int len = iov[USERPOLICYID].iov_len = fillUserPolicyId(record, &policyid);
+    int len = iov[USERPOLICYID].iov_len = fillUserPolicyId(record, direction, &policyid);
     iov[USERPOLICYID_PAD].iov_len = NLMSG_ALIGN(len) - len;
 
     len = iov[MARK].iov_len = fillNlAttrXfrmMark(record, &xfrmmark);
@@ -1326,14 +1246,15 @@
     return sock.sendMessage(XFRM_MSG_DELPOLICY, NETLINK_REQUEST_FLAGS, 0, &iov);
 }
 
-int XfrmController::fillUserSpInfo(const XfrmSpInfo& record, xfrm_userpolicy_info* usersp) {
+int XfrmController::fillUserSpInfo(const XfrmSpInfo& record, XfrmDirection direction,
+                                   xfrm_userpolicy_info* usersp) {
     fillXfrmSelector(record.selAddrFamily, &usersp->sel);
     fillXfrmLifetimeDefaults(&usersp->lft);
     fillXfrmCurLifetimeDefaults(&usersp->curlft);
     /* if (index) index & 0x3 == dir -- must be true
      * xfrm_user.c:verify_newpolicy_info() */
     usersp->index = 0;
-    usersp->dir = static_cast<uint8_t>(record.direction);
+    usersp->dir = static_cast<uint8_t>(direction);
     usersp->action = XFRM_POLICY_ALLOW;
     usersp->flags = XFRM_POLICY_LOCALOK;
     usersp->share = XFRM_SHARE_UNIQUE;
@@ -1381,30 +1302,24 @@
 
 // This function sets the output mark (or set-mark in newer kernels) to that of the underlying
 // Network's netid. This allows outbound IPsec Tunnel mode packets to be correctly directed to a
-// preselected underlying Network. Outbound packets are marked as protected from VPNs and have a
-// network explicitly selected to prevent interference or routing loops. Also sets permission flag
-// to PERMISSION_SYSTEM to allow use of background/restricted networks. Inbound packets have all
-// the flags and fields cleared to simulate the decapsulated packet being a fresh, unseen packet.
-int XfrmController::fillNlAttrXfrmOutputMark(const XfrmSaInfo& record,
+// preselected underlying Network. Packet as marked as protected from VPNs and have a network
+// explicitly selected to prevent interference or routing loops. Also set permission flag to
+// PERMISSION_SYSTEM to ensure we can use background/restricted networks. Permission to use
+// restricted networks is checked in IpSecService.
+int XfrmController::fillNlAttrXfrmOutputMark(const __u32 underlyingNetId,
                                              nlattr_xfrm_output_mark* output_mark) {
-    // Only set for tunnel mode transforms
-    if (record.mode != XfrmMode::TUNNEL) {
+    // Do not set if we were not given an output mark
+    if (underlyingNetId == 0) {
         return 0;
     }
 
     Fwmark fwmark;
+    fwmark.netId = underlyingNetId;
 
-    // Only outbound transforms have an underlying network set.
-    if (record.netId != 0) {
-        fwmark.netId = record.netId;
-        fwmark.permission = PERMISSION_SYSTEM;
-        fwmark.explicitlySelected = true;
-        fwmark.protectedFromVpn = true;
-    }
-
-    // Else (inbound transforms), reset to default mark (empty); UID billing for inbound tunnel mode
-    // transforms are exclusively done on inner packet, and therefore can never have been set.
-
+    // TODO: Rework this to more accurately follow the underlying network
+    fwmark.permission = PERMISSION_SYSTEM;
+    fwmark.explicitlySelected = true;
+    fwmark.protectedFromVpn = true;
     output_mark->outputMark = fwmark.intValue;
 
     int len = NLA_HDRLEN + sizeof(__u32);
@@ -1425,28 +1340,11 @@
     return len;
 }
 
-int XfrmController::fillNlAttrXfrmMigrate(const XfrmMigrateInfo& record,
-                                          nlattr_xfrm_user_migrate* migrate) {
-    migrate->migrate.old_daddr = record.dstAddr;
-    migrate->migrate.old_saddr = record.srcAddr;
-    migrate->migrate.new_daddr = record.newEndpointInfo.dstAddr;
-    migrate->migrate.new_saddr = record.newEndpointInfo.srcAddr;
-    migrate->migrate.proto = IPPROTO_ESP;
-    migrate->migrate.mode = static_cast<uint8_t>(XfrmMode::TUNNEL);
-    migrate->migrate.reqid = record.transformId;
-    migrate->migrate.old_family = record.addrFamily;
-    migrate->migrate.new_family = record.newEndpointInfo.addrFamily;
-
-    int len = NLA_HDRLEN + sizeof(xfrm_user_migrate);
-    fillXfrmNlaHdr(&migrate->hdr, XFRMA_MIGRATE, len);
-
-    return len;
-}
-
-int XfrmController::fillUserPolicyId(const XfrmSpInfo& record, xfrm_userpolicy_id* usersp) {
+int XfrmController::fillUserPolicyId(const XfrmSpInfo& record, XfrmDirection direction,
+                                     xfrm_userpolicy_id* usersp) {
     // For DELPOLICY, when index is absent, selector is needed to match the policy
     fillXfrmSelector(record.selAddrFamily, &usersp->sel);
-    usersp->dir = static_cast<uint8_t>(record.direction);
+    usersp->dir = static_cast<uint8_t>(direction);
     return sizeof(*usersp);
 }
 
diff --git a/server/XfrmController.h b/server/XfrmController.h
index 4f167c5..15eef3d 100644
--- a/server/XfrmController.h
+++ b/server/XfrmController.h
@@ -107,19 +107,15 @@
     uint16_t dstPort;
 };
 
-struct XfrmEndpointPair {
+// minimally sufficient structure to match either an SA or a Policy
+struct XfrmCommonInfo {
     xfrm_address_t dstAddr; // network order
     xfrm_address_t srcAddr;
     int addrFamily;  // AF_INET or AF_INET6
-};
-
-// minimally sufficient structure to match either an SA or a Policy
-struct XfrmCommonInfo : XfrmEndpointPair {
     int transformId; // requestId
     int spi;
     xfrm_mark mark;
     int xfrm_if_id;
-    XfrmMode mode;
 };
 
 struct XfrmSaInfo : XfrmCommonInfo {
@@ -127,18 +123,14 @@
     XfrmAlgo crypt;
     XfrmAlgo aead;
     int netId;
+    XfrmMode mode;
     XfrmEncap encap;
 };
 
-struct XfrmSpInfo : XfrmCommonInfo {
+struct XfrmSpInfo : XfrmSaInfo {
     // Address family in XfrmCommonInfo used for template/SA matching, need separate addrFamily
     // for selectors
     int selAddrFamily;  // AF_INET or AF_INET6
-    XfrmDirection direction;
-};
-
-struct XfrmMigrateInfo : XfrmSpInfo {
-    XfrmEndpointPair newEndpointInfo;
 };
 
 /*
@@ -268,13 +260,6 @@
 
     static netdutils::Status ipSecRemoveTunnelInterface(const std::string& deviceName);
 
-    // Only available for Tunnel must already have a matching tunnel SA and policy
-    static netdutils::Status ipSecMigrate(int32_t transformId, int32_t selAddrFamily,
-                                          int32_t direction, const std::string& oldSourceAddress,
-                                          const std::string& oldDestinationAddress,
-                                          const std::string& newSourceAddress,
-                                          const std::string& newDestinationAddress,
-                                          int32_t xfrmInterfaceId);
     void dump(netdutils::DumpWriter& dw);
 
     // Some XFRM netlink attributes comprise a header, a struct, and some data
@@ -344,13 +329,6 @@
         __u32 if_id;
     };
 
-    // Container for the content of an XFRMA_MIGRATE netlink attribute.
-    // Exposed for testing
-    struct nlattr_xfrm_user_migrate {
-        nlattr hdr;
-        xfrm_user_migrate migrate;
-    };
-
     // Exposed for testing
     struct nlattr_payload_u32 {
         nlattr hdr;
@@ -360,9 +338,6 @@
   private:
     static bool isXfrmIntfSupported();
 
-    static netdutils::Status fillXfrmEndpointPair(const std::string& sourceAddress,
-                                                  const std::string& destinationAddress,
-                                                  XfrmEndpointPair* info);
     // helper functions for filling in the XfrmCommonInfo (and XfrmSaInfo) structure
     static netdutils::Status fillXfrmCommonInfo(const std::string& sourceAddress,
                                                 const std::string& destinationAddress, int32_t spi,
@@ -398,15 +373,15 @@
     static int fillUserSaId(const XfrmCommonInfo& record, xfrm_usersa_id* said);
     static void fillUserTemplate(const XfrmSpInfo& record, xfrm_user_tmpl* tmpl);
 
-    static int fillUserSpInfo(const XfrmSpInfo& record, xfrm_userpolicy_info* usersp);
+    static int fillUserSpInfo(const XfrmSpInfo& record, XfrmDirection direction,
+                              xfrm_userpolicy_info* usersp);
     static int fillNlAttrUserTemplate(const XfrmSpInfo& record, nlattr_user_tmpl* tmpl);
-    static int fillUserPolicyId(const XfrmSpInfo& record, xfrm_userpolicy_id* policy_id);
+    static int fillUserPolicyId(const XfrmSpInfo& record, XfrmDirection direction,
+                                xfrm_userpolicy_id* policy_id);
     static int fillNlAttrXfrmMark(const XfrmCommonInfo& record, nlattr_xfrm_mark* mark);
-    static int fillNlAttrXfrmOutputMark(const XfrmSaInfo& record,
+    static int fillNlAttrXfrmOutputMark(const __u32 underlyingNetId,
                                         nlattr_xfrm_output_mark* output_mark);
     static int fillNlAttrXfrmIntfId(const __u32 intf_id_value, nlattr_xfrm_interface_id* intf_id);
-    static int fillNlAttrXfrmMigrate(const XfrmMigrateInfo& record,
-                                     nlattr_xfrm_user_migrate* migrate);
 
     static netdutils::Status allocateSpi(const XfrmSaInfo& record, uint32_t minSpi, uint32_t maxSpi,
                                          uint32_t* outSpi, const XfrmSocket& sock);
@@ -419,10 +394,11 @@
                                                    int32_t xfrmInterfaceId, int32_t msgType);
     static netdutils::Status updateTunnelModeSecurityPolicy(const XfrmSpInfo& record,
                                                             const XfrmSocket& sock,
+                                                            XfrmDirection direction,
                                                             uint16_t msgType);
     static netdutils::Status deleteTunnelModeSecurityPolicy(const XfrmSpInfo& record,
-                                                            const XfrmSocket& sock);
-    static netdutils::Status migrate(const XfrmMigrateInfo& record, const XfrmSocket& sock);
+                                                            const XfrmSocket& sock,
+                                                            XfrmDirection direction);
     static netdutils::Status flushInterfaces();
     static netdutils::Status flushSaDb(const XfrmSocket& s);
     static netdutils::Status flushPolicyDb(const XfrmSocket& s);
diff --git a/server/aidl_api/netd_aidl_interface/1/.hash b/server/aidl_api/netd_aidl_interface/1/.hash
new file mode 100644
index 0000000..d33e903
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/1/.hash
@@ -0,0 +1 @@
+69c2ac134efbb31e9591d7e5c3640fb839e23bdb
diff --git a/server/aidl_api/netd_aidl_interface/1/android/net/INetd.aidl b/server/aidl_api/netd_aidl_interface/1/android/net/INetd.aidl
new file mode 100644
index 0000000..664c643
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/1/android/net/INetd.aidl
@@ -0,0 +1,132 @@
+package android.net;
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isWhitelist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  void networkCreatePhysical(int netId, int permission);
+  void networkCreateVpn(int netId, boolean secure);
+  void networkDestroy(int netId);
+  void networkAddInterface(int netId, in @utf8InCpp String iface);
+  void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+  void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+  void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+  boolean tetherApplyDnsInterfaces();
+  android.net.TetherStatsParcel[] tetherGetStats();
+  void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+  void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+  void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+  int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+  void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+  void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+  void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+  void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+  void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void strictUidCleartextPenalty(int uid, int policyPenalty);
+  @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+  void clatdStop(in @utf8InCpp String ifName);
+  boolean ipfwdEnabled();
+  @utf8InCpp String[] ipfwdGetRequesterList();
+  void ipfwdEnableForwarding(in @utf8InCpp String requester);
+  void ipfwdDisableForwarding(in @utf8InCpp String requester);
+  void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+  void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+  void bandwidthSetGlobalAlert(long bytes);
+  void bandwidthAddNaughtyApp(int uid);
+  void bandwidthRemoveNaughtyApp(int uid);
+  void bandwidthAddNiceApp(int uid);
+  void bandwidthRemoveNiceApp(int uid);
+  void tetherStart(in @utf8InCpp String[] dhcpRanges);
+  void tetherStop();
+  boolean tetherIsEnabled();
+  void tetherInterfaceAdd(in @utf8InCpp String ifName);
+  void tetherInterfaceRemove(in @utf8InCpp String ifName);
+  @utf8InCpp String[] tetherInterfaceList();
+  void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+  @utf8InCpp String[] tetherDnsList();
+  void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  int networkGetDefault();
+  void networkSetDefault(int netId);
+  void networkClearDefault();
+  void networkSetPermissionForNetwork(int netId, int permission);
+  void networkSetPermissionForUser(int permission, in int[] uids);
+  void networkClearPermissionForUser(in int[] uids);
+  void trafficSetNetPermForUids(int permission, in int[] uids);
+  void networkSetProtectAllow(int uid);
+  void networkSetProtectDeny(int uid);
+  boolean networkCanProtect(int uid);
+  void firewallSetFirewallType(int firewalltype);
+  void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+  void firewallSetUidRule(int childChain, int uid, int firewallRule);
+  void firewallEnableChildChain(int childChain, boolean enable);
+  @utf8InCpp String[] interfaceGetList();
+  android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+  void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+  void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+  void interfaceClearAddrs(in @utf8InCpp String ifName);
+  void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+  void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+  void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+  void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+  const int IPV4 = 4;
+  const int IPV6 = 6;
+  const int CONF = 1;
+  const int NEIGH = 2;
+  const String IPSEC_INTERFACE_PREFIX = "ipsec";
+  const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+  const int IPV6_ADDR_GEN_MODE_NONE = 1;
+  const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+  const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+  const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+  const int PENALTY_POLICY_ACCEPT = 1;
+  const int PENALTY_POLICY_LOG = 2;
+  const int PENALTY_POLICY_REJECT = 3;
+  const int LOCAL_NET_ID = 99;
+  const String NEXTHOP_NONE = "";
+  const String NEXTHOP_UNREACHABLE = "unreachable";
+  const String NEXTHOP_THROW = "throw";
+  const int PERMISSION_NONE = 0;
+  const int PERMISSION_NETWORK = 1;
+  const int PERMISSION_SYSTEM = 2;
+  const int NO_PERMISSIONS = 0;
+  const int PERMISSION_INTERNET = 4;
+  const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+  const int PERMISSION_UNINSTALLED = -1;
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_BLACKLIST = 1;
+  const int FIREWALL_RULE_ALLOW = 1;
+  const int FIREWALL_RULE_DENY = 2;
+  const int FIREWALL_CHAIN_NONE = 0;
+  const int FIREWALL_CHAIN_DOZABLE = 1;
+  const int FIREWALL_CHAIN_STANDBY = 2;
+  const int FIREWALL_CHAIN_POWERSAVE = 3;
+  const String IF_STATE_UP = "up";
+  const String IF_STATE_DOWN = "down";
+  const String IF_FLAG_BROADCAST = "broadcast";
+  const String IF_FLAG_LOOPBACK = "loopback";
+  const String IF_FLAG_POINTOPOINT = "point-to-point";
+  const String IF_FLAG_RUNNING = "running";
+  const String IF_FLAG_MULTICAST = "multicast";
+}
diff --git a/server/aidl_api/netd_aidl_interface/1/android/net/INetdUnsolicitedEventListener.aidl b/server/aidl_api/netd_aidl_interface/1/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..18631ff
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/1/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,14 @@
+package android.net;
+interface INetdUnsolicitedEventListener {
+  oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+  oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+  oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+  oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAdded(@utf8InCpp String ifName);
+  oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+  oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+  oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/server/aidl_api/netd_aidl_interface/1/android/net/InterfaceConfigurationParcel.aidl b/server/aidl_api/netd_aidl_interface/1/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..93407dc
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/1/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,8 @@
+package android.net;
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/server/aidl_api/netd_aidl_interface/1/android/net/TetherStatsParcel.aidl b/server/aidl_api/netd_aidl_interface/1/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..d1782bb
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/1/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,8 @@
+package android.net;
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+}
diff --git a/server/aidl_api/netd_aidl_interface/1/android/net/UidRangeParcel.aidl b/server/aidl_api/netd_aidl_interface/1/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..d3bc7ed
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/1/android/net/UidRangeParcel.aidl
@@ -0,0 +1,5 @@
+package android.net;
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/server/aidl_api/netd_aidl_interface/2/.hash b/server/aidl_api/netd_aidl_interface/2/.hash
new file mode 100644
index 0000000..5fc5b2d
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/2/.hash
@@ -0,0 +1 @@
+e395d63302c47e7d2dac0d503045779029ff598b
diff --git a/server/aidl_api/netd_aidl_interface/2/android/net/INetd.aidl b/server/aidl_api/netd_aidl_interface/2/android/net/INetd.aidl
new file mode 100644
index 0000000..0e2d5f4
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/2/android/net/INetd.aidl
@@ -0,0 +1,153 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isWhitelist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  void networkCreatePhysical(int netId, int permission);
+  void networkCreateVpn(int netId, boolean secure);
+  void networkDestroy(int netId);
+  void networkAddInterface(int netId, in @utf8InCpp String iface);
+  void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+  void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+  void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+  boolean tetherApplyDnsInterfaces();
+  android.net.TetherStatsParcel[] tetherGetStats();
+  void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+  void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+  void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+  int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+  void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+  void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+  void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+  void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+  void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void strictUidCleartextPenalty(int uid, int policyPenalty);
+  @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+  void clatdStop(in @utf8InCpp String ifName);
+  boolean ipfwdEnabled();
+  @utf8InCpp String[] ipfwdGetRequesterList();
+  void ipfwdEnableForwarding(in @utf8InCpp String requester);
+  void ipfwdDisableForwarding(in @utf8InCpp String requester);
+  void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+  void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+  void bandwidthSetGlobalAlert(long bytes);
+  void bandwidthAddNaughtyApp(int uid);
+  void bandwidthRemoveNaughtyApp(int uid);
+  void bandwidthAddNiceApp(int uid);
+  void bandwidthRemoveNiceApp(int uid);
+  void tetherStart(in @utf8InCpp String[] dhcpRanges);
+  void tetherStop();
+  boolean tetherIsEnabled();
+  void tetherInterfaceAdd(in @utf8InCpp String ifName);
+  void tetherInterfaceRemove(in @utf8InCpp String ifName);
+  @utf8InCpp String[] tetherInterfaceList();
+  void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+  @utf8InCpp String[] tetherDnsList();
+  void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  int networkGetDefault();
+  void networkSetDefault(int netId);
+  void networkClearDefault();
+  void networkSetPermissionForNetwork(int netId, int permission);
+  void networkSetPermissionForUser(int permission, in int[] uids);
+  void networkClearPermissionForUser(in int[] uids);
+  void trafficSetNetPermForUids(int permission, in int[] uids);
+  void networkSetProtectAllow(int uid);
+  void networkSetProtectDeny(int uid);
+  boolean networkCanProtect(int uid);
+  void firewallSetFirewallType(int firewalltype);
+  void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+  void firewallSetUidRule(int childChain, int uid, int firewallRule);
+  void firewallEnableChildChain(int childChain, boolean enable);
+  @utf8InCpp String[] interfaceGetList();
+  android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+  void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+  void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+  void interfaceClearAddrs(in @utf8InCpp String ifName);
+  void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+  void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+  void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+  void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+  void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+  void firewallRemoveUidInterfaceRules(in int[] uids);
+  void trafficSwapActiveStatsMap();
+  IBinder getOemNetd();
+  const int IPV4 = 4;
+  const int IPV6 = 6;
+  const int CONF = 1;
+  const int NEIGH = 2;
+  const String IPSEC_INTERFACE_PREFIX = "ipsec";
+  const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+  const int IPV6_ADDR_GEN_MODE_NONE = 1;
+  const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+  const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+  const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+  const int PENALTY_POLICY_ACCEPT = 1;
+  const int PENALTY_POLICY_LOG = 2;
+  const int PENALTY_POLICY_REJECT = 3;
+  const int LOCAL_NET_ID = 99;
+  const String NEXTHOP_NONE = "";
+  const String NEXTHOP_UNREACHABLE = "unreachable";
+  const String NEXTHOP_THROW = "throw";
+  const int PERMISSION_NONE = 0;
+  const int PERMISSION_NETWORK = 1;
+  const int PERMISSION_SYSTEM = 2;
+  const int NO_PERMISSIONS = 0;
+  const int PERMISSION_INTERNET = 4;
+  const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+  const int PERMISSION_UNINSTALLED = -1;
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_BLACKLIST = 1;
+  const int FIREWALL_RULE_ALLOW = 1;
+  const int FIREWALL_RULE_DENY = 2;
+  const int FIREWALL_CHAIN_NONE = 0;
+  const int FIREWALL_CHAIN_DOZABLE = 1;
+  const int FIREWALL_CHAIN_STANDBY = 2;
+  const int FIREWALL_CHAIN_POWERSAVE = 3;
+  const String IF_STATE_UP = "up";
+  const String IF_STATE_DOWN = "down";
+  const String IF_FLAG_BROADCAST = "broadcast";
+  const String IF_FLAG_LOOPBACK = "loopback";
+  const String IF_FLAG_POINTOPOINT = "point-to-point";
+  const String IF_FLAG_RUNNING = "running";
+  const String IF_FLAG_MULTICAST = "multicast";
+}
diff --git a/server/aidl_api/netd_aidl_interface/2/android/net/INetdUnsolicitedEventListener.aidl b/server/aidl_api/netd_aidl_interface/2/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..621f1cf
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/2/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,31 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+interface INetdUnsolicitedEventListener {
+  oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+  oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+  oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+  oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAdded(@utf8InCpp String ifName);
+  oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+  oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+  oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/server/aidl_api/netd_aidl_interface/2/android/net/InterfaceConfigurationParcel.aidl b/server/aidl_api/netd_aidl_interface/2/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..18de61f
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/2/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/server/aidl_api/netd_aidl_interface/2/android/net/TetherStatsParcel.aidl b/server/aidl_api/netd_aidl_interface/2/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..c0ba676
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/2/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+}
diff --git a/server/aidl_api/netd_aidl_interface/2/android/net/UidRangeParcel.aidl b/server/aidl_api/netd_aidl_interface/2/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..c2c35db
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/2/android/net/UidRangeParcel.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/server/aidl_api/netd_aidl_interface/3/.hash b/server/aidl_api/netd_aidl_interface/3/.hash
new file mode 100644
index 0000000..59cf708
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/3/.hash
@@ -0,0 +1 @@
+e17c1f9b2068b539b22e3a4a447edea3c80aee4b
diff --git a/server/aidl_api/netd_aidl_interface/3/android/net/INetd.aidl b/server/aidl_api/netd_aidl_interface/3/android/net/INetd.aidl
new file mode 100644
index 0000000..135b738
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/3/android/net/INetd.aidl
@@ -0,0 +1,161 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isWhitelist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  void networkCreatePhysical(int netId, int permission);
+  void networkCreateVpn(int netId, boolean secure);
+  void networkDestroy(int netId);
+  void networkAddInterface(int netId, in @utf8InCpp String iface);
+  void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+  void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+  void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+  boolean tetherApplyDnsInterfaces();
+  android.net.TetherStatsParcel[] tetherGetStats();
+  void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+  void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+  void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+  int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+  void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+  void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+  void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+  void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+  void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void strictUidCleartextPenalty(int uid, int policyPenalty);
+  @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+  void clatdStop(in @utf8InCpp String ifName);
+  boolean ipfwdEnabled();
+  @utf8InCpp String[] ipfwdGetRequesterList();
+  void ipfwdEnableForwarding(in @utf8InCpp String requester);
+  void ipfwdDisableForwarding(in @utf8InCpp String requester);
+  void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+  void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+  void bandwidthSetGlobalAlert(long bytes);
+  void bandwidthAddNaughtyApp(int uid);
+  void bandwidthRemoveNaughtyApp(int uid);
+  void bandwidthAddNiceApp(int uid);
+  void bandwidthRemoveNiceApp(int uid);
+  void tetherStart(in @utf8InCpp String[] dhcpRanges);
+  void tetherStop();
+  boolean tetherIsEnabled();
+  void tetherInterfaceAdd(in @utf8InCpp String ifName);
+  void tetherInterfaceRemove(in @utf8InCpp String ifName);
+  @utf8InCpp String[] tetherInterfaceList();
+  void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+  @utf8InCpp String[] tetherDnsList();
+  void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  int networkGetDefault();
+  void networkSetDefault(int netId);
+  void networkClearDefault();
+  void networkSetPermissionForNetwork(int netId, int permission);
+  void networkSetPermissionForUser(int permission, in int[] uids);
+  void networkClearPermissionForUser(in int[] uids);
+  void trafficSetNetPermForUids(int permission, in int[] uids);
+  void networkSetProtectAllow(int uid);
+  void networkSetProtectDeny(int uid);
+  boolean networkCanProtect(int uid);
+  void firewallSetFirewallType(int firewalltype);
+  void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+  void firewallSetUidRule(int childChain, int uid, int firewallRule);
+  void firewallEnableChildChain(int childChain, boolean enable);
+  @utf8InCpp String[] interfaceGetList();
+  android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+  void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+  void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+  void interfaceClearAddrs(in @utf8InCpp String ifName);
+  void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+  void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+  void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+  void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+  void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+  void firewallRemoveUidInterfaceRules(in int[] uids);
+  void trafficSwapActiveStatsMap();
+  IBinder getOemNetd();
+  void tetherStartWithConfiguration(in android.net.TetherConfigParcel config);
+  android.net.MarkMaskParcel getFwmarkForNetwork(int netId);
+  void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule);
+  void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule);
+  const int IPV4 = 4;
+  const int IPV6 = 6;
+  const int CONF = 1;
+  const int NEIGH = 2;
+  const String IPSEC_INTERFACE_PREFIX = "ipsec";
+  const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+  const int IPV6_ADDR_GEN_MODE_NONE = 1;
+  const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+  const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+  const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+  const int PENALTY_POLICY_ACCEPT = 1;
+  const int PENALTY_POLICY_LOG = 2;
+  const int PENALTY_POLICY_REJECT = 3;
+  const int LOCAL_NET_ID = 99;
+  const String NEXTHOP_NONE = "";
+  const String NEXTHOP_UNREACHABLE = "unreachable";
+  const String NEXTHOP_THROW = "throw";
+  const int PERMISSION_NONE = 0;
+  const int PERMISSION_NETWORK = 1;
+  const int PERMISSION_SYSTEM = 2;
+  const int NO_PERMISSIONS = 0;
+  const int PERMISSION_INTERNET = 4;
+  const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+  const int PERMISSION_UNINSTALLED = -1;
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_BLACKLIST = 1;
+  const int FIREWALL_RULE_ALLOW = 1;
+  const int FIREWALL_RULE_DENY = 2;
+  const int FIREWALL_CHAIN_NONE = 0;
+  const int FIREWALL_CHAIN_DOZABLE = 1;
+  const int FIREWALL_CHAIN_STANDBY = 2;
+  const int FIREWALL_CHAIN_POWERSAVE = 3;
+  const String IF_STATE_UP = "up";
+  const String IF_STATE_DOWN = "down";
+  const String IF_FLAG_BROADCAST = "broadcast";
+  const String IF_FLAG_LOOPBACK = "loopback";
+  const String IF_FLAG_POINTOPOINT = "point-to-point";
+  const String IF_FLAG_RUNNING = "running";
+  const String IF_FLAG_MULTICAST = "multicast";
+}
diff --git a/server/aidl_api/netd_aidl_interface/3/android/net/INetdUnsolicitedEventListener.aidl b/server/aidl_api/netd_aidl_interface/3/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..4459363
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/3/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,32 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetdUnsolicitedEventListener {
+  oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+  oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+  oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+  oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAdded(@utf8InCpp String ifName);
+  oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+  oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+  oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/server/aidl_api/netd_aidl_interface/3/android/net/InterfaceConfigurationParcel.aidl b/server/aidl_api/netd_aidl_interface/3/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..01e0f95
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/3/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/server/aidl_api/netd_aidl_interface/3/android/net/MarkMaskParcel.aidl b/server/aidl_api/netd_aidl_interface/3/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..62be838
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/3/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable MarkMaskParcel {
+  int mark;
+  int mask;
+}
diff --git a/server/aidl_api/netd_aidl_interface/3/android/net/RouteInfoParcel.aidl b/server/aidl_api/netd_aidl_interface/3/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..5e0ee62
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/3/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable RouteInfoParcel {
+  @utf8InCpp String destination;
+  @utf8InCpp String ifName;
+  @utf8InCpp String nextHop;
+  int mtu;
+}
diff --git a/server/aidl_api/netd_aidl_interface/3/android/net/TetherConfigParcel.aidl b/server/aidl_api/netd_aidl_interface/3/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..b136454
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/3/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherConfigParcel {
+  boolean usingLegacyDnsProxy;
+  @utf8InCpp String[] dhcpRanges;
+}
diff --git a/server/aidl_api/netd_aidl_interface/3/android/net/TetherOffloadRuleParcel.aidl b/server/aidl_api/netd_aidl_interface/3/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..3abf0f8
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/3/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherOffloadRuleParcel {
+  int inputInterfaceIndex;
+  int outputInterfaceIndex;
+  byte[] destination;
+  int prefixLength;
+  byte[] srcL2Address;
+  byte[] dstL2Address;
+}
diff --git a/server/aidl_api/netd_aidl_interface/3/android/net/TetherStatsParcel.aidl b/server/aidl_api/netd_aidl_interface/3/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..71ffb9b
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/3/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+}
diff --git a/server/aidl_api/netd_aidl_interface/3/android/net/UidRangeParcel.aidl b/server/aidl_api/netd_aidl_interface/3/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..84ff457
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/3/android/net/UidRangeParcel.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/server/aidl_api/netd_aidl_interface/4/.hash b/server/aidl_api/netd_aidl_interface/4/.hash
new file mode 100644
index 0000000..0c3f810
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/4/.hash
@@ -0,0 +1 @@
+63adaa5098e4d8621e90c5a84f7cb93505c79311
diff --git a/server/aidl_api/netd_aidl_interface/4/android/net/INetd.aidl b/server/aidl_api/netd_aidl_interface/4/android/net/INetd.aidl
new file mode 100644
index 0000000..47e2931
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/4/android/net/INetd.aidl
@@ -0,0 +1,164 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isWhitelist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  void networkCreatePhysical(int netId, int permission);
+  void networkCreateVpn(int netId, boolean secure);
+  void networkDestroy(int netId);
+  void networkAddInterface(int netId, in @utf8InCpp String iface);
+  void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+  void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+  void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+  boolean tetherApplyDnsInterfaces();
+  android.net.TetherStatsParcel[] tetherGetStats();
+  void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+  void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+  void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+  int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+  void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+  void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+  void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+  void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+  void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void strictUidCleartextPenalty(int uid, int policyPenalty);
+  @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+  void clatdStop(in @utf8InCpp String ifName);
+  boolean ipfwdEnabled();
+  @utf8InCpp String[] ipfwdGetRequesterList();
+  void ipfwdEnableForwarding(in @utf8InCpp String requester);
+  void ipfwdDisableForwarding(in @utf8InCpp String requester);
+  void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+  void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+  void bandwidthSetGlobalAlert(long bytes);
+  void bandwidthAddNaughtyApp(int uid);
+  void bandwidthRemoveNaughtyApp(int uid);
+  void bandwidthAddNiceApp(int uid);
+  void bandwidthRemoveNiceApp(int uid);
+  void tetherStart(in @utf8InCpp String[] dhcpRanges);
+  void tetherStop();
+  boolean tetherIsEnabled();
+  void tetherInterfaceAdd(in @utf8InCpp String ifName);
+  void tetherInterfaceRemove(in @utf8InCpp String ifName);
+  @utf8InCpp String[] tetherInterfaceList();
+  void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+  @utf8InCpp String[] tetherDnsList();
+  void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  int networkGetDefault();
+  void networkSetDefault(int netId);
+  void networkClearDefault();
+  void networkSetPermissionForNetwork(int netId, int permission);
+  void networkSetPermissionForUser(int permission, in int[] uids);
+  void networkClearPermissionForUser(in int[] uids);
+  void trafficSetNetPermForUids(int permission, in int[] uids);
+  void networkSetProtectAllow(int uid);
+  void networkSetProtectDeny(int uid);
+  boolean networkCanProtect(int uid);
+  void firewallSetFirewallType(int firewalltype);
+  void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+  void firewallSetUidRule(int childChain, int uid, int firewallRule);
+  void firewallEnableChildChain(int childChain, boolean enable);
+  @utf8InCpp String[] interfaceGetList();
+  android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+  void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+  void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+  void interfaceClearAddrs(in @utf8InCpp String ifName);
+  void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+  void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+  void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+  void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+  void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+  void firewallRemoveUidInterfaceRules(in int[] uids);
+  void trafficSwapActiveStatsMap();
+  IBinder getOemNetd();
+  void tetherStartWithConfiguration(in android.net.TetherConfigParcel config);
+  android.net.MarkMaskParcel getFwmarkForNetwork(int netId);
+  void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule);
+  void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule);
+  android.net.TetherStatsParcel[] tetherOffloadGetStats();
+  void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
+  android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex);
+  const int IPV4 = 4;
+  const int IPV6 = 6;
+  const int CONF = 1;
+  const int NEIGH = 2;
+  const String IPSEC_INTERFACE_PREFIX = "ipsec";
+  const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+  const int IPV6_ADDR_GEN_MODE_NONE = 1;
+  const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+  const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+  const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+  const int PENALTY_POLICY_ACCEPT = 1;
+  const int PENALTY_POLICY_LOG = 2;
+  const int PENALTY_POLICY_REJECT = 3;
+  const int LOCAL_NET_ID = 99;
+  const String NEXTHOP_NONE = "";
+  const String NEXTHOP_UNREACHABLE = "unreachable";
+  const String NEXTHOP_THROW = "throw";
+  const int PERMISSION_NONE = 0;
+  const int PERMISSION_NETWORK = 1;
+  const int PERMISSION_SYSTEM = 2;
+  const int NO_PERMISSIONS = 0;
+  const int PERMISSION_INTERNET = 4;
+  const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+  const int PERMISSION_UNINSTALLED = -1;
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_BLACKLIST = 1;
+  const int FIREWALL_RULE_ALLOW = 1;
+  const int FIREWALL_RULE_DENY = 2;
+  const int FIREWALL_CHAIN_NONE = 0;
+  const int FIREWALL_CHAIN_DOZABLE = 1;
+  const int FIREWALL_CHAIN_STANDBY = 2;
+  const int FIREWALL_CHAIN_POWERSAVE = 3;
+  const String IF_STATE_UP = "up";
+  const String IF_STATE_DOWN = "down";
+  const String IF_FLAG_BROADCAST = "broadcast";
+  const String IF_FLAG_LOOPBACK = "loopback";
+  const String IF_FLAG_POINTOPOINT = "point-to-point";
+  const String IF_FLAG_RUNNING = "running";
+  const String IF_FLAG_MULTICAST = "multicast";
+}
diff --git a/server/aidl_api/netd_aidl_interface/4/android/net/INetdUnsolicitedEventListener.aidl b/server/aidl_api/netd_aidl_interface/4/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..4459363
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/4/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,32 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetdUnsolicitedEventListener {
+  oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+  oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+  oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+  oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAdded(@utf8InCpp String ifName);
+  oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+  oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+  oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/server/aidl_api/netd_aidl_interface/4/android/net/InterfaceConfigurationParcel.aidl b/server/aidl_api/netd_aidl_interface/4/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..01e0f95
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/4/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/server/aidl_api/netd_aidl_interface/4/android/net/MarkMaskParcel.aidl b/server/aidl_api/netd_aidl_interface/4/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..62be838
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/4/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable MarkMaskParcel {
+  int mark;
+  int mask;
+}
diff --git a/server/aidl_api/netd_aidl_interface/4/android/net/RouteInfoParcel.aidl b/server/aidl_api/netd_aidl_interface/4/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..5e0ee62
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/4/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable RouteInfoParcel {
+  @utf8InCpp String destination;
+  @utf8InCpp String ifName;
+  @utf8InCpp String nextHop;
+  int mtu;
+}
diff --git a/server/aidl_api/netd_aidl_interface/4/android/net/TetherConfigParcel.aidl b/server/aidl_api/netd_aidl_interface/4/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..b136454
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/4/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherConfigParcel {
+  boolean usingLegacyDnsProxy;
+  @utf8InCpp String[] dhcpRanges;
+}
diff --git a/server/aidl_api/netd_aidl_interface/4/android/net/TetherOffloadRuleParcel.aidl b/server/aidl_api/netd_aidl_interface/4/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..c9d8458
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/4/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,28 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherOffloadRuleParcel {
+  int inputInterfaceIndex;
+  int outputInterfaceIndex;
+  byte[] destination;
+  int prefixLength;
+  byte[] srcL2Address;
+  byte[] dstL2Address;
+  int pmtu = 1500;
+}
diff --git a/server/aidl_api/netd_aidl_interface/4/android/net/TetherStatsParcel.aidl b/server/aidl_api/netd_aidl_interface/4/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..0b0960e
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/4/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+  int ifIndex = 0;
+}
diff --git a/server/aidl_api/netd_aidl_interface/4/android/net/UidRangeParcel.aidl b/server/aidl_api/netd_aidl_interface/4/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..84ff457
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/4/android/net/UidRangeParcel.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/server/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl b/server/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl
new file mode 100644
index 0000000..47e2931
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl
@@ -0,0 +1,164 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isWhitelist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  void networkCreatePhysical(int netId, int permission);
+  void networkCreateVpn(int netId, boolean secure);
+  void networkDestroy(int netId);
+  void networkAddInterface(int netId, in @utf8InCpp String iface);
+  void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+  void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+  void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+  boolean tetherApplyDnsInterfaces();
+  android.net.TetherStatsParcel[] tetherGetStats();
+  void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+  void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+  void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+  int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+  void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+  void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+  void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+  void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+  void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void strictUidCleartextPenalty(int uid, int policyPenalty);
+  @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+  void clatdStop(in @utf8InCpp String ifName);
+  boolean ipfwdEnabled();
+  @utf8InCpp String[] ipfwdGetRequesterList();
+  void ipfwdEnableForwarding(in @utf8InCpp String requester);
+  void ipfwdDisableForwarding(in @utf8InCpp String requester);
+  void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+  void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+  void bandwidthSetGlobalAlert(long bytes);
+  void bandwidthAddNaughtyApp(int uid);
+  void bandwidthRemoveNaughtyApp(int uid);
+  void bandwidthAddNiceApp(int uid);
+  void bandwidthRemoveNiceApp(int uid);
+  void tetherStart(in @utf8InCpp String[] dhcpRanges);
+  void tetherStop();
+  boolean tetherIsEnabled();
+  void tetherInterfaceAdd(in @utf8InCpp String ifName);
+  void tetherInterfaceRemove(in @utf8InCpp String ifName);
+  @utf8InCpp String[] tetherInterfaceList();
+  void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+  @utf8InCpp String[] tetherDnsList();
+  void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  int networkGetDefault();
+  void networkSetDefault(int netId);
+  void networkClearDefault();
+  void networkSetPermissionForNetwork(int netId, int permission);
+  void networkSetPermissionForUser(int permission, in int[] uids);
+  void networkClearPermissionForUser(in int[] uids);
+  void trafficSetNetPermForUids(int permission, in int[] uids);
+  void networkSetProtectAllow(int uid);
+  void networkSetProtectDeny(int uid);
+  boolean networkCanProtect(int uid);
+  void firewallSetFirewallType(int firewalltype);
+  void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+  void firewallSetUidRule(int childChain, int uid, int firewallRule);
+  void firewallEnableChildChain(int childChain, boolean enable);
+  @utf8InCpp String[] interfaceGetList();
+  android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+  void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+  void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+  void interfaceClearAddrs(in @utf8InCpp String ifName);
+  void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+  void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+  void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+  void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+  void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+  void firewallRemoveUidInterfaceRules(in int[] uids);
+  void trafficSwapActiveStatsMap();
+  IBinder getOemNetd();
+  void tetherStartWithConfiguration(in android.net.TetherConfigParcel config);
+  android.net.MarkMaskParcel getFwmarkForNetwork(int netId);
+  void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule);
+  void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule);
+  android.net.TetherStatsParcel[] tetherOffloadGetStats();
+  void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
+  android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex);
+  const int IPV4 = 4;
+  const int IPV6 = 6;
+  const int CONF = 1;
+  const int NEIGH = 2;
+  const String IPSEC_INTERFACE_PREFIX = "ipsec";
+  const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+  const int IPV6_ADDR_GEN_MODE_NONE = 1;
+  const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+  const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+  const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+  const int PENALTY_POLICY_ACCEPT = 1;
+  const int PENALTY_POLICY_LOG = 2;
+  const int PENALTY_POLICY_REJECT = 3;
+  const int LOCAL_NET_ID = 99;
+  const String NEXTHOP_NONE = "";
+  const String NEXTHOP_UNREACHABLE = "unreachable";
+  const String NEXTHOP_THROW = "throw";
+  const int PERMISSION_NONE = 0;
+  const int PERMISSION_NETWORK = 1;
+  const int PERMISSION_SYSTEM = 2;
+  const int NO_PERMISSIONS = 0;
+  const int PERMISSION_INTERNET = 4;
+  const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+  const int PERMISSION_UNINSTALLED = -1;
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_BLACKLIST = 1;
+  const int FIREWALL_RULE_ALLOW = 1;
+  const int FIREWALL_RULE_DENY = 2;
+  const int FIREWALL_CHAIN_NONE = 0;
+  const int FIREWALL_CHAIN_DOZABLE = 1;
+  const int FIREWALL_CHAIN_STANDBY = 2;
+  const int FIREWALL_CHAIN_POWERSAVE = 3;
+  const String IF_STATE_UP = "up";
+  const String IF_STATE_DOWN = "down";
+  const String IF_FLAG_BROADCAST = "broadcast";
+  const String IF_FLAG_LOOPBACK = "loopback";
+  const String IF_FLAG_POINTOPOINT = "point-to-point";
+  const String IF_FLAG_RUNNING = "running";
+  const String IF_FLAG_MULTICAST = "multicast";
+}
diff --git a/server/aidl_api/netd_aidl_interface/current/android/net/INetdUnsolicitedEventListener.aidl b/server/aidl_api/netd_aidl_interface/current/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..4459363
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/current/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,32 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetdUnsolicitedEventListener {
+  oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+  oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+  oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+  oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAdded(@utf8InCpp String ifName);
+  oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+  oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+  oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/server/aidl_api/netd_aidl_interface/current/android/net/InterfaceConfigurationParcel.aidl b/server/aidl_api/netd_aidl_interface/current/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..01e0f95
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/current/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/server/aidl_api/netd_aidl_interface/current/android/net/MarkMaskParcel.aidl b/server/aidl_api/netd_aidl_interface/current/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..62be838
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/current/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable MarkMaskParcel {
+  int mark;
+  int mask;
+}
diff --git a/server/aidl_api/netd_aidl_interface/current/android/net/RouteInfoParcel.aidl b/server/aidl_api/netd_aidl_interface/current/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..5e0ee62
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/current/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable RouteInfoParcel {
+  @utf8InCpp String destination;
+  @utf8InCpp String ifName;
+  @utf8InCpp String nextHop;
+  int mtu;
+}
diff --git a/server/aidl_api/netd_aidl_interface/current/android/net/TetherConfigParcel.aidl b/server/aidl_api/netd_aidl_interface/current/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..b136454
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/current/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherConfigParcel {
+  boolean usingLegacyDnsProxy;
+  @utf8InCpp String[] dhcpRanges;
+}
diff --git a/server/aidl_api/netd_aidl_interface/current/android/net/TetherOffloadRuleParcel.aidl b/server/aidl_api/netd_aidl_interface/current/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..c9d8458
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/current/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,28 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherOffloadRuleParcel {
+  int inputInterfaceIndex;
+  int outputInterfaceIndex;
+  byte[] destination;
+  int prefixLength;
+  byte[] srcL2Address;
+  byte[] dstL2Address;
+  int pmtu = 1500;
+}
diff --git a/server/aidl_api/netd_aidl_interface/current/android/net/TetherStatsParcel.aidl b/server/aidl_api/netd_aidl_interface/current/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..0b0960e
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/current/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+  int ifIndex = 0;
+}
diff --git a/server/aidl_api/netd_aidl_interface/current/android/net/UidRangeParcel.aidl b/server/aidl_api/netd_aidl_interface/current/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..84ff457
--- /dev/null
+++ b/server/aidl_api/netd_aidl_interface/current/android/net/UidRangeParcel.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/server/aidl_api/netd_event_listener_interface/1/.hash b/server/aidl_api/netd_event_listener_interface/1/.hash
new file mode 100644
index 0000000..f39f730
--- /dev/null
+++ b/server/aidl_api/netd_event_listener_interface/1/.hash
@@ -0,0 +1 @@
+8e27594d285ca7c567d87e8cf74766c27647e02b
diff --git a/server/aidl_api/netd_event_listener_interface/1/android/net/metrics/INetdEventListener.aidl b/server/aidl_api/netd_event_listener_interface/1/android/net/metrics/INetdEventListener.aidl
new file mode 100644
index 0000000..9898a67
--- /dev/null
+++ b/server/aidl_api/netd_event_listener_interface/1/android/net/metrics/INetdEventListener.aidl
@@ -0,0 +1,34 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.metrics;
+interface INetdEventListener {
+  oneway void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs, @utf8InCpp String hostname, in @utf8InCpp String[] ipAddresses, int ipAddressesCount, int uid);
+  oneway void onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname, boolean validated);
+  oneway void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, int uid);
+  oneway void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, in byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs);
+  oneway void onTcpSocketStatsEvent(in int[] networkIds, in int[] sentPackets, in int[] lostPackets, in int[] rttUs, in int[] sentAckDiffMs);
+  oneway void onNat64PrefixEvent(int netId, boolean added, @utf8InCpp String prefixString, int prefixLength);
+  const int EVENT_GETADDRINFO = 1;
+  const int EVENT_GETHOSTBYNAME = 2;
+  const int EVENT_GETHOSTBYADDR = 3;
+  const int EVENT_RES_NSEND = 4;
+  const int REPORTING_LEVEL_NONE = 0;
+  const int REPORTING_LEVEL_METRICS = 1;
+  const int REPORTING_LEVEL_FULL = 2;
+  const int DNS_REPORTED_IP_ADDRESSES_LIMIT = 10;
+}
diff --git a/server/aidl_api/netd_event_listener_interface/current/android/net/metrics/INetdEventListener.aidl b/server/aidl_api/netd_event_listener_interface/current/android/net/metrics/INetdEventListener.aidl
new file mode 100644
index 0000000..d71c3f2
--- /dev/null
+++ b/server/aidl_api/netd_event_listener_interface/current/android/net/metrics/INetdEventListener.aidl
@@ -0,0 +1,35 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.metrics;
+/* @hide */
+interface INetdEventListener {
+  oneway void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs, @utf8InCpp String hostname, in @utf8InCpp String[] ipAddresses, int ipAddressesCount, int uid);
+  oneway void onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname, boolean validated);
+  oneway void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, int uid);
+  oneway void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, in byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs);
+  oneway void onTcpSocketStatsEvent(in int[] networkIds, in int[] sentPackets, in int[] lostPackets, in int[] rttUs, in int[] sentAckDiffMs);
+  oneway void onNat64PrefixEvent(int netId, boolean added, @utf8InCpp String prefixString, int prefixLength);
+  const int EVENT_GETADDRINFO = 1;
+  const int EVENT_GETHOSTBYNAME = 2;
+  const int EVENT_GETHOSTBYADDR = 3;
+  const int EVENT_RES_NSEND = 4;
+  const int REPORTING_LEVEL_NONE = 0;
+  const int REPORTING_LEVEL_METRICS = 1;
+  const int REPORTING_LEVEL_FULL = 2;
+  const int DNS_REPORTED_IP_ADDRESSES_LIMIT = 10;
+}
diff --git a/server/binder/android/net/INetd.aidl b/server/binder/android/net/INetd.aidl
new file mode 100644
index 0000000..3b221cf
--- /dev/null
+++ b/server/binder/android/net/INetd.aidl
@@ -0,0 +1,1312 @@
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.INetdUnsolicitedEventListener;
+import android.net.InterfaceConfigurationParcel;
+import android.net.MarkMaskParcel;
+import android.net.RouteInfoParcel;
+import android.net.TetherConfigParcel;
+import android.net.TetherOffloadRuleParcel;
+import android.net.TetherStatsParcel;
+import android.net.UidRangeParcel;
+
+/** {@hide} */
+interface INetd {
+    /**
+     * Returns true if the service is responding.
+     */
+    boolean isAlive();
+
+    /**
+     * Replaces the contents of the specified UID-based firewall chain.
+     *
+     * The chain may be a whitelist chain or a blacklist chain. A blacklist chain contains DROP
+     * rules for the specified UIDs and a RETURN rule at the end. A whitelist chain contains RETURN
+     * rules for the system UID range (0 to {@code UID_APP} - 1), RETURN rules for for the specified
+     * UIDs, and a DROP rule at the end. The chain will be created if it does not exist.
+     *
+     * @param chainName The name of the chain to replace.
+     * @param isWhitelist Whether this is a whitelist or blacklist chain.
+     * @param uids The list of UIDs to allow/deny.
+     * @return true if the chain was successfully replaced, false otherwise.
+     */
+    boolean firewallReplaceUidChain(in @utf8InCpp String chainName,
+                                    boolean isWhitelist,
+                                    in int[] uids);
+
+    /**
+     * Enables or disables data saver mode on costly network interfaces.
+     *
+     * - When disabled, all packets to/from apps in the penalty box chain are rejected on costly
+     *   interfaces. Traffic to/from other apps or on other network interfaces is allowed.
+     * - When enabled, only apps that are in the happy box chain and not in the penalty box chain
+     *   are allowed network connectivity on costly interfaces. All other packets on these
+     *   interfaces are rejected. The happy box chain always contains all system UIDs; to disallow
+     *   traffic from system UIDs, place them in the penalty box chain.
+     *
+     * By default, data saver mode is disabled. This command has no effect but might still return an
+     * error) if {@code enable} is the same as the current value.
+     *
+     * @param enable whether to enable or disable data saver mode.
+     * @return true if the if the operation was successful, false otherwise.
+     */
+    boolean bandwidthEnableDataSaver(boolean enable);
+
+    /**
+     * Creates a physical network (i.e., one containing physical interfaces.
+     *
+     * @param netId the networkId to create.
+     * @param permission the permission necessary to use the network. Must be one of
+     *         PERMISSION_NONE/PERMISSION_NETWORK/PERMISSION_SYSTEM.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void networkCreatePhysical(int netId, int permission);
+
+    /**
+     * Creates a VPN network.
+     *
+     * @param netId the network to create.
+     * @param secure whether unprivileged apps are allowed to bypass the VPN.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void networkCreateVpn(int netId, boolean secure);
+
+    /**
+     * Destroys a network. Any interfaces added to the network are removed, and the network ceases
+     * to be the default network.
+     *
+     * @param netId the network to destroy.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void networkDestroy(int netId);
+
+    /**
+     * Adds an interface to a network. The interface must not be assigned to any network, including
+     * the specified network.
+     *
+     * @param netId the network to add the interface to.
+     * @param interface the name of the interface to add.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void networkAddInterface(int netId, in @utf8InCpp String iface);
+
+    /**
+     * Adds an interface to a network. The interface must be assigned to the specified network.
+     *
+     * @param netId the network to remove the interface from.
+     * @param interface the name of the interface to remove.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+
+    /**
+     * Adds the specified UID ranges to the specified network. The network must be a VPN. Traffic
+     * from the UID ranges will be routed through the VPN.
+     *
+     * @param netId the network ID of the network to add the ranges to.
+     * @param uidRanges a set of non-overlapping, contiguous ranges of UIDs to add. The ranges
+     *        must not overlap with existing ranges routed to this network.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void networkAddUidRanges(int netId, in UidRangeParcel[] uidRanges);
+
+    /**
+     * Adds the specified UID ranges to the specified network. The network must be a VPN. Traffic
+     * from the UID ranges will no longer be routed through the VPN.
+     *
+     * @param netId the network ID of the network to remove the ranges from.
+     * @param uidRanges a set of non-overlapping, contiguous ranges of UIDs to add. The ranges
+     *        must already be routed to this network.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void networkRemoveUidRanges(int netId, in UidRangeParcel[] uidRanges);
+
+    /**
+     * Adds or removes one rule for each supplied UID range to prohibit all network activity outside
+     * of secure VPN.
+     *
+     * When a UID is covered by one of these rules, traffic sent through any socket that is not
+     * protected or explicitly overriden by the system will be rejected. The kernel will respond
+     * with an ICMP prohibit message.
+     *
+     * Initially, there are no such rules. Any rules that are added will only last until the next
+     * restart of netd or the device.
+     *
+     * @param add {@code true} if the specified UID ranges should be denied access to any network
+     *        which is not secure VPN by adding rules, {@code false} to remove existing rules.
+     * @param uidRanges a set of non-overlapping, contiguous ranges of UIDs to which to apply or
+     *        remove this restriction.
+     *        <p> Added rules should not overlap with existing rules. Likewise, removed rules should
+     *        each correspond to an existing rule.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void networkRejectNonSecureVpn(boolean add, in UidRangeParcel[] uidRanges);
+
+    /**
+     * Administratively closes sockets belonging to the specified UIDs.
+     */
+    void socketDestroy(in UidRangeParcel[] uidRanges, in int[] exemptUids);
+
+    /**
+     * Instruct the tethering DNS server to reevaluated serving interfaces.
+     * This is needed to for the DNS server to observe changes in the set
+     * of potential listening IP addresses. (Listening on wildcard addresses
+     * can turn the device into an open resolver; b/7530468)
+     *
+     * TODO: Return something richer than just a boolean.
+     */
+    boolean tetherApplyDnsInterfaces();
+
+    /**
+     * Return tethering statistics.
+     *
+     * @return an array of TetherStatsParcel, where each entry contains the upstream interface
+     *         name and its tethering statistics since netd startup.
+     *         There will only ever be one entry for a given interface.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *         cause of the failure.
+     */
+    TetherStatsParcel[] tetherGetStats();
+
+    /**
+     * Add/Remove and IP address from an interface.
+     *
+     * @param ifName the interface name
+     * @param addrString the IP address to add/remove as a string literal
+     * @param prefixLength the prefix length associated with this IP address
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString,
+            int prefixLength);
+    void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString,
+            int prefixLength);
+
+    /**
+     * Set and get /proc/sys/net interface configuration parameters.
+     *
+     * @param ipversion One of IPV4/IPV6 integers, indicating the desired IP version directory.
+     * @param which One of CONF/NEIGH integers, indicating the desired parameter category directory.
+     * @param ifname The interface name portion of the path; may also be "all" or "default".
+     * @param parameter The parameter name portion of the path.
+     * @param value The value string to be written into the assembled path.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+
+    const int IPV4  = 4;
+    const int IPV6  = 6;
+    const int CONF  = 1;
+    const int NEIGH = 2;
+    @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname,
+            in @utf8InCpp String parameter);
+    void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname,
+            in @utf8InCpp String parameter, in @utf8InCpp String value);
+
+   /**
+    * Sets owner of socket ParcelFileDescriptor to the new UID, checking to ensure that the caller's
+    * uid is that of the old owner's, and that this is a UDP-encap socket
+    *
+    * @param ParcelFileDescriptor socket Socket file descriptor
+    * @param int newUid UID of the new socket fd owner
+    */
+    void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+
+   /**
+    * Reserve an SPI from the kernel
+    *
+    * @param transformId a unique identifier for allocated resources
+    * @param sourceAddress InetAddress as string for the sending endpoint
+    * @param destinationAddress InetAddress as string for the receiving endpoint
+    * @param spi a requested 32-bit unique ID or 0 to request random allocation
+    * @return the SPI that was allocated or 0 if failed
+    */
+    int ipSecAllocateSpi(
+            int transformId,
+            in @utf8InCpp String sourceAddress,
+            in @utf8InCpp String destinationAddress,
+            int spi);
+
+   /**
+    * Create an IpSec Security Association describing how ip(v6) traffic will be encrypted
+    * or decrypted.
+    *
+    * @param transformId a unique identifier for allocated resources
+    * @param mode either Transport or Tunnel mode
+    * @param sourceAddress InetAddress as string for the sending endpoint
+    * @param destinationAddress InetAddress as string for the receiving endpoint
+    * @param underlyingNetId the netId of the network to which the SA is applied. Only accepted for
+    *        tunnel mode SAs.
+    * @param spi a 32-bit unique ID allocated to the user
+    * @param markValue a 32-bit unique ID chosen by the user
+    * @param markMask a 32-bit mask chosen by the user
+    * @param authAlgo a string identifying the authentication algorithm to be used
+    * @param authKey a byte array containing the authentication key
+    * @param authTruncBits the truncation length of the MAC produced by the authentication algorithm
+    * @param cryptAlgo a string identifying the encryption algorithm to be used
+    * @param cryptKey a byte arrray containing the encryption key
+    * @param cryptTruncBits unused parameter
+    * @param aeadAlgo a string identifying the authenticated encryption algorithm to be used
+    * @param aeadKey a byte arrray containing the key to be used in authenticated encryption
+    * @param aeadIcvBits the truncation length of the ICV produced by the authentication algorithm
+    *        (similar to authTruncBits in function)
+    * @param encapType encapsulation type used (if any) for the udp encap socket
+    * @param encapLocalPort the port number on the host to be used in encap packets
+    * @param encapRemotePort the port number of the remote to be used for encap packets
+    * @param interfaceId the identifier for the IPsec tunnel interface.
+    *        Only accepted for tunnel mode SAs.
+    */
+    void ipSecAddSecurityAssociation(
+            int transformId,
+            int mode,
+            in @utf8InCpp String sourceAddress,
+            in @utf8InCpp String destinationAddress,
+            int underlyingNetId,
+            int spi,
+            int markValue,
+            int markMask,
+            in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits,
+            in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits,
+            in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits,
+            int encapType,
+            int encapLocalPort,
+            int encapRemotePort,
+            int interfaceId);
+
+   /**
+    * Delete a previously created security association identified by the provided parameters
+    *
+    * @param transformId a unique identifier for allocated resources
+    * @param sourceAddress InetAddress as string for the sending endpoint
+    * @param destinationAddress InetAddress as string for the receiving endpoint
+    * @param spi a requested 32-bit unique ID allocated to the user
+    * @param markValue a 32-bit unique ID chosen by the user
+    * @param markMask a 32-bit mask chosen by the user
+    * @param interfaceId the identifier for the IPsec tunnel interface.
+    */
+    void ipSecDeleteSecurityAssociation(
+            int transformId,
+            in @utf8InCpp String sourceAddress,
+            in @utf8InCpp String destinationAddress,
+            int spi,
+            int markValue,
+            int markMask,
+            int interfaceId);
+
+   /**
+    * Apply a previously created SA to a specified socket, starting IPsec on that socket
+    *
+    * @param socket a user-provided socket that will have IPsec applied
+    * @param transformId a unique identifier for allocated resources
+    * @param direction DIRECTION_IN or DIRECTION_OUT
+    * @param sourceAddress InetAddress as string for the sending endpoint
+    * @param destinationAddress InetAddress as string for the receiving endpoint
+    * @param spi a 32-bit unique ID allocated to the user (socket owner)
+    */
+    void ipSecApplyTransportModeTransform(
+            in ParcelFileDescriptor socket,
+            int transformId,
+            int direction,
+            in @utf8InCpp String sourceAddress,
+            in @utf8InCpp String destinationAddress,
+            int spi);
+
+   /**
+    * Remove an IPsec SA from a given socket. This will allow unencrypted traffic to flow
+    * on that socket if a transform had been previously applied.
+    *
+    * @param socket a user-provided socket from which to remove any IPsec configuration
+    */
+    void ipSecRemoveTransportModeTransform(
+            in ParcelFileDescriptor socket);
+
+   /**
+    * Adds an IPsec global policy.
+    *
+    * @param transformId a unique identifier for allocated resources
+    * @param selAddrFamily the address family identifier for the selector
+    * @param direction DIRECTION_IN or DIRECTION_OUT
+    * @param tmplSrcAddress InetAddress as string for the sending endpoint
+    * @param tmplDstAddress InetAddress as string for the receiving endpoint
+    * @param spi a 32-bit unique ID allocated to the user
+    * @param markValue a 32-bit unique ID chosen by the user
+    * @param markMask a 32-bit mask chosen by the user
+    * @param interfaceId the identifier for the IPsec tunnel interface.
+    */
+    void ipSecAddSecurityPolicy(
+            int transformId,
+            int selAddrFamily,
+            int direction,
+            in @utf8InCpp String tmplSrcAddress,
+            in @utf8InCpp String tmplDstAddress,
+            int spi,
+            int markValue,
+            int markMask,
+            int interfaceId);
+
+   /**
+    * Updates an IPsec global policy.
+    *
+    * @param transformId a unique identifier for allocated resources
+    * @param selAddrFamily the address family identifier for the selector
+    * @param direction DIRECTION_IN or DIRECTION_OUT
+    * @param tmplSrcAddress InetAddress as string for the sending endpoint
+    * @param tmplDstAddress InetAddress as string for the receiving endpoint
+    * @param spi a 32-bit unique ID allocated to the user
+    * @param markValue a 32-bit unique ID chosen by the user
+    * @param markMask a 32-bit mask chosen by the user
+    * @param interfaceId the identifier for the IPsec tunnel interface.
+    */
+    void ipSecUpdateSecurityPolicy(
+            int transformId,
+            int selAddrFamily,
+            int direction,
+            in @utf8InCpp String tmplSrcAddress,
+            in @utf8InCpp String tmplDstAddress,
+            int spi,
+            int markValue,
+            int markMask,
+            int interfaceId);
+
+   /**
+    * Deletes an IPsec global policy.
+    *
+    * Deletion of global policies does not do any matching based on the templates, thus
+    * template source/destination addresses are not needed (as opposed to add/update).
+    *
+    * @param transformId a unique identifier for allocated resources
+    * @param selAddrFamily the address family identifier for the selector
+    * @param direction DIRECTION_IN or DIRECTION_OUT
+    * @param markValue a 32-bit unique ID chosen by the user
+    * @param markMask a 32-bit mask chosen by the user
+    * @param interfaceId the identifier for the IPsec tunnel interface.
+    */
+    void ipSecDeleteSecurityPolicy(
+            int transformId,
+            int selAddrFamily,
+            int direction,
+            int markValue,
+            int markMask,
+            int interfaceId);
+
+    // This could not be declared as @uft8InCpp; thus, when used in native code it must be
+    // converted from a UTF-16 string to an ASCII string.
+    const String IPSEC_INTERFACE_PREFIX = "ipsec";
+
+   /**
+    * Add a IPsec Tunnel Interface.
+    *
+    * @param devName a unique identifier that represents the name of the device
+    * @param localAddress InetAddress as string for the local endpoint
+    * @param remoteAddress InetAddress as string for the remote endpoint
+    * @param iKey, to match Policies and SAs for input packets.
+    * @param oKey, to match Policies and SAs for output packets.
+    * @param interfaceId the identifier for the IPsec tunnel interface.
+    */
+    void ipSecAddTunnelInterface(
+            in @utf8InCpp String deviceName,
+            in @utf8InCpp String localAddress,
+            in @utf8InCpp String remoteAddress,
+            int iKey,
+            int oKey,
+            int interfaceId);
+
+   /**
+    * Update a IPsec Tunnel Interface.
+    *
+    * @param devName a unique identifier that represents the name of the device
+    * @param localAddress InetAddress as string for the local endpoint
+    * @param remoteAddress InetAddress as string for the remote endpoint
+    * @param iKey, to match Policies and SAs for input packets.
+    * @param oKey, to match Policies and SAs for output packets.
+    * @param interfaceId the identifier for the IPsec tunnel interface.
+    */
+    void ipSecUpdateTunnelInterface(
+            in @utf8InCpp String deviceName,
+            in @utf8InCpp String localAddress,
+            in @utf8InCpp String remoteAddress,
+            int iKey,
+            int oKey,
+            int interfaceId);
+
+   /**
+    * Removes a IPsec Tunnel Interface.
+    *
+    * @param devName a unique identifier that represents the name of the device
+    */
+    void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+
+   /**
+    * Request notification of wakeup packets arriving on an interface. Notifications will be
+    * delivered to INetdEventListener.onWakeupEvent().
+    *
+    * @param ifName the interface
+    * @param prefix arbitrary string used to identify wakeup sources in onWakeupEvent
+    */
+    void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+
+   /**
+    * Stop notification of wakeup packets arriving on an interface.
+    *
+    * @param ifName the interface
+    * @param prefix arbitrary string used to identify wakeup sources in onWakeupEvent
+    */
+    void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+
+    const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+    const int IPV6_ADDR_GEN_MODE_NONE = 1;
+    const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+    const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+
+    const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+   /**
+    * Set IPv6 address generation mode. IPv6 should be disabled before changing mode.
+    *
+    * @param mode SLAAC address generation mechanism to use
+    */
+    void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+
+   /**
+    * Add idletimer for specific interface
+    *
+    * @param ifName Name of target interface
+    * @param timeout The time in seconds that will trigger idletimer
+    * @param classLabel The unique identifier for this idletimer
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void idletimerAddInterface(
+            in @utf8InCpp String ifName,
+            int timeout,
+            in @utf8InCpp String classLabel);
+
+   /**
+    * Remove idletimer for specific interface
+    *
+    * @param ifName Name of target interface
+    * @param timeout The time in seconds that will trigger idletimer
+    * @param classLabel The unique identifier for this idletimer
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void idletimerRemoveInterface(
+            in @utf8InCpp String ifName,
+            int timeout,
+            in @utf8InCpp String classLabel);
+
+    const int PENALTY_POLICY_ACCEPT = 1;
+    const int PENALTY_POLICY_LOG = 2;
+    const int PENALTY_POLICY_REJECT = 3;
+
+   /**
+    * Offers to detect sockets sending data not wrapped inside a layer of SSL/TLS encryption.
+    *
+    * @param uid Uid of the app
+    * @param policyPenalty The penalty policy of the app
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void strictUidCleartextPenalty(int uid, int policyPenalty);
+
+   /**
+    * Start clatd
+    *
+    * @param ifName interface name to start clatd
+    * @param nat64Prefix the NAT64 prefix, e.g., "2001:db8:64::/96".
+    * @return a string, the IPv6 address that will be used for 464xlat.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+
+   /**
+    * Stop clatd
+    *
+    * @param ifName interface name to stop clatd
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void clatdStop(in @utf8InCpp String ifName);
+
+   /**
+    * Get status of IP forwarding
+    *
+    * @return true if IP forwarding is enabled, false otherwise.
+    */
+    boolean ipfwdEnabled();
+
+   /**
+    * Get requester list of IP forwarding
+    *
+    * @return An array of strings containing requester list of IP forwarding
+    */
+    @utf8InCpp String[] ipfwdGetRequesterList();
+
+   /**
+    * Enable IP forwarding for specific requester
+    *
+    * @param requester requester name to enable IP forwarding. It is a unique name which will be
+    *                  stored in Netd to make sure if any requester needs IP forwarding.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void ipfwdEnableForwarding(in @utf8InCpp String requester);
+
+   /**
+    * Disable IP forwarding for specific requester
+    *
+    * @param requester requester name to disable IP forwarding. This name should match the
+    *                  names which are set by ipfwdEnableForwarding.
+    *                  IP forwarding would be disabled if it is the last requester.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void ipfwdDisableForwarding(in @utf8InCpp String requester);
+
+   /**
+    * Add forwarding ip rule
+    *
+    * @param fromIface interface name to add forwarding ip rule
+    * @param toIface interface name to add forwarding ip rule
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+
+   /**
+    * Remove forwarding ip rule
+    *
+    * @param fromIface interface name to remove forwarding ip rule
+    * @param toIface interface name to remove forwarding ip rule
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+
+   /**
+    * Set quota for interface
+    *
+    * @param ifName Name of target interface
+    * @param bytes Quota value in bytes
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+
+   /**
+    * Remove quota for interface
+    *
+    * @param ifName Name of target interface
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+
+   /**
+    * Set alert for interface
+    *
+    * @param ifName Name of target interface
+    * @param bytes Alert value in bytes
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+
+   /**
+    * Remove alert for interface
+    *
+    * @param ifName Name of target interface
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+
+   /**
+    * Set global alert
+    *
+    * @param bytes Alert value in bytes
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthSetGlobalAlert(long bytes);
+
+   /**
+    * Add naughty app bandwidth rule for specific app
+    *
+    * @param uid uid of target app
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthAddNaughtyApp(int uid);
+
+   /**
+    * Remove naughty app bandwidth rule for specific app
+    *
+    * @param uid uid of target app
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthRemoveNaughtyApp(int uid);
+
+   /**
+    * Add nice app bandwidth rule for specific app
+    *
+    * @param uid uid of target app
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthAddNiceApp(int uid);
+
+   /**
+    * Remove nice app bandwidth rule for specific app
+    *
+    * @param uid uid of target app
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthRemoveNiceApp(int uid);
+
+   /**
+    * Start tethering
+    *
+    * @param dhcpRanges dhcp ranges to set.
+    *                   dhcpRanges might contain many addresss {addr1, addr2, aadr3, addr4...}
+    *                   Netd splits them into ranges: addr1-addr2, addr3-addr4, etc.
+    *                   An odd number of addrs will fail.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void tetherStart(in @utf8InCpp String[] dhcpRanges);
+
+   /**
+    * Stop tethering
+    *
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void tetherStop();
+
+   /**
+    * Get status of tethering
+    *
+    * @return true if tethering is enabled, false otherwise.
+    */
+    boolean tetherIsEnabled();
+
+   /**
+    * Setup interface for tethering
+    *
+    * @param ifName interface name to add
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void tetherInterfaceAdd(in @utf8InCpp String ifName);
+
+   /**
+    * Reset interface for tethering
+    *
+    * @param ifName interface name to remove
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void tetherInterfaceRemove(in @utf8InCpp String ifName);
+
+   /**
+    * Get the interface list which is stored in netd
+    * The list contains the interfaces managed by tetherInterfaceAdd/tetherInterfaceRemove
+    *
+    * @return An array of strings containing interface list result
+    */
+    @utf8InCpp String[] tetherInterfaceList();
+
+   /**
+    * Set DNS forwarder server
+    *
+    * @param netId the upstream network to forward DNS queries to
+    * @param dnsAddrs DNS server address to set
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+
+   /**
+    * Return the DNS list set by tetherDnsSet
+    *
+    * @return An array of strings containing the list of DNS servers
+    */
+    @utf8InCpp String[] tetherDnsList();
+
+    const int LOCAL_NET_ID = 99;
+
+    // Route does not specify a next hop
+    const String NEXTHOP_NONE = "";
+    // Route next hop is unreachable
+    const String NEXTHOP_UNREACHABLE = "unreachable";
+    // Route next hop is throw
+    const String NEXTHOP_THROW = "throw";
+
+   /**
+    * Add a route for specific network
+    *
+    * @param netId the network to add the route to
+    * @param ifName the name of interface of the route.
+    *               This interface should be assigned to the netID.
+    * @param destination the destination of the route
+    * @param nextHop The route's next hop address,
+    *                or it could be either NEXTHOP_NONE, NEXTHOP_UNREACHABLE, NEXTHOP_THROW.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkAddRoute(
+            int netId,
+            in @utf8InCpp String ifName,
+            in @utf8InCpp String destination,
+            in @utf8InCpp String nextHop);
+
+   /**
+    * Remove a route for specific network
+    *
+    * @param netId the network to remove the route from
+    * @param ifName the name of interface of the route.
+    *               This interface should be assigned to the netID.
+    * @param destination the destination of the route
+    * @param nextHop The route's next hop address,
+    *                or it could be either NEXTHOP_NONE, NEXTHOP_UNREACHABLE, NEXTHOP_THROW.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkRemoveRoute(
+            int netId,
+            in @utf8InCpp String ifName,
+            in @utf8InCpp String destination,
+            in @utf8InCpp String nextHop);
+
+   /**
+    * Add a route to legacy routing table for specific network
+    *
+    * @param netId the network to add the route to
+    * @param ifName the name of interface of the route.
+    *               This interface should be assigned to the netID.
+    * @param destination the destination of the route
+    * @param nextHop The route's next hop address,
+    *                or it could be either NEXTHOP_NONE, NEXTHOP_UNREACHABLE, NEXTHOP_THROW.
+    * @param uid uid of the user
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkAddLegacyRoute(
+            int netId,
+            in @utf8InCpp String ifName,
+            in @utf8InCpp String destination,
+            in @utf8InCpp String nextHop,
+            int uid);
+
+   /**
+    * Remove a route from legacy routing table for specific network
+    *
+    * @param netId the network to remove the route from
+    * @param ifName the name of interface of the route.
+    *               This interface should be assigned to the netID.
+    * @param destination the destination of the route
+    * @param nextHop The route's next hop address,
+    *                or it could be either NEXTHOP_NONE, NEXTHOP_UNREACHABLE, NEXTHOP_THROW.
+    * @param uid uid of the user
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkRemoveLegacyRoute(
+            int netId,
+            in @utf8InCpp String ifName,
+            in @utf8InCpp String destination,
+            in @utf8InCpp String nextHop,
+            int uid);
+
+   /**
+    * Get default network
+    *
+    * @return netId of default network
+    */
+    int networkGetDefault();
+
+   /**
+    * Set network as default network
+    *
+    * @param netId the network to set as the default
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkSetDefault(int netId);
+
+   /**
+    * Clear default network
+    *
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkClearDefault();
+
+   /**
+    * PERMISSION_NONE is used for regular networks and apps. TODO: use PERMISSION_INTERNET
+    * for this instead, and use PERMISSION_NONE to indicate no network permissions at all.
+    */
+    const int PERMISSION_NONE = 0;
+
+   /**
+    * PERMISSION_NETWORK represents the CHANGE_NETWORK_STATE permission.
+    */
+    const int PERMISSION_NETWORK = 1;
+
+   /**
+    * PERMISSION_SYSTEM represents the ability to use restricted networks. This is mostly
+    * equivalent to the CONNECTIVITY_USE_RESTRICTED_NETWORKS permission.
+    */
+    const int PERMISSION_SYSTEM = 2;
+
+   /**
+    * NO_PERMISSIONS indicates that this app is installed and doesn't have either
+    * PERMISSION_INTERNET or PERMISSION_UPDATE_DEVICE_STATS.
+    * TODO: use PERMISSION_NONE to represent this case
+    */
+    const int NO_PERMISSIONS = 0;
+
+   /**
+    * PERMISSION_INTERNET indicates that the app can create AF_INET and AF_INET6 sockets
+    */
+    const int PERMISSION_INTERNET = 4;
+
+   /**
+    * PERMISSION_UPDATE_DEVICE_STATS is used for system UIDs and privileged apps
+    * that have the UPDATE_DEVICE_STATS permission
+    */
+    const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+
+   /**
+    * PERMISSION_UNINSTALLED is used when an app is uninstalled from the device. All internet
+    * related permissions need to be cleaned
+    */
+    const int PERMISSION_UNINSTALLED = -1;
+
+
+   /**
+    * Sets the permission required to access a specific network.
+    *
+    * @param netId the network to set
+    * @param permission network permission to use
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkSetPermissionForNetwork(int netId, int permission);
+
+   /**
+    * Assigns network access permissions to the specified users.
+    *
+    * @param permission network permission to use
+    * @param uids uid of users to set permission
+    */
+    void networkSetPermissionForUser(int permission, in int[] uids);
+
+   /**
+    * Clears network access permissions for the specified users.
+    *
+    * @param uids uid of users to clear permission
+    */
+    void networkClearPermissionForUser(in int[] uids);
+
+   /**
+    * Assigns android.permission.INTERNET and/or android.permission.UPDATE_DEVICE_STATS to the uids
+    * specified. Or remove all permissions from the uids.
+    *
+    * @param permission The permission to grant, it could be either PERMISSION_INTERNET and/or
+    *                   PERMISSION_UPDATE_DEVICE_STATS. If the permission is NO_PERMISSIONS, then
+    *                   revoke all permissions for the uids.
+    * @param uids uid of users to grant permission
+    */
+    void trafficSetNetPermForUids(int permission, in int[] uids);
+
+   /**
+    * Gives the specified user permission to protect sockets from VPNs.
+    * Typically used by VPN apps themselves, to ensure that the sockets
+    * they use to communicate with the VPN server aren't routed through
+    * the VPN network.
+    *
+    * @param uid uid of user to set
+    */
+    void networkSetProtectAllow(int uid);
+
+   /**
+    * Removes the permission to protect sockets from VPN.
+    *
+    * @param uid uid of user to set
+    */
+    void networkSetProtectDeny(int uid);
+
+   /**
+    * Get the status of network protect for user
+    *
+    * @param uids uid of user
+    * @return true if the user can protect sockets from VPN, false otherwise.
+    */
+    boolean networkCanProtect(int uid);
+
+    // Whitelist only allows packets from specific UID/Interface
+    const int FIREWALL_WHITELIST = 0;
+    // Blacklist blocks packets from specific UID/Interface
+    const int FIREWALL_BLACKLIST = 1;
+
+   /**
+    * Set type of firewall
+    * Type whitelist only allows packets from specific UID/Interface
+    * Type blacklist blocks packets from specific UID/Interface
+    *
+    * @param firewalltype type of firewall, either FIREWALL_WHITELIST or FIREWALL_BLACKLIST
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void firewallSetFirewallType(int firewalltype);
+
+    // Specify allow Rule which allows packets
+    const int FIREWALL_RULE_ALLOW = 1;
+    // Specify deny Rule which drops packets
+    const int FIREWALL_RULE_DENY = 2;
+
+    // No specific chain is chosen, use general firewall chain(fw_input, fw_output)
+    const int FIREWALL_CHAIN_NONE = 0;
+    // Specify DOZABLE chain(fw_dozable) which is used in dozable mode
+    const int FIREWALL_CHAIN_DOZABLE = 1;
+    // Specify STANDBY chain(fw_standby) which is used in standby mode
+    const int FIREWALL_CHAIN_STANDBY = 2;
+    // Specify POWERSAVE chain(fw_powersave) which is used in power save mode
+    const int FIREWALL_CHAIN_POWERSAVE = 3;
+
+   /**
+    * Set firewall rule for interface
+    *
+    * @param ifName the interface to allow/deny
+    * @param firewallRule either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+
+   /**
+    * Set firewall rule for uid
+    *
+    * @param childChain target chain
+    * @param uid uid to allow/deny
+    * @param firewallRule either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void firewallSetUidRule(int childChain, int uid, int firewallRule);
+
+   /**
+    * Enable/Disable target firewall child chain
+    *
+    * @param childChain target chain to enable
+    * @param enable whether to enable or disable child chain.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void firewallEnableChildChain(int childChain, boolean enable);
+
+   /**
+    * Get interface list
+    *
+    * @return An array of strings containing all the interfaces on the system.
+    * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+    *         unix errno.
+    */
+    @utf8InCpp String[] interfaceGetList();
+
+    // Must be kept in sync with constant in InterfaceConfiguration.java
+    const String IF_STATE_UP = "up";
+    const String IF_STATE_DOWN = "down";
+
+    const String IF_FLAG_BROADCAST = "broadcast";
+    const String IF_FLAG_LOOPBACK = "loopback";
+    const String IF_FLAG_POINTOPOINT = "point-to-point";
+    const String IF_FLAG_RUNNING = "running";
+    const String IF_FLAG_MULTICAST = "multicast";
+
+   /**
+    * Get interface configuration
+    *
+    * @param ifName interface name
+    * @return An InterfaceConfigurationParcel for the specified interface.
+    * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+    *         unix errno.
+    */
+    InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+
+   /**
+    * Set interface configuration
+    *
+    * @param cfg Interface configuration to set
+    * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+    *         unix errno.
+    */
+    void interfaceSetCfg(in InterfaceConfigurationParcel cfg);
+
+   /**
+    * Set interface IPv6 privacy extensions
+    *
+    * @param ifName interface name
+    * @param enable whether to enable or disable this setting.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+
+   /**
+    * Clear all IP addresses on the given interface
+    *
+    * @param ifName interface name
+    * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+    *         POSIX errno.
+    */
+    void interfaceClearAddrs(in @utf8InCpp String ifName);
+
+   /**
+    * Enable or disable IPv6 on the given interface
+    *
+    * @param ifName interface name
+    * @param enable whether to enable or disable this setting.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+
+   /**
+    * Set interface MTU
+    *
+    * @param ifName interface name
+    * @param mtu MTU value
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+
+   /**
+    * Add forwarding rule/stats on given interface.
+    *
+    * @param intIface downstream interface
+    * @param extIface upstream interface
+    */
+    void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+
+   /**
+    * Remove forwarding rule/stats on given interface.
+    *
+    * @param intIface downstream interface
+    * @param extIface upstream interface
+    */
+    void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+
+   /**
+    * Set the values of tcp_{rmem,wmem}.
+    *
+    * @param rmemValues the target values of tcp_rmem, each value is separated by spaces
+    * @param wmemValues the target values of tcp_wmem, each value is separated by spaces
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+
+   /**
+    * Register unsolicited event listener
+    * Netd supports multiple unsolicited event listeners.
+    *
+    * @param listener unsolicited event listener to register
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void registerUnsolicitedEventListener(INetdUnsolicitedEventListener listener);
+
+    /**
+     * Add ingress interface filtering rules to a list of UIDs
+     *
+     * For a given uid, once a filtering rule is added, the kernel will only allow packets from the
+     * whitelisted interface and loopback to be sent to the list of UIDs.
+     *
+     * Calling this method on one or more UIDs with an existing filtering rule but a different
+     * interface name will result in the filtering rule being updated to allow the new interface
+     * instead. Otherwise calling this method will not affect existing rules set on other UIDs.
+     *
+     * @param ifName the name of the interface on which the filtering rules will allow packets to
+              be received.
+     * @param uids an array of UIDs which the filtering rules will be set
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *         cause of the failure.
+     */
+    void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+
+    /**
+     * Remove ingress interface filtering rules from a list of UIDs
+     *
+     * Clear the ingress interface filtering rules from the list of UIDs which were previously set
+     * by firewallAddUidInterfaceRules(). Ignore any uid which does not have filtering rule.
+     *
+     * @param uids an array of UIDs from which the filtering rules will be removed
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *         cause of the failure.
+     */
+    void firewallRemoveUidInterfaceRules(in int[] uids);
+
+   /**
+    * Request netd to change the current active network stats map.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void trafficSwapActiveStatsMap();
+
+   /**
+    * Retrieves OEM netd listener interface
+    *
+    * @return a IBinder object, it could be casted to oem specific interface.
+    */
+    IBinder getOemNetd();
+
+   /**
+    * Start tethering with given configuration
+    *
+    * @param config config to start tethering.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void tetherStartWithConfiguration(in TetherConfigParcel config);
+
+
+    /**
+     * Get the fwmark and its net id mask for the given network id.
+     *
+     * @param netId the network to get the fwmark and mask for.
+     * @return A MarkMaskParcel of the given network id.
+     */
+    MarkMaskParcel getFwmarkForNetwork(int netId);
+
+    /**
+    * Add a route for specific network
+    *
+    * @param netId the network to add the route to
+    * @param routeInfo parcelable with route information
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+
+    /**
+    * Update a route for specific network
+    *
+    * @param routeInfo parcelable with route information
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+
+    /**
+    * Remove a route for specific network
+    *
+    * @param routeInfo parcelable with route information
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+
+    /**
+     * Adds a tethering offload rule, or updates it if it already exists.
+     *
+     * Currently, only downstream /128 IPv6 entries are supported. An existing rule will be updated
+     * if the input interface and destination prefix match. Otherwise, a new rule will be created.
+     *
+     * @param rule The rule to add or update.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+    void tetherOffloadRuleAdd(in TetherOffloadRuleParcel rule);
+
+    /**
+     * Deletes a tethering offload rule.
+     *
+     * Currently, only downstream /128 IPv6 entries are supported. An existing rule will be deleted
+     * if the destination IP address and the source interface match. It is not an error if there is
+     * no matching rule to delete.
+     *
+     * @param rule The rule to delete.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+    void tetherOffloadRuleRemove(in TetherOffloadRuleParcel rule);
+
+    /**
+     * Return BPF tethering offload statistics.
+     *
+     * @return an array of TetherStatsParcel's, where each entry contains the upstream interface
+     *         index and its tethering statistics since tethering was first started.
+     *         There will only ever be one entry for a given interface index.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *         cause of the failure.
+     */
+    TetherStatsParcel[] tetherOffloadGetStats();
+
+   /**
+    * Set a per-interface quota for tethering offload.
+    *
+    * @param ifIndex Index of upstream interface
+    * @param quotaBytes The quota defined as the number of bytes, starting from zero and counting
+     *       from *now*. A value of QUOTA_UNLIMITED (-1) indicates there is no limit.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
+
+    /**
+     * Return BPF tethering offload statistics and clear the stats for a given upstream.
+     *
+     * Must only be called once all offload rules have already been deleted for the given upstream
+     * interface. The existing stats will be fetched and returned. The stats and the limit for the
+     * given upstream interface will be deleted as well.
+     *
+     * The stats and limit for a given upstream interface must be initialized (using
+     * tetherOffloadSetInterfaceQuota) before any offload will occur on that interface.
+     *
+     * @param ifIndex Index of upstream interface.
+     * @return TetherStatsParcel, which contains the given upstream interface index and its
+     *         tethering statistics since tethering was first started on that upstream interface.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+     TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex);
+}
diff --git a/server/binder/android/net/INetdUnsolicitedEventListener.aidl b/server/binder/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..652a79c
--- /dev/null
+++ b/server/binder/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,145 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * Unsolicited netd events which are reported by the kernel via netlink.
+ * This one-way interface groups asynchronous notifications sent
+ * by netd to any process that registered itself via INetd.registerUnsolEventListener.
+ *
+ * {@hide}
+ */
+oneway interface INetdUnsolicitedEventListener {
+
+    /**
+     * Notifies that an interface has been idle/active for a certain period of time.
+     * It is the event for idletimer.
+     *
+     * @param isActive true for active status, false for idle
+     * @param timerLabel unique identifier of the idletimer.
+     *              Since NMS only set the identifier as int, only report event with int label.
+     * @param timestampNs kernel timestamp of this event, 0 for no timestamp
+     * @param uid uid of this event, -1 for no uid.
+     *            It represents the uid that was responsible for waking the radio.
+     */
+    void onInterfaceClassActivityChanged(
+            boolean isActive,
+            int timerLabel,
+            long timestampNs,
+            int uid);
+
+    /**
+     * Notifies that a specific interface reached its quota limit.
+     *
+     * @param alertName alert name of the quota limit
+     * @param ifName interface which reached the limit
+     */
+    void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+
+    /**
+     * Provides information on IPv6 DNS servers on a specific interface.
+     *
+     * @param ifName interface name
+     * @param lifetimeS lifetime for the DNS servers in seconds
+     * @param servers the address of servers.
+     *                  e.g. IpV6: "2001:4860:4860::6464"
+     *
+     */
+    void onInterfaceDnsServerInfo(
+            @utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+
+    /**
+     * Notifies that an address has updated on a specific interface.
+     *
+     * @param addr address that is being updated
+     * @param ifName the name of the interface on which the address is configured
+     * @param flags address flags, see ifa_flags in if_addr.h
+     * @param scope current scope of the address
+     */
+    void onInterfaceAddressUpdated(
+            @utf8InCpp String addr,
+            @utf8InCpp String ifName,
+            int flags,
+            int scope);
+
+    /**
+     * Notifies that an address has been removed on a specific interface.
+     *
+     * @param addr address of this change
+     * @param ifName the name of the interface that changed addresses
+     * @param flags address flags, see ifa_flags in if_addr.h
+     * @param scope address address scope
+     */
+    void onInterfaceAddressRemoved(
+            @utf8InCpp String addr,
+            @utf8InCpp String ifName,
+            int flags,
+            int scope);
+
+    /**
+     * Notifies that an interface has been added.
+     *
+     * @param ifName the name of the added interface
+     */
+    void onInterfaceAdded(@utf8InCpp String ifName);
+
+    /**
+     * Notifies that an interface has been removed.
+     *
+     * @param ifName the name of the removed interface
+     */
+    void onInterfaceRemoved(@utf8InCpp String ifName);
+
+    /**
+     * Notifies that the status of the specific interface has changed.
+     *
+     * @param ifName the name of the interface that changed status
+     * @param up true for interface up, false for down
+     */
+    void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+
+    /**
+     * Notifies that the link state of the specific interface has changed.
+     *
+     * @param ifName the name of the interface whose link state has changed
+     * @param up true for interface link state up, false for link state down
+     */
+    void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+
+    /**
+     * Notifies that an IP route has changed.
+     *
+     * @param updated true for update, false for remove
+     * @param route destination prefix of this route, e.g., "2001:db8::/64"
+     * @param gateway address of gateway, empty string for no gateway
+     * @param ifName interface name of this route, empty string for no interface
+     */
+    void onRouteChanged(
+            boolean updated,
+            @utf8InCpp String route,
+            @utf8InCpp String gateway,
+            @utf8InCpp String ifName);
+
+    /**
+     * Notifies that kernel has detected a socket sending data not wrapped
+     * inside a layer of SSL/TLS encryption.
+     *
+     * @param uid uid of this event
+     * @param hex packet content in hex format
+     */
+    void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/server/binder/android/net/InterfaceConfigurationParcel.aidl b/server/binder/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..c20792c
--- /dev/null
+++ b/server/binder/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * Configuration details for a network interface.
+ *
+ * {@hide}
+ */
+parcelable InterfaceConfigurationParcel {
+    @utf8InCpp String ifName;
+    @utf8InCpp String hwAddr;
+    @utf8InCpp String ipv4Addr;
+    int prefixLength;
+    /**
+    * Interface flags, String versions of IFF_* defined in netd/if.h
+    */
+    @utf8InCpp String[] flags;
+}
diff --git a/server/binder/android/net/MarkMaskParcel.aidl b/server/binder/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..932b7bf
--- /dev/null
+++ b/server/binder/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * Structure that stores a firewall mark and its mask.
+ *
+ * {@hide}
+ */
+parcelable MarkMaskParcel {
+    // The fwmark.
+    int mark;
+    // Net id mask of fwmark.
+    int mask;
+}
diff --git a/server/binder/android/net/RouteInfoParcel.aidl b/server/binder/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..fcc86e3
--- /dev/null
+++ b/server/binder/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+parcelable RouteInfoParcel {
+  // The destination of the route.
+  @utf8InCpp String destination;
+  // The name of interface of the route. This interface should be assigned to the netID.
+  @utf8InCpp String ifName;
+  // The route's next hop address, or one of the NEXTHOP_* constants defined in INetd.aidl.
+  @utf8InCpp String nextHop;
+  // The MTU of the route.
+  int mtu;
+}
diff --git a/server/binder/android/net/TetherConfigParcel.aidl b/server/binder/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..9f371ce
--- /dev/null
+++ b/server/binder/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * The configuration to start tethering.
+ *
+ * {@hide}
+ */
+parcelable TetherConfigParcel {
+    // Whether to enable or disable legacy DNS proxy server.
+    boolean usingLegacyDnsProxy;
+    // DHCP ranges to set.
+    // dhcpRanges might contain many addresss {addr1, addr2, addr3, addr4...}
+    // Netd splits them into ranges: addr1-addr2, addr3-addr4, etc.
+    // An odd number of addrs will fail.
+    @utf8InCpp String[] dhcpRanges;
+}
diff --git a/server/binder/android/net/TetherOffloadRuleParcel.aidl b/server/binder/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..c549e61
--- /dev/null
+++ b/server/binder/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * Represents a forwarding rule for tethering offload.
+ *
+ * {@hide}
+ */
+parcelable TetherOffloadRuleParcel {
+    /** The interface index of the input interface. */
+    int inputInterfaceIndex;
+
+    /** The interface index of the output interface. */
+    int outputInterfaceIndex;
+
+    /** The base IP address of the destination prefix as a byte array. */
+    byte[] destination;
+
+    /** The destination prefix length. */
+    int prefixLength;
+
+    /** The source link-layer address. Currently, must be a 6-byte MAC address.*/
+    byte[] srcL2Address;
+
+    /** The destination link-layer address. Currently, must be a 6-byte MAC address. */
+    byte[] dstL2Address;
+
+    /** The outbound path mtu. */
+    int pmtu = 1500;
+}
diff --git a/server/binder/android/net/TetherStatsParcel.aidl b/server/binder/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..6bf60a8
--- /dev/null
+++ b/server/binder/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * The statistics of tethering interface
+ *
+ * {@hide}
+ */
+parcelable TetherStatsParcel {
+    /**
+     * Parcel representing tethering interface statistics.
+     *
+     * This parcel is used by tetherGetStats, tetherOffloadGetStats and
+     * tetherOffloadGetAndClearStats in INetd.aidl. tetherGetStats uses this parcel to return the
+     * tethering statistics since netd startup and presents the interface via its interface name.
+     * Both tetherOffloadGetStats and tetherOffloadGetAndClearStats use this parcel to return
+     * the tethering statistics since tethering was first started. They present the interface via
+     * its interface index. Note that the interface must be presented by either interface name
+     * |iface| or interface index |ifIndex| in this parcel. The unused interface name is set to
+     * an empty string "" by default and the unused interface index is set to 0 by default.
+     */
+
+    /** The interface name. */
+    @utf8InCpp String iface;
+
+    /** Total number of received bytes. */
+    long rxBytes;
+
+    /** Total number of received packets. */
+    long rxPackets;
+
+    /** Total number of transmitted bytes. */
+    long txBytes;
+
+    /** Total number of transmitted packets. */
+    long txPackets;
+
+    /** The interface index. */
+    int ifIndex = 0;
+}
diff --git a/server/binder/android/net/UidRangeParcel.aidl b/server/binder/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..08fd491
--- /dev/null
+++ b/server/binder/android/net/UidRangeParcel.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * An inclusive range of UIDs.
+ *
+ * {@hide}
+ */
+parcelable UidRangeParcel {
+    int start;
+    int stop;
+}
\ No newline at end of file
diff --git a/server/binder/android/net/metrics/INetdEventListener.aidl b/server/binder/android/net/metrics/INetdEventListener.aidl
new file mode 100644
index 0000000..ef1b2cb
--- /dev/null
+++ b/server/binder/android/net/metrics/INetdEventListener.aidl
@@ -0,0 +1,128 @@
+/**
+ * Copyright (c) 2016, 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.metrics;
+
+/**
+ * Logs netd events.
+ *
+ * {@hide}
+ */
+oneway interface INetdEventListener {
+    const int EVENT_GETADDRINFO = 1;
+    const int EVENT_GETHOSTBYNAME = 2;
+    const int EVENT_GETHOSTBYADDR = 3;
+    const int EVENT_RES_NSEND = 4;
+
+    const int REPORTING_LEVEL_NONE = 0;
+    const int REPORTING_LEVEL_METRICS = 1;
+    const int REPORTING_LEVEL_FULL = 2;
+
+    // Maximum number of IP addresses logged for DNS lookups before we truncate the full list.
+    const int DNS_REPORTED_IP_ADDRESSES_LIMIT = 10;
+
+    /**
+     * Logs a DNS lookup function call (getaddrinfo and gethostbyname).
+     *
+     * @param netId the ID of the network the lookup was performed on.
+     * @param eventType one of the EVENT_* constants in this interface.
+     * @param returnCode the return value of the function call.
+     * @param latencyMs the latency of the function call.
+     * @param hostname the name that was looked up.
+     * @param ipAddresses (possibly a subset of) the IP addresses returned.
+     *        At most {@link #DNS_REPORTED_IP_ADDRESSES_LIMIT} addresses are logged.
+     * @param ipAddressesCount the number of IP addresses returned. May be different from the length
+     *        of ipAddresses if there were too many addresses to log.
+     * @param uid the UID of the application that performed the query.
+     */
+    void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs,
+            @utf8InCpp String hostname, in @utf8InCpp String[] ipAddresses,
+            int ipAddressesCount, int uid);
+
+    /**
+     * Represents a private DNS validation success or failure.
+     *
+     * @param netId the ID of the network the validation was performed on.
+     * @param ipAddress the IP address for which validation was performed.
+     * @param hostname the hostname for which validation was performed.
+     * @param validated whether or not validation was successful.
+     */
+    void onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname,
+            boolean validated);
+
+    /**
+     * Logs a single connect library call.
+     *
+     * @param netId the ID of the network the connect was performed on.
+     * @param error 0 if the connect call succeeded, otherwise errno if it failed.
+     * @param latencyMs the latency of the connect call.
+     * @param ipAddr destination IP address.
+     * @param port destination port number.
+     * @param uid the UID of the application that performed the connection.
+     */
+    void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, int uid);
+
+    /**
+     * Logs a single RX packet which caused the main CPU to exit sleep state.
+     * @param prefix arbitrary string provided via wakeupAddInterface()
+     * @param uid UID of the destination process or -1 if no UID is available.
+     * @param ethertype of the RX packet encoded in an int in native order, or -1 if not available.
+     * @param ipNextHeader ip protocol of the RX packet as IPPROTO_* number,
+              or -1 if the packet was not IPv4 or IPv6.
+     * @param dstHw destination hardware address, or 0 if not available.
+     * @param srcIp source IP address, or null if not available.
+     * @param dstIp destination IP address, or null if not available.
+     * @param srcPort src port of RX packet in native order, or -1 if the packet was not UDP or TCP.
+     * @param dstPort dst port of RX packet in native order, or -1 if the packet was not UDP or TCP.
+     * @param timestampNs receive timestamp for the offending packet. In units of nanoseconds and
+     *        synchronized to CLOCK_MONOTONIC.
+     */
+    void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, in byte[] dstHw,
+            String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs);
+
+    /**
+     * An event sent after every Netlink sock_diag poll performed by Netd. This reported batch
+     * groups TCP socket stats aggregated by network id. Per-network data are stored in a
+     * structure-of-arrays style where networkIds, sentPackets, lostPackets, rttUs, and
+     * sentAckDiffMs have the same length. Stats for the i-th network is spread across all these
+     * arrays at index i.
+     * @param networkIds an array of network ids for which there was tcp socket stats to collect in
+     *        the last sock_diag poll.
+     * @param sentPackets an array of packet sent across all TCP sockets still alive and new
+              TCP sockets since the last sock_diag poll, summed per network id.
+     * @param lostPackets, an array of packet lost across all TCP sockets still alive and new
+              TCP sockets since the last sock_diag poll, summed per network id.
+     * @param rttUs an array of smoothed round trip times in microseconds, averaged across all TCP
+              sockets since the last sock_diag poll for a given network id.
+     * @param sentAckDiffMs an array of milliseconds duration between the last packet sent and the
+              last ack received for a socket, averaged across all TCP sockets for a network id.
+     */
+    void onTcpSocketStatsEvent(in int[] networkIds, in int[] sentPackets,
+            in int[] lostPackets, in int[] rttUs, in int[] sentAckDiffMs);
+
+    /**
+     * Represents adding or removing a NAT64 prefix.
+     *
+     * @param netId the ID of the network the prefix was discovered on.
+     * @param added true if the NAT64 prefix was added, or false if the NAT64 prefix was removed.
+     *        There is only one prefix at a time for each netId. If a prefix is added, it replaces
+     *        the previous-added prefix.
+     * @param prefixString the detected NAT64 prefix as a string literal.
+     * @param prefixLength the prefix length associated with this NAT64 prefix.
+     */
+    void onNat64PrefixEvent(int netId, boolean added, @utf8InCpp String prefixString,
+            int prefixLength);
+}
diff --git a/server/main.cpp b/server/main.cpp
index bc48ac2..4949ff6 100644
--- a/server/main.cpp
+++ b/server/main.cpp
@@ -110,9 +110,7 @@
     gLog.info("netd 1.0 starting");
 
     android::net::process::removePidFile(PID_FILE_PATH);
-    gLog.info("Pid file removed");
     android::net::process::blockSigPipe();
-    gLog.info("SIGPIPE is blocked");
 
     // Before we do anything that could fork, mark CLOEXEC the UNIX sockets that we get from init.
     // FrameworkListener does this on initialization as well, but we only initialize these
@@ -120,19 +118,16 @@
     for (const auto& sock :
          {DNSPROXYLISTENER_SOCKET_NAME, FwmarkServer::SOCKET_NAME, MDnsSdListener::SOCKET_NAME}) {
         setCloseOnExec(sock);
-        gLog.info("setCloseOnExec(%s)", sock);
     }
 
     // Make sure BPF programs are loaded before doing anything
     android::bpf::waitForProgsLoaded();
-    gLog.info("BPF programs are loaded");
 
     NetlinkManager *nm = NetlinkManager::Instance();
     if (nm == nullptr) {
         ALOGE("Unable to create NetlinkManager");
         exit(1);
     };
-    gLog.info("NetlinkManager instanced");
 
     gCtls = new android::net::Controllers();
     gCtls->init();
diff --git a/tests/Android.bp b/tests/Android.bp
index c5d9bb5..3baaa18 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -13,15 +13,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "system_netd_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["system_netd_license"],
-}
-
 cc_test_library {
     name: "libnetd_test_tun_interface",
     defaults: ["netd_defaults"],
@@ -52,7 +43,7 @@
         "libnetutils",
         "libsysutils",
         "libutils",
-        "netd_aidl_interface-V7-cpp",
+        "netd_aidl_interface-cpp",
     ],
 }
 
@@ -95,7 +86,6 @@
         "libnetutils",
         "libprocessgroup",
         "libssl",
-        "libsysutils",
         "libutils",
     ],
     static_libs: [
@@ -107,8 +97,8 @@
         "libnetdbpf",
         "libnetdutils",
         "libqtaguid",
-        "netd_aidl_interface-V7-cpp",
-        "netd_event_listener_interface-V1-cpp",
+        "netd_aidl_interface-unstable-cpp",
+        "netd_event_listener_interface-cpp",
         "oemnetd_aidl_interface-cpp",
     ],
     compile_multilib: "both",
@@ -121,7 +111,7 @@
         },
     },
     sanitize: {
-        address: false,
+        address: true,
         recover: [ "all" ],
     },
 }
diff --git a/tests/benchmarks/Android.bp b/tests/benchmarks/Android.bp
index 2aa8df0..c4e8c5f 100644
--- a/tests/benchmarks/Android.bp
+++ b/tests/benchmarks/Android.bp
@@ -1,14 +1,5 @@
 // APCT build target for metrics tests
 
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "system_netd_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["system_netd_license"],
-}
-
 cc_benchmark {
     name: "netd_benchmark",
     defaults: ["netd_defaults"],
@@ -22,10 +13,10 @@
     ],
     static_libs: [
         "libnetd_test_dnsresponder_ndk",
-        "dnsresolver_aidl_interface-lateststable-ndk",
+        "dnsresolver_aidl_interface-lateststable-ndk_platform",
         "netd_aidl_interface-lateststable-cpp", //  system/netd/server/UidRanges.h
-        "netd_aidl_interface-lateststable-ndk",
-        "netd_event_listener_interface-lateststable-ndk",
+        "netd_aidl_interface-lateststable-ndk_platform",
+        "netd_event_listener_interface-lateststable-ndk_platform",
     ],
     aidl: {
         include_dirs: ["system/netd/server/binder"],
diff --git a/tests/benchmarks/bpf_benchmark.cpp b/tests/benchmarks/bpf_benchmark.cpp
index bf4bd54..12aaf95 100644
--- a/tests/benchmarks/bpf_benchmark.cpp
+++ b/tests/benchmarks/bpf_benchmark.cpp
@@ -34,7 +34,7 @@
 };
 
 BENCHMARK_DEFINE_F(BpfBenchMark, MapWriteNewEntry)(benchmark::State& state) {
-    for (auto _ : state) {  // NOLINT(clang-analyzer-deadcode.DeadStores)
+    for (auto _ : state) {
         // TODO(b/147676069) assert
         mBpfTestMap.writeValue(state.range(0), state.range(0), BPF_NOEXIST);
     }
@@ -45,7 +45,7 @@
         // TODO(b/147676069) assert
         mBpfTestMap.writeValue(i, i, BPF_NOEXIST);
     }
-    for (auto _ : state) {  // NOLINT(clang-analyzer-deadcode.DeadStores)
+    for (auto _ : state) {
         // TODO(b/147676069) assert
         mBpfTestMap.writeValue(state.range(0), state.range(0) + 1, BPF_EXIST);
     }
@@ -56,7 +56,7 @@
         // TODO(b/147676069) assert
         mBpfTestMap.writeValue(i, i, BPF_NOEXIST);
     }
-    for (auto _ : state) {  // NOLINT(clang-analyzer-deadcode.DeadStores)
+    for (auto _ : state) {
         // TODO(b/147676069) assert
         mBpfTestMap.deleteValue(state.range(0));
         // TODO(b/147676069) assert
@@ -65,7 +65,7 @@
 }
 
 BENCHMARK_DEFINE_F(BpfBenchMark, WaitForRcu)(benchmark::State& state) {
-    for (auto _ : state) {  // NOLINT(clang-analyzer-deadcode.DeadStores)
+    for (auto _ : state) {
         int ret = android::bpf::synchronizeKernelRCU();
         if (ret) {
             state.SkipWithError(
diff --git a/tests/binder_test.cpp b/tests/binder_test.cpp
index 22d1f22..2a6bb7c 100644
--- a/tests/binder_test.cpp
+++ b/tests/binder_test.cpp
@@ -24,10 +24,7 @@
 #include <cstdlib>
 #include <iostream>
 #include <mutex>
-#include <regex>
 #include <set>
-#include <string>
-#include <thread>
 #include <vector>
 
 #include <dirent.h>
@@ -60,13 +57,11 @@
 #include <gtest/gtest.h>
 #include <netdbpf/bpf_shared.h>
 #include <netutils/ifc.h>
-#include <utils/Errors.h>
 #include "Fwmark.h"
 #include "InterfaceController.h"
 #include "NetdClient.h"
 #include "NetdConstants.h"
 #include "NetworkController.h"
-#include "RouteController.h"
 #include "SockDiag.h"
 #include "TestUnsolService.h"
 #include "XfrmController.h"
@@ -95,7 +90,6 @@
 using android::String8;
 using android::base::Join;
 using android::base::make_scope_guard;
-using android::base::ReadFdToString;
 using android::base::ReadFileToString;
 using android::base::StartsWith;
 using android::base::StringPrintf;
@@ -105,28 +99,11 @@
 using android::net::InterfaceConfigurationParcel;
 using android::net::InterfaceController;
 using android::net::MarkMaskParcel;
-using android::net::NativeNetworkConfig;
-using android::net::NativeNetworkType;
-using android::net::NativeVpnType;
-using android::net::RULE_PRIORITY_BYPASSABLE_VPN;
-using android::net::RULE_PRIORITY_DEFAULT_NETWORK;
-using android::net::RULE_PRIORITY_EXPLICIT_NETWORK;
-using android::net::RULE_PRIORITY_OUTPUT_INTERFACE;
-using android::net::RULE_PRIORITY_PROHIBIT_NON_VPN;
-using android::net::RULE_PRIORITY_SECURE_VPN;
-using android::net::RULE_PRIORITY_TETHERING;
-using android::net::RULE_PRIORITY_UID_DEFAULT_NETWORK;
-using android::net::RULE_PRIORITY_UID_DEFAULT_UNREACHABLE;
-using android::net::RULE_PRIORITY_UID_EXPLICIT_NETWORK;
-using android::net::RULE_PRIORITY_UID_IMPLICIT_NETWORK;
-using android::net::RULE_PRIORITY_VPN_FALLTHROUGH;
 using android::net::SockDiag;
 using android::net::TetherOffloadRuleParcel;
 using android::net::TetherStatsParcel;
 using android::net::TunInterface;
 using android::net::UidRangeParcel;
-using android::net::UidRanges;
-using android::net::netd::aidl::NativeUidRangeConfig;
 using android::netdutils::IPAddress;
 using android::netdutils::ScopedAddrinfo;
 using android::netdutils::sSyscalls;
@@ -136,29 +113,18 @@
 static const char* IP_RULE_V6 = "-6";
 static const int TEST_NETID1 = 65501;
 static const int TEST_NETID2 = 65502;
-static const int TEST_NETID3 = 65503;
-static const int TEST_NETID4 = 65504;
-static const int TEST_DUMP_NETID = 65123;
 static const char* DNSMASQ = "dnsmasq";
 
 // Use maximum reserved appId for applications to avoid conflict with existing
 // uids.
 static const int TEST_UID1 = 99999;
 static const int TEST_UID2 = 99998;
-static const int TEST_UID3 = 99997;
-static const int TEST_UID4 = 99996;
-static const int TEST_UID5 = 99995;
-static const int TEST_UID6 = 99994;
 
 constexpr int BASE_UID = AID_USER_OFFSET * 5;
 
 static const std::string NO_SOCKET_ALLOW_RULE("! owner UID match 0-4294967294");
 static const std::string ESP_ALLOW_RULE("esp");
 
-static const in6_addr V6_ADDR = {
-        {// 2001:db8:cafe::8888
-         .u6_addr8 = {0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe, 0, 0, 0, 0, 0, 0, 0, 0, 0x88, 0x88}}};
-
 class NetdBinderTest : public ::testing::Test {
   public:
     NetdBinderTest() {
@@ -176,8 +142,6 @@
     void TearDown() override {
         mNetd->networkDestroy(TEST_NETID1);
         mNetd->networkDestroy(TEST_NETID2);
-        mNetd->networkDestroy(TEST_NETID3);
-        mNetd->networkDestroy(TEST_NETID4);
         setNetworkForProcess(NETID_UNSET);
         // Restore default network
         if (mStoredDefaultNetwork >= 0) mNetd->networkSetDefault(mStoredDefaultNetwork);
@@ -189,20 +153,14 @@
     static void SetUpTestCase() {
         ASSERT_EQ(0, sTun.init());
         ASSERT_EQ(0, sTun2.init());
-        ASSERT_EQ(0, sTun3.init());
-        ASSERT_EQ(0, sTun4.init());
         ASSERT_LE(sTun.name().size(), static_cast<size_t>(IFNAMSIZ));
         ASSERT_LE(sTun2.name().size(), static_cast<size_t>(IFNAMSIZ));
-        ASSERT_LE(sTun3.name().size(), static_cast<size_t>(IFNAMSIZ));
-        ASSERT_LE(sTun4.name().size(), static_cast<size_t>(IFNAMSIZ));
     }
 
     static void TearDownTestCase() {
         // Closing the socket removes the interface and IP addresses.
         sTun.destroy();
         sTun2.destroy();
-        sTun3.destroy();
-        sTun4.destroy();
     }
 
     static void fakeRemoteSocketPair(unique_fd* clientSocket, unique_fd* serverSocket,
@@ -211,22 +169,6 @@
     void createVpnNetworkWithUid(bool secure, uid_t uid, int vpnNetId = TEST_NETID2,
                                  int fallthroughNetId = TEST_NETID1);
 
-    void createAndSetDefaultNetwork(int netId, const std::string& interface,
-                                    int permission = INetd::PERMISSION_NONE);
-
-    void createPhysicalNetwork(int netId, const std::string& interface,
-                               int permission = INetd::PERMISSION_NONE);
-
-    void createDefaultAndOtherPhysicalNetwork(int defaultNetId, int otherNetId);
-
-    void createVpnAndOtherPhysicalNetwork(int systemDefaultNetId, int otherNetId, int vpnNetId,
-                                          bool secure);
-
-    void createVpnAndAppDefaultNetworkWithUid(int systemDefaultNetId, int appDefaultNetId,
-                                              int vpnNetId, bool secure,
-                                              std::vector<UidRangeParcel>&& appDefaultUidRanges,
-                                              std::vector<UidRangeParcel>&& vpnUidRanges);
-
   protected:
     // Use -1 to represent that default network was not modified because
     // real netId must be an unsigned value.
@@ -234,14 +176,10 @@
     sp<INetd> mNetd;
     static TunInterface sTun;
     static TunInterface sTun2;
-    static TunInterface sTun3;
-    static TunInterface sTun4;
 };
 
 TunInterface NetdBinderTest::sTun;
 TunInterface NetdBinderTest::sTun2;
-TunInterface NetdBinderTest::sTun3;
-TunInterface NetdBinderTest::sTun4;
 
 class TimedOperation : public Stopwatch {
   public:
@@ -261,62 +199,67 @@
     ASSERT_TRUE(isAlive);
 }
 
-namespace {
-
-NativeNetworkConfig makeNativeNetworkConfig(int netId, NativeNetworkType networkType,
-                                            int permission, bool secure) {
-    NativeNetworkConfig config = {};
-    config.netId = netId;
-    config.networkType = networkType;
-    config.permission = permission;
-    config.secure = secure;
-    // The vpnType doesn't matter in AOSP. Just pick a well defined one from INetd.
-    config.vpnType = NativeVpnType::PLATFORM;
-    return config;
+static bool iptablesNoSocketAllowRuleExists(const char *chainName){
+    return iptablesRuleExists(IPTABLES_PATH, chainName, NO_SOCKET_ALLOW_RULE) &&
+           iptablesRuleExists(IP6TABLES_PATH, chainName, NO_SOCKET_ALLOW_RULE);
 }
 
-}  // namespace
+static bool iptablesEspAllowRuleExists(const char *chainName){
+    return iptablesRuleExists(IPTABLES_PATH, chainName, ESP_ALLOW_RULE) &&
+           iptablesRuleExists(IP6TABLES_PATH, chainName, ESP_ALLOW_RULE);
+}
 
-bool testNetworkExistsButCannotConnect(const sp<INetd>& netd, TunInterface& ifc, const int netId) {
-    // If this network exists, we should definitely not be able to create it.
-    // Note that this networkCreate is never allowed to create reserved network IDs, so
-    // this call may fail for other reasons than the network already existing.
-    const auto& config = makeNativeNetworkConfig(netId, NativeNetworkType::PHYSICAL,
-                                                 INetd::PERMISSION_NONE, false);
-    EXPECT_FALSE(netd->networkCreate(config).isOk());
-    // Test if the network exist by adding interface. INetd has no dedicated method to query. When
-    // the network exists and the interface can be added, the function succeeds. When the network
-    // exists but the interface cannot be added, it fails with EINVAL, otherwise it is ENONET.
-    binder::Status status = netd->networkAddInterface(netId, ifc.name());
-    if (status.isOk()) {  // clean up
-        EXPECT_TRUE(netd->networkRemoveInterface(netId, ifc.name()).isOk());
-    } else if (status.serviceSpecificErrorCode() == ENONET) {
-        return false;
+TEST_F(NetdBinderTest, FirewallReplaceUidChain) {
+    SKIP_IF_BPF_SUPPORTED;
+
+    std::string chainName = StringPrintf("netd_binder_test_%u", arc4random_uniform(10000));
+    const int kNumUids = 500;
+    std::vector<int32_t> noUids(0);
+    std::vector<int32_t> uids(kNumUids);
+    for (int i = 0; i < kNumUids; i++) {
+        uids[i] = randomUid();
     }
 
-    const sockaddr_in6 sin6 = {.sin6_family = AF_INET6,
-                               .sin6_addr = {{.u6_addr32 = {htonl(0x20010db8), 0, 0, 0}}},
-                               .sin6_port = 53};
-    const int s = socket(AF_INET6, SOCK_DGRAM, 0);
-    EXPECT_NE(-1, s);
-    if (s == -1) return true;
-    Fwmark fwmark;
-    fwmark.explicitlySelected = true;
-    fwmark.netId = netId;
-    EXPECT_EQ(0, setsockopt(s, SOL_SOCKET, SO_MARK, &fwmark.intValue, sizeof(fwmark.intValue)));
-    const int ret = connect(s, (struct sockaddr*)&sin6, sizeof(sin6));
-    const int err = errno;
-    EXPECT_EQ(-1, ret);
-    EXPECT_EQ(ENETUNREACH, err);
-    close(s);
-    return true;
-}
+    bool ret;
+    {
+        TimedOperation op(StringPrintf("Programming %d-UID whitelist chain", kNumUids));
+        mNetd->firewallReplaceUidChain(chainName, true, uids, &ret);
+    }
+    EXPECT_EQ(true, ret);
+    EXPECT_EQ((int) uids.size() + 9, iptablesRuleLineLength(IPTABLES_PATH, chainName.c_str()));
+    EXPECT_EQ((int) uids.size() + 15, iptablesRuleLineLength(IP6TABLES_PATH, chainName.c_str()));
+    EXPECT_EQ(true, iptablesNoSocketAllowRuleExists(chainName.c_str()));
+    EXPECT_EQ(true, iptablesEspAllowRuleExists(chainName.c_str()));
+    {
+        TimedOperation op("Clearing whitelist chain");
+        mNetd->firewallReplaceUidChain(chainName, false, noUids, &ret);
+    }
+    EXPECT_EQ(true, ret);
+    EXPECT_EQ(5, iptablesRuleLineLength(IPTABLES_PATH, chainName.c_str()));
+    EXPECT_EQ(5, iptablesRuleLineLength(IP6TABLES_PATH, chainName.c_str()));
 
-TEST_F(NetdBinderTest, InitialNetworksExist) {
-    EXPECT_TRUE(testNetworkExistsButCannotConnect(mNetd, sTun, INetd::DUMMY_NET_ID));
-    EXPECT_TRUE(testNetworkExistsButCannotConnect(mNetd, sTun, INetd::LOCAL_NET_ID));
-    EXPECT_TRUE(testNetworkExistsButCannotConnect(mNetd, sTun, INetd::UNREACHABLE_NET_ID));
-    EXPECT_FALSE(testNetworkExistsButCannotConnect(mNetd, sTun, 77 /* not exist */));
+    {
+        TimedOperation op(StringPrintf("Programming %d-UID blacklist chain", kNumUids));
+        mNetd->firewallReplaceUidChain(chainName, false, uids, &ret);
+    }
+    EXPECT_EQ(true, ret);
+    EXPECT_EQ((int) uids.size() + 5, iptablesRuleLineLength(IPTABLES_PATH, chainName.c_str()));
+    EXPECT_EQ((int) uids.size() + 5, iptablesRuleLineLength(IP6TABLES_PATH, chainName.c_str()));
+    EXPECT_EQ(false, iptablesNoSocketAllowRuleExists(chainName.c_str()));
+    EXPECT_EQ(false, iptablesEspAllowRuleExists(chainName.c_str()));
+
+    {
+        TimedOperation op("Clearing blacklist chain");
+        mNetd->firewallReplaceUidChain(chainName, false, noUids, &ret);
+    }
+    EXPECT_EQ(true, ret);
+    EXPECT_EQ(5, iptablesRuleLineLength(IPTABLES_PATH, chainName.c_str()));
+    EXPECT_EQ(5, iptablesRuleLineLength(IP6TABLES_PATH, chainName.c_str()));
+
+    // Check that the call fails if iptables returns an error.
+    std::string veryLongStringName = "netd_binder_test_UnacceptablyLongIptablesChainName";
+    mNetd->firewallReplaceUidChain(veryLongStringName, true, noUids, &ret);
+    EXPECT_EQ(false, ret);
 }
 
 TEST_F(NetdBinderTest, IpSecTunnelInterface) {
@@ -564,22 +507,14 @@
 }
 
 static bool ipRuleExistsForRange(const uint32_t priority, const UidRangeParcel& range,
-                                 const std::string& action, const char* ipVersion,
-                                 const char* oif) {
+                                 const std::string& action, const char* ipVersion) {
     // Output looks like this:
-    //   "<priority>:\tfrom all iif lo oif netdc0ca6 uidrange 500000-500000 lookup netdc0ca6"
-    //   "<priority>:\tfrom all fwmark 0x0/0x20000 iif lo uidrange 1000-2000 prohibit"
+    //   "12500:\tfrom all fwmark 0x0/0x20000 iif lo uidrange 1000-2000 prohibit"
     std::vector<std::string> rules = listIpRules(ipVersion);
 
     std::string prefix = StringPrintf("%" PRIu32 ":", priority);
-    std::string suffix;
-    if (oif) {
-        suffix = StringPrintf(" iif lo oif %s uidrange %d-%d %s\n", oif, range.start, range.stop,
-                              action.c_str());
-    } else {
-        suffix = StringPrintf(" iif lo uidrange %d-%d %s\n", range.start, range.stop,
-                              action.c_str());
-    }
+    std::string suffix =
+            StringPrintf(" iif lo uidrange %d-%d %s\n", range.start, range.stop, action.c_str());
     for (const auto& line : rules) {
         if (android::base::StartsWith(line, prefix) && android::base::EndsWith(line, suffix)) {
             return true;
@@ -588,18 +523,12 @@
     return false;
 }
 
-// Overloads function with oif parameter for VPN rules compare.
-static bool ipRuleExistsForRange(const uint32_t priority, const UidRangeParcel& range,
-                                 const std::string& action, const char* oif) {
-    bool existsIp4 = ipRuleExistsForRange(priority, range, action, IP_RULE_V4, oif);
-    bool existsIp6 = ipRuleExistsForRange(priority, range, action, IP_RULE_V6, oif);
-    EXPECT_EQ(existsIp4, existsIp6);
-    return existsIp4;
-}
-
 static bool ipRuleExistsForRange(const uint32_t priority, const UidRangeParcel& range,
                                  const std::string& action) {
-    return ipRuleExistsForRange(priority, range, action, nullptr);
+    bool existsIp4 = ipRuleExistsForRange(priority, range, action, IP_RULE_V4);
+    bool existsIp6 = ipRuleExistsForRange(priority, range, action, IP_RULE_V6);
+    EXPECT_EQ(existsIp4, existsIp6);
+    return existsIp4;
 }
 
 namespace {
@@ -612,31 +541,14 @@
     return res;
 }
 
-NativeUidRangeConfig makeNativeUidRangeConfig(unsigned netId,
-                                              std::vector<UidRangeParcel>&& uidRanges,
-                                              uint32_t subPriority) {
-    NativeUidRangeConfig res;
-    res.netId = netId;
-    res.uidRanges = uidRanges;
-    res.subPriority = subPriority;
-
-    return res;
-}
-
 }  // namespace
 
 TEST_F(NetdBinderTest, NetworkInterfaces) {
-    auto config = makeNativeNetworkConfig(TEST_NETID1, NativeNetworkType::PHYSICAL,
-                                          INetd::PERMISSION_NONE, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
-    EXPECT_EQ(EEXIST, mNetd->networkCreate(config).serviceSpecificErrorCode());
-
-    config.networkType = NativeNetworkType::VIRTUAL;
-    config.secure = true;
-    EXPECT_EQ(EEXIST, mNetd->networkCreate(config).serviceSpecificErrorCode());
-
-    config.netId = TEST_NETID2;
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
+    EXPECT_EQ(EEXIST, mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE)
+                              .serviceSpecificErrorCode());
+    EXPECT_EQ(EEXIST, mNetd->networkCreateVpn(TEST_NETID1, true).serviceSpecificErrorCode());
+    EXPECT_TRUE(mNetd->networkCreateVpn(TEST_NETID2, true).isOk());
 
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
     EXPECT_EQ(EBUSY,
@@ -649,49 +561,51 @@
 }
 
 TEST_F(NetdBinderTest, NetworkUidRules) {
-    auto config = makeNativeNetworkConfig(TEST_NETID1, NativeNetworkType::VIRTUAL,
-                                          INetd::PERMISSION_NONE, true);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
-    EXPECT_EQ(EEXIST, mNetd->networkCreate(config).serviceSpecificErrorCode());
+    const uint32_t RULE_PRIORITY_SECURE_VPN = 12000;
+
+    EXPECT_TRUE(mNetd->networkCreateVpn(TEST_NETID1, true).isOk());
+    EXPECT_EQ(EEXIST, mNetd->networkCreateVpn(TEST_NETID1, true).serviceSpecificErrorCode());
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
 
     std::vector<UidRangeParcel> uidRanges = {makeUidRangeParcel(BASE_UID + 8005, BASE_UID + 8012),
                                              makeUidRangeParcel(BASE_UID + 8090, BASE_UID + 8099)};
     UidRangeParcel otherRange = makeUidRangeParcel(BASE_UID + 8190, BASE_UID + 8299);
-    std::string action = StringPrintf("lookup %s ", sTun.name().c_str());
+    std::string suffix = StringPrintf("lookup %s ", sTun.name().c_str());
 
     EXPECT_TRUE(mNetd->networkAddUidRanges(TEST_NETID1, uidRanges).isOk());
 
-    EXPECT_TRUE(ipRuleExistsForRange(RULE_PRIORITY_SECURE_VPN, uidRanges[0], action));
-    EXPECT_FALSE(ipRuleExistsForRange(RULE_PRIORITY_SECURE_VPN, otherRange, action));
+    EXPECT_TRUE(ipRuleExistsForRange(RULE_PRIORITY_SECURE_VPN, uidRanges[0], suffix));
+    EXPECT_FALSE(ipRuleExistsForRange(RULE_PRIORITY_SECURE_VPN, otherRange, suffix));
     EXPECT_TRUE(mNetd->networkRemoveUidRanges(TEST_NETID1, uidRanges).isOk());
-    EXPECT_FALSE(ipRuleExistsForRange(RULE_PRIORITY_SECURE_VPN, uidRanges[0], action));
+    EXPECT_FALSE(ipRuleExistsForRange(RULE_PRIORITY_SECURE_VPN, uidRanges[0], suffix));
 
     EXPECT_TRUE(mNetd->networkAddUidRanges(TEST_NETID1, uidRanges).isOk());
-    EXPECT_TRUE(ipRuleExistsForRange(RULE_PRIORITY_SECURE_VPN, uidRanges[1], action));
+    EXPECT_TRUE(ipRuleExistsForRange(RULE_PRIORITY_SECURE_VPN, uidRanges[1], suffix));
     EXPECT_TRUE(mNetd->networkDestroy(TEST_NETID1).isOk());
-    EXPECT_FALSE(ipRuleExistsForRange(RULE_PRIORITY_SECURE_VPN, uidRanges[1], action));
+    EXPECT_FALSE(ipRuleExistsForRange(RULE_PRIORITY_SECURE_VPN, uidRanges[1], suffix));
 
     EXPECT_EQ(ENONET, mNetd->networkDestroy(TEST_NETID1).serviceSpecificErrorCode());
 }
 
 TEST_F(NetdBinderTest, NetworkRejectNonSecureVpn) {
+    constexpr uint32_t RULE_PRIORITY = 12500;
+
     std::vector<UidRangeParcel> uidRanges = {makeUidRangeParcel(BASE_UID + 150, BASE_UID + 224),
                                              makeUidRangeParcel(BASE_UID + 226, BASE_UID + 300)};
     // Make sure no rules existed before calling commands.
     for (auto const& range : uidRanges) {
-        EXPECT_FALSE(ipRuleExistsForRange(RULE_PRIORITY_PROHIBIT_NON_VPN, range, "prohibit"));
+        EXPECT_FALSE(ipRuleExistsForRange(RULE_PRIORITY, range, "prohibit"));
     }
     // Create two valid rules.
     ASSERT_TRUE(mNetd->networkRejectNonSecureVpn(true, uidRanges).isOk());
     for (auto const& range : uidRanges) {
-        EXPECT_TRUE(ipRuleExistsForRange(RULE_PRIORITY_PROHIBIT_NON_VPN, range, "prohibit"));
+        EXPECT_TRUE(ipRuleExistsForRange(RULE_PRIORITY, range, "prohibit"));
     }
 
     // Remove the rules.
     ASSERT_TRUE(mNetd->networkRejectNonSecureVpn(false, uidRanges).isOk());
     for (auto const& range : uidRanges) {
-        EXPECT_FALSE(ipRuleExistsForRange(RULE_PRIORITY_PROHIBIT_NON_VPN, range, "prohibit"));
+        EXPECT_FALSE(ipRuleExistsForRange(RULE_PRIORITY, range, "prohibit"));
     }
 
     // Fail to remove the rules a second time after they are already deleted.
@@ -788,72 +702,6 @@
     checkSocketpairClosed(clientSocket, acceptedSocket);
 }
 
-TEST_F(NetdBinderTest, SocketDestroyLinkLocal) {
-    // Add the same link-local address to two interfaces.
-    const char* kLinkLocalAddress = "fe80::ace:d00d";
-
-    const struct addrinfo hints = {
-            .ai_family = AF_INET6,
-            .ai_socktype = SOCK_STREAM,
-            .ai_flags = AI_NUMERICHOST,
-    };
-
-    binder::Status status = mNetd->interfaceAddAddress(sTun.name(), kLinkLocalAddress, 64);
-    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
-    status = mNetd->interfaceAddAddress(sTun2.name(), kLinkLocalAddress, 64);
-    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
-
-    // Bind a listening socket to the address on each of two interfaces.
-    // The sockets must be open at the same time, because this test checks that SOCK_DESTROY only
-    // destroys the sockets on the interface where the address is deleted.
-    struct addrinfo* addrinfoList = nullptr;
-    int ret = getaddrinfo(kLinkLocalAddress, nullptr, &hints, &addrinfoList);
-    ScopedAddrinfo addrinfoCleanup(addrinfoList);
-    ASSERT_EQ(0, ret);
-
-    socklen_t len = addrinfoList[0].ai_addrlen;
-    sockaddr_in6 sin6_1 = *reinterpret_cast<sockaddr_in6*>(addrinfoList[0].ai_addr);
-    sockaddr_in6 sin6_2 = sin6_1;
-    sin6_1.sin6_scope_id = if_nametoindex(sTun.name().c_str());
-    sin6_2.sin6_scope_id = if_nametoindex(sTun2.name().c_str());
-
-    int s1 = socket(AF_INET6, SOCK_STREAM, 0);
-    ASSERT_EQ(0, bind(s1, reinterpret_cast<sockaddr*>(&sin6_1), len));
-    ASSERT_EQ(0, getsockname(s1, reinterpret_cast<sockaddr*>(&sin6_1), &len));
-
-    int s2 = socket(AF_INET6, SOCK_STREAM, 0);
-    ASSERT_EQ(0, bind(s2, reinterpret_cast<sockaddr*>(&sin6_2), len));
-    ASSERT_EQ(0, getsockname(s2, reinterpret_cast<sockaddr*>(&sin6_2), &len));
-
-    ASSERT_EQ(0, listen(s1, 10));
-    ASSERT_EQ(0, listen(s2, 10));
-
-    // Connect one client socket to each and accept the connections.
-    int c1 = socket(AF_INET6, SOCK_STREAM, 0);
-    int c2 = socket(AF_INET6, SOCK_STREAM, 0);
-    ASSERT_EQ(0, connect(c1, reinterpret_cast<sockaddr*>(&sin6_1), len));
-    ASSERT_EQ(0, connect(c2, reinterpret_cast<sockaddr*>(&sin6_2), len));
-    int a1 = accept(s1, nullptr, 0);
-    ASSERT_NE(-1, a1);
-    int a2 = accept(s2, nullptr, 0);
-    ASSERT_NE(-1, a2);
-
-    // Delete the address on sTun2.
-    status = mNetd->interfaceDelAddress(sTun2.name(), kLinkLocalAddress, 64);
-    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
-
-    // The sockets on sTun2 are closed, but the ones on sTun1 remain open.
-    char buf[1024];
-    EXPECT_EQ(-1, read(c2, buf, sizeof(buf)));
-    EXPECT_EQ(ECONNABORTED, errno);
-    // The blocking read above ensures that SOCK_DESTROY has completed.
-
-    EXPECT_EQ(3, write(a1, "foo", 3));
-    EXPECT_EQ(3, read(c1, buf, sizeof(buf)));
-    EXPECT_EQ(-1, write(a2, "foo", 3));
-    EXPECT_TRUE(errno == ECONNABORTED || errno == ECONNRESET);
-}
-
 namespace {
 
 int netmaskToPrefixLength(const uint8_t *buf, size_t buflen) {
@@ -972,7 +820,6 @@
             {"2001:db8::1", 64, 0, 0},
             {"2001:db8::2", 65, 0, 0},
             {"2001:db8::3", 128, 0, 0},
-            {"fe80::1234", 64, 0, 0},
             {"2001:db8::4", 129, EINVAL, EINVAL},
             {"foo:bar::bad", 64, EINVAL, EINVAL},
             {"2001:db8::1/64", 64, EINVAL, EINVAL},
@@ -1028,7 +875,7 @@
 }
 
 TEST_F(NetdBinderTest, GetProcSysNet) {
-    const char* LOOPBACK = "lo";
+    const char LOOPBACK[] = "lo";
     static const struct {
         const int ipversion;
         const int which;
@@ -1243,7 +1090,7 @@
 void expectIdletimerInterfaceRuleExists(const std::string& ifname, int timeout,
                                         const std::string& classLabel) {
     std::string IdletimerRule =
-            StringPrintf("timeout:%u label:%s send_nl_msg", timeout, classLabel.c_str());
+            StringPrintf("timeout:%u label:%s send_nl_msg:1", timeout, classLabel.c_str());
     for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
         EXPECT_TRUE(iptablesIdleTimerInterfaceRuleExists(binary, IDLETIMER_RAW_PREROUTING, ifname,
                                                          IdletimerRule, RAW_TABLE));
@@ -1255,7 +1102,7 @@
 void expectIdletimerInterfaceRuleNotExists(const std::string& ifname, int timeout,
                                            const std::string& classLabel) {
     std::string IdletimerRule =
-            StringPrintf("timeout:%u label:%s send_nl_msg", timeout, classLabel.c_str());
+            StringPrintf("timeout:%u label:%s send_nl_msg:1", timeout, classLabel.c_str());
     for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
         EXPECT_FALSE(iptablesIdleTimerInterfaceRuleExists(binary, IDLETIMER_RAW_PREROUTING, ifname,
                                                           IdletimerRule, RAW_TABLE));
@@ -1408,9 +1255,7 @@
     EXPECT_EQ(ENODEV, status.serviceSpecificErrorCode());
 
     // ... so create a test physical network and add our tun to it.
-    const auto& config = makeNativeNetworkConfig(TEST_NETID1, NativeNetworkType::PHYSICAL,
-                                                 INetd::PERMISSION_NONE, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
 
     // Prefix must be 96 bits long.
@@ -1500,8 +1345,7 @@
 }
 
 void expectIpfwdRuleExists(const char* fromIf, const char* toIf) {
-    std::string ipfwdRule =
-            StringPrintf("%u:\tfrom all iif %s lookup %s ", RULE_PRIORITY_TETHERING, fromIf, toIf);
+    std::string ipfwdRule = StringPrintf("18000:\tfrom all iif %s lookup %s ", fromIf, toIf);
 
     for (const auto& ipVersion : {IP_RULE_V4, IP_RULE_V6}) {
         EXPECT_TRUE(ipRuleIpfwdExists(ipVersion, ipfwdRule));
@@ -1509,8 +1353,7 @@
 }
 
 void expectIpfwdRuleNotExists(const char* fromIf, const char* toIf) {
-    std::string ipfwdRule =
-            StringPrintf("%u:\tfrom all iif %s lookup %s ", RULE_PRIORITY_TETHERING, fromIf, toIf);
+    std::string ipfwdRule = StringPrintf("18000:\tfrom all iif %s lookup %s ", fromIf, toIf);
 
     for (const auto& ipVersion : {IP_RULE_V4, IP_RULE_V6}) {
         EXPECT_FALSE(ipRuleIpfwdExists(ipVersion, ipfwdRule));
@@ -1574,13 +1417,9 @@
 
 TEST_F(NetdBinderTest, TestIpfwdAddRemoveInterfaceForward) {
     // Add test physical network
-    auto config = makeNativeNetworkConfig(TEST_NETID1, NativeNetworkType::PHYSICAL,
-                                          INetd::PERMISSION_NONE, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
-
-    config.netId = TEST_NETID2;
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID2, INetd::PERMISSION_NONE).isOk());
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID2, sTun2.name()).isOk());
 
     binder::Status status = mNetd->ipfwdAddInterfaceForward(sTun.name(), sTun2.name());
@@ -1598,6 +1437,7 @@
 constexpr char BANDWIDTH_OUTPUT[] = "bw_OUTPUT";
 constexpr char BANDWIDTH_FORWARD[] = "bw_FORWARD";
 constexpr char BANDWIDTH_NAUGHTY[] = "bw_penalty_box";
+constexpr char BANDWIDTH_NICE[] = "bw_happy_box";
 constexpr char BANDWIDTH_ALERT[] = "bw_global_alert";
 
 // TODO: Move iptablesTargetsExists and listIptablesRuleByTable to the top.
@@ -1690,15 +1530,29 @@
     expectXtQuotaValueEqual(globalAlertName, alertBytes);
 }
 
+void expectBandwidthManipulateSpecialAppRuleExists(const char* chain, const char* target, int uid) {
+    std::string uidRule = StringPrintf("owner UID match %u", uid);
+
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_TRUE(iptablesTargetsExists(binary, 1, FILTER_TABLE, chain, target, uidRule));
+    }
+}
+
+void expectBandwidthManipulateSpecialAppRuleDoesNotExist(const char* chain, int uid) {
+    std::string uidRule = StringPrintf("owner UID match %u", uid);
+
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_FALSE(iptablesRuleExists(binary, chain, uidRule));
+    }
+}
+
 }  // namespace
 
 TEST_F(NetdBinderTest, BandwidthSetRemoveInterfaceQuota) {
     long testQuotaBytes = 5550;
 
     // Add test physical network
-    const auto& config = makeNativeNetworkConfig(TEST_NETID1, NativeNetworkType::PHYSICAL,
-                                                 INetd::PERMISSION_NONE, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
 
     binder::Status status = mNetd->bandwidthSetInterfaceQuota(sTun.name(), testQuotaBytes);
@@ -1716,9 +1570,7 @@
 TEST_F(NetdBinderTest, BandwidthSetRemoveInterfaceAlert) {
     long testAlertBytes = 373;
     // Add test physical network
-    const auto& config = makeNativeNetworkConfig(TEST_NETID1, NativeNetworkType::PHYSICAL,
-                                                 INetd::PERMISSION_NONE, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
     // Need to have a prior interface quota set to set an alert
     binder::Status status = mNetd->bandwidthSetInterfaceQuota(sTun.name(), testAlertBytes);
@@ -1752,6 +1604,34 @@
     expectBandwidthGlobalAlertRuleExists(testAlertBytes);
 }
 
+TEST_F(NetdBinderTest, BandwidthManipulateSpecialApp) {
+    SKIP_IF_BPF_SUPPORTED;
+
+    int32_t uid = randomUid();
+    static const char targetReject[] = "REJECT";
+    static const char targetReturn[] = "RETURN";
+
+    // add NaughtyApp
+    binder::Status status = mNetd->bandwidthAddNaughtyApp(uid);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectBandwidthManipulateSpecialAppRuleExists(BANDWIDTH_NAUGHTY, targetReject, uid);
+
+    // remove NaughtyApp
+    status = mNetd->bandwidthRemoveNaughtyApp(uid);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectBandwidthManipulateSpecialAppRuleDoesNotExist(BANDWIDTH_NAUGHTY, uid);
+
+    // add NiceApp
+    status = mNetd->bandwidthAddNiceApp(uid);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectBandwidthManipulateSpecialAppRuleExists(BANDWIDTH_NICE, targetReturn, uid);
+
+    // remove NiceApp
+    status = mNetd->bandwidthRemoveNiceApp(uid);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectBandwidthManipulateSpecialAppRuleDoesNotExist(BANDWIDTH_NICE, uid);
+}
+
 namespace {
 
 std::string ipRouteString(const std::string& ifName, const std::string& dst,
@@ -1815,8 +1695,7 @@
 
 void expectNetworkDefaultIpRuleExists(const char* ifName) {
     std::string networkDefaultRule =
-            StringPrintf("%u:\tfrom all fwmark 0x0/0xffff iif lo lookup %s",
-                         RULE_PRIORITY_DEFAULT_NETWORK, ifName);
+            StringPrintf("22000:\tfrom all fwmark 0x0/0xffff iif lo lookup %s", ifName);
 
     for (const auto& ipVersion : {IP_RULE_V4, IP_RULE_V6}) {
         EXPECT_TRUE(ipRuleExists(ipVersion, networkDefaultRule));
@@ -1824,8 +1703,7 @@
 }
 
 void expectNetworkDefaultIpRuleDoesNotExist() {
-    std::string networkDefaultRule =
-            StringPrintf("%u:\tfrom all fwmark 0x0/0xffff iif lo", RULE_PRIORITY_DEFAULT_NETWORK);
+    static const char networkDefaultRule[] = "22000:\tfrom all fwmark 0x0/0xffff iif lo";
 
     for (const auto& ipVersion : {IP_RULE_V4, IP_RULE_V6}) {
         EXPECT_FALSE(ipRuleExists(ipVersion, networkDefaultRule));
@@ -1836,19 +1714,16 @@
     std::string networkPermissionRule = "";
     switch (permission) {
         case INetd::PERMISSION_NONE:
-            networkPermissionRule =
-                    StringPrintf("%u:\tfrom all fwmark 0x1ffdd/0x1ffff iif lo lookup %s",
-                                 RULE_PRIORITY_EXPLICIT_NETWORK, ifName);
+            networkPermissionRule = StringPrintf(
+                    "13000:\tfrom all fwmark 0x1ffdd/0x1ffff iif lo lookup %s", ifName);
             break;
         case INetd::PERMISSION_NETWORK:
-            networkPermissionRule =
-                    StringPrintf("%u:\tfrom all fwmark 0x5ffdd/0x5ffff iif lo lookup %s",
-                                 RULE_PRIORITY_EXPLICIT_NETWORK, ifName);
+            networkPermissionRule = StringPrintf(
+                    "13000:\tfrom all fwmark 0x5ffdd/0x5ffff iif lo lookup %s", ifName);
             break;
         case INetd::PERMISSION_SYSTEM:
-            networkPermissionRule =
-                    StringPrintf("%u:\tfrom all fwmark 0xdffdd/0xdffff iif lo lookup %s",
-                                 RULE_PRIORITY_EXPLICIT_NETWORK, ifName);
+            networkPermissionRule = StringPrintf(
+                    "13000:\tfrom all fwmark 0xdffdd/0xdffff iif lo lookup %s", ifName);
             break;
     }
 
@@ -1929,9 +1804,7 @@
     const std::vector<int32_t> testUids = {testUid};
 
     // Add test physical network
-    const auto& config = makeNativeNetworkConfig(TEST_NETID1, NativeNetworkType::PHYSICAL,
-                                                 INetd::PERMISSION_NONE, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
 
     // Setup route for testing nextHop
@@ -2085,9 +1958,7 @@
 
 TEST_F(NetdBinderTest, NetworkPermissionDefault) {
     // Add test physical network
-    const auto& config = makeNativeNetworkConfig(TEST_NETID1, NativeNetworkType::PHYSICAL,
-                                                 INetd::PERMISSION_NONE, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
 
     // Get current default network NetId
@@ -2408,8 +2279,13 @@
 constexpr char FIREWALL_INPUT[] = "fw_INPUT";
 constexpr char FIREWALL_OUTPUT[] = "fw_OUTPUT";
 constexpr char FIREWALL_FORWARD[] = "fw_FORWARD";
+constexpr char FIREWALL_DOZABLE[] = "fw_dozable";
+constexpr char FIREWALL_POWERSAVE[] = "fw_powersave";
+constexpr char FIREWALL_STANDBY[] = "fw_standby";
+constexpr char targetReturn[] = "RETURN";
+constexpr char targetDrop[] = "DROP";
 
-void expectFirewallAllowlistMode() {
+void expectFirewallWhitelistMode() {
     static const char dropRule[] = "DROP       all";
     static const char rejectRule[] = "REJECT     all";
     for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
@@ -2419,7 +2295,7 @@
     }
 }
 
-void expectFirewallDenylistMode() {
+void expectFirewallBlacklistMode() {
     for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
         EXPECT_EQ(2, iptablesRuleLineLength(binary, FIREWALL_INPUT));
         EXPECT_EQ(2, iptablesRuleLineLength(binary, FIREWALL_OUTPUT));
@@ -2481,45 +2357,126 @@
     }
 }
 
+bool iptablesFirewallUidFirstRuleExists(const char* binary, const char* chainName,
+                                        const std::string& expectedTarget,
+                                        const std::string& expectedRule) {
+    std::vector<std::string> rules = listIptablesRuleByTable(binary, FILTER_TABLE, chainName);
+    int firstRuleIndex = 2;
+    if (rules.size() < 4) return false;
+    if (rules[firstRuleIndex].find(expectedTarget) != std::string::npos) {
+        if (rules[firstRuleIndex].find(expectedRule) != std::string::npos) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool iptablesFirewallUidLastRuleExists(const char* binary, const char* chainName,
+                                       const std::string& expectedTarget,
+                                       const std::string& expectedRule) {
+    std::vector<std::string> rules = listIptablesRuleByTable(binary, FILTER_TABLE, chainName);
+    int lastRuleIndex = rules.size() - 1;
+    if (lastRuleIndex < 0) return false;
+    if (rules[lastRuleIndex].find(expectedTarget) != std::string::npos) {
+        if (rules[lastRuleIndex].find(expectedRule) != std::string::npos) {
+            return true;
+        }
+    }
+    return false;
+}
+
+void expectFirewallUidFirstRuleExists(const char* chainName, int32_t uid) {
+    std::string uidRule = StringPrintf("owner UID match %u", uid);
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH})
+        EXPECT_TRUE(iptablesFirewallUidFirstRuleExists(binary, chainName, targetReturn, uidRule));
+}
+
+void expectFirewallUidFirstRuleDoesNotExist(const char* chainName, int32_t uid) {
+    std::string uidRule = StringPrintf("owner UID match %u", uid);
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH})
+        EXPECT_FALSE(iptablesFirewallUidFirstRuleExists(binary, chainName, targetReturn, uidRule));
+}
+
+void expectFirewallUidLastRuleExists(const char* chainName, int32_t uid) {
+    std::string uidRule = StringPrintf("owner UID match %u", uid);
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH})
+        EXPECT_TRUE(iptablesFirewallUidLastRuleExists(binary, chainName, targetDrop, uidRule));
+}
+
+void expectFirewallUidLastRuleDoesNotExist(const char* chainName, int32_t uid) {
+    std::string uidRule = StringPrintf("owner UID match %u", uid);
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH})
+        EXPECT_FALSE(iptablesFirewallUidLastRuleExists(binary, chainName, targetDrop, uidRule));
+}
+
+bool iptablesFirewallChildChainsLastRuleExists(const char* binary, const char* chainName) {
+    std::vector<std::string> inputRules =
+            listIptablesRuleByTable(binary, FILTER_TABLE, FIREWALL_INPUT);
+    std::vector<std::string> outputRules =
+            listIptablesRuleByTable(binary, FILTER_TABLE, FIREWALL_OUTPUT);
+    int inputLastRuleIndex = inputRules.size() - 1;
+    int outputLastRuleIndex = outputRules.size() - 1;
+
+    if (inputLastRuleIndex < 0 || outputLastRuleIndex < 0) return false;
+    if (inputRules[inputLastRuleIndex].find(chainName) != std::string::npos) {
+        if (outputRules[outputLastRuleIndex].find(chainName) != std::string::npos) {
+            return true;
+        }
+    }
+    return false;
+}
+
+void expectFirewallChildChainsLastRuleExists(const char* chainRule) {
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH})
+        EXPECT_TRUE(iptablesFirewallChildChainsLastRuleExists(binary, chainRule));
+}
+
+void expectFirewallChildChainsLastRuleDoesNotExist(const char* chainRule) {
+    for (const auto& binary : {IPTABLES_PATH, IP6TABLES_PATH}) {
+        EXPECT_FALSE(iptablesRuleExists(binary, FIREWALL_INPUT, chainRule));
+        EXPECT_FALSE(iptablesRuleExists(binary, FIREWALL_OUTPUT, chainRule));
+    }
+}
+
 }  // namespace
 
 TEST_F(NetdBinderTest, FirewallSetFirewallType) {
-    binder::Status status = mNetd->firewallSetFirewallType(INetd::FIREWALL_ALLOWLIST);
+    binder::Status status = mNetd->firewallSetFirewallType(INetd::FIREWALL_WHITELIST);
     EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
-    expectFirewallAllowlistMode();
+    expectFirewallWhitelistMode();
 
-    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_DENYLIST);
+    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_BLACKLIST);
     EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
-    expectFirewallDenylistMode();
+    expectFirewallBlacklistMode();
 
     // set firewall type blacklist twice
-    mNetd->firewallSetFirewallType(INetd::FIREWALL_DENYLIST);
-    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_DENYLIST);
+    mNetd->firewallSetFirewallType(INetd::FIREWALL_BLACKLIST);
+    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_BLACKLIST);
     EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
-    expectFirewallDenylistMode();
+    expectFirewallBlacklistMode();
 
     // set firewall type whitelist twice
-    mNetd->firewallSetFirewallType(INetd::FIREWALL_ALLOWLIST);
-    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_ALLOWLIST);
+    mNetd->firewallSetFirewallType(INetd::FIREWALL_WHITELIST);
+    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_WHITELIST);
     EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
-    expectFirewallAllowlistMode();
+    expectFirewallWhitelistMode();
 
     // reset firewall type to default
-    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_DENYLIST);
+    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_BLACKLIST);
     EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
-    expectFirewallDenylistMode();
+    expectFirewallBlacklistMode();
 }
 
 TEST_F(NetdBinderTest, FirewallSetInterfaceRule) {
     // setinterfaceRule is not supported in BLACKLIST MODE
-    binder::Status status = mNetd->firewallSetFirewallType(INetd::FIREWALL_DENYLIST);
+    binder::Status status = mNetd->firewallSetFirewallType(INetd::FIREWALL_BLACKLIST);
     EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
 
     status = mNetd->firewallSetInterfaceRule(sTun.name(), INetd::FIREWALL_RULE_ALLOW);
     EXPECT_FALSE(status.isOk()) << status.exceptionMessage();
 
     // set WHITELIST mode first
-    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_ALLOWLIST);
+    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_WHITELIST);
     EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
 
     status = mNetd->firewallSetInterfaceRule(sTun.name(), INetd::FIREWALL_RULE_ALLOW);
@@ -2531,9 +2488,113 @@
     expectFireWallInterfaceRuleAllowDoesNotExist(sTun.name());
 
     // reset firewall mode to default
-    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_DENYLIST);
+    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_BLACKLIST);
     EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
-    expectFirewallDenylistMode();
+    expectFirewallBlacklistMode();
+}
+
+TEST_F(NetdBinderTest, FirewallSetUidRule) {
+    SKIP_IF_BPF_SUPPORTED;
+
+    int32_t uid = randomUid();
+
+    // Doze allow
+    binder::Status status = mNetd->firewallSetUidRule(INetd::FIREWALL_CHAIN_DOZABLE, uid,
+                                                      INetd::FIREWALL_RULE_ALLOW);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallUidFirstRuleExists(FIREWALL_DOZABLE, uid);
+
+    // Doze deny
+    status = mNetd->firewallSetUidRule(INetd::FIREWALL_CHAIN_DOZABLE, uid,
+                                       INetd::FIREWALL_RULE_DENY);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallUidFirstRuleDoesNotExist(FIREWALL_DOZABLE, uid);
+
+    // Powersave allow
+    status = mNetd->firewallSetUidRule(INetd::FIREWALL_CHAIN_POWERSAVE, uid,
+                                       INetd::FIREWALL_RULE_ALLOW);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallUidFirstRuleExists(FIREWALL_POWERSAVE, uid);
+
+    // Powersave deny
+    status = mNetd->firewallSetUidRule(INetd::FIREWALL_CHAIN_POWERSAVE, uid,
+                                       INetd::FIREWALL_RULE_DENY);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallUidFirstRuleDoesNotExist(FIREWALL_POWERSAVE, uid);
+
+    // Standby deny
+    status = mNetd->firewallSetUidRule(INetd::FIREWALL_CHAIN_STANDBY, uid,
+                                       INetd::FIREWALL_RULE_DENY);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallUidLastRuleExists(FIREWALL_STANDBY, uid);
+
+    // Standby allow
+    status = mNetd->firewallSetUidRule(INetd::FIREWALL_CHAIN_STANDBY, uid,
+                                       INetd::FIREWALL_RULE_ALLOW);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallUidLastRuleDoesNotExist(FIREWALL_STANDBY, uid);
+
+    // None deny in BLACKLIST
+    status = mNetd->firewallSetUidRule(INetd::FIREWALL_CHAIN_NONE, uid, INetd::FIREWALL_RULE_DENY);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallUidLastRuleExists(FIREWALL_INPUT, uid);
+    expectFirewallUidLastRuleExists(FIREWALL_OUTPUT, uid);
+
+    // None allow in BLACKLIST
+    status = mNetd->firewallSetUidRule(INetd::FIREWALL_CHAIN_NONE, uid, INetd::FIREWALL_RULE_ALLOW);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallUidLastRuleDoesNotExist(FIREWALL_INPUT, uid);
+    expectFirewallUidLastRuleDoesNotExist(FIREWALL_OUTPUT, uid);
+
+    // set firewall type whitelist twice
+    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_WHITELIST);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallWhitelistMode();
+
+    // None allow in WHITELIST
+    status = mNetd->firewallSetUidRule(INetd::FIREWALL_CHAIN_NONE, uid, INetd::FIREWALL_RULE_ALLOW);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallUidFirstRuleExists(FIREWALL_INPUT, uid);
+    expectFirewallUidFirstRuleExists(FIREWALL_OUTPUT, uid);
+
+    // None deny in WHITELIST
+    status = mNetd->firewallSetUidRule(INetd::FIREWALL_CHAIN_NONE, uid, INetd::FIREWALL_RULE_DENY);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallUidFirstRuleDoesNotExist(FIREWALL_INPUT, uid);
+    expectFirewallUidFirstRuleDoesNotExist(FIREWALL_OUTPUT, uid);
+
+    // reset firewall mode to default
+    status = mNetd->firewallSetFirewallType(INetd::FIREWALL_BLACKLIST);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallBlacklistMode();
+}
+
+TEST_F(NetdBinderTest, FirewallEnableDisableChildChains) {
+    SKIP_IF_BPF_SUPPORTED;
+
+    binder::Status status = mNetd->firewallEnableChildChain(INetd::FIREWALL_CHAIN_DOZABLE, true);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallChildChainsLastRuleExists(FIREWALL_DOZABLE);
+
+    status = mNetd->firewallEnableChildChain(INetd::FIREWALL_CHAIN_STANDBY, true);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallChildChainsLastRuleExists(FIREWALL_STANDBY);
+
+    status = mNetd->firewallEnableChildChain(INetd::FIREWALL_CHAIN_POWERSAVE, true);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallChildChainsLastRuleExists(FIREWALL_POWERSAVE);
+
+    status = mNetd->firewallEnableChildChain(INetd::FIREWALL_CHAIN_DOZABLE, false);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallChildChainsLastRuleDoesNotExist(FIREWALL_DOZABLE);
+
+    status = mNetd->firewallEnableChildChain(INetd::FIREWALL_CHAIN_STANDBY, false);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallChildChainsLastRuleDoesNotExist(FIREWALL_STANDBY);
+
+    status = mNetd->firewallEnableChildChain(INetd::FIREWALL_CHAIN_POWERSAVE, false);
+    EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
+    expectFirewallChildChainsLastRuleDoesNotExist(FIREWALL_POWERSAVE);
 }
 
 namespace {
@@ -2736,9 +2797,7 @@
     InterfaceConfigurationParcel interfaceCfgResult;
 
     // Add test physical network
-    const auto& config = makeNativeNetworkConfig(TEST_NETID1, NativeNetworkType::PHYSICAL,
-                                                 INetd::PERMISSION_NONE, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
 
     binder::Status status = mNetd->interfaceGetCfg(sTun.name(), &interfaceCfgResult);
@@ -2757,9 +2816,7 @@
     std::vector<std::string> downFlags = {"down"};
 
     // Add test physical network
-    const auto& config = makeNativeNetworkConfig(TEST_NETID1, NativeNetworkType::PHYSICAL,
-                                                 INetd::PERMISSION_NONE, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
 
     // Set tun interface down.
@@ -2797,9 +2854,7 @@
     std::vector<std::string> noFlags{};
 
     // Add test physical network
-    const auto& config = makeNativeNetworkConfig(TEST_NETID1, NativeNetworkType::PHYSICAL,
-                                                 INetd::PERMISSION_NONE, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
 
     auto interfaceCfg = makeInterfaceCfgParcel(sTun.name(), testAddr, testPrefixLength, noFlags);
@@ -2817,9 +2872,7 @@
 
 TEST_F(NetdBinderTest, InterfaceSetEnableIPv6) {
     // Add test physical network
-    const auto& config = makeNativeNetworkConfig(TEST_NETID1, NativeNetworkType::PHYSICAL,
-                                                 INetd::PERMISSION_NONE, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
 
     // disable
@@ -2840,9 +2893,7 @@
     const int testMtu = 1200;
 
     // Add test physical network
-    const auto& config = makeNativeNetworkConfig(TEST_NETID1, NativeNetworkType::PHYSICAL,
-                                                 INetd::PERMISSION_NONE, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
 
     binder::Status status = mNetd->interfaceSetMtu(sTun.name(), testMtu);
@@ -3008,6 +3059,8 @@
 }  // namespace
 
 TEST_F(NetdBinderTest, TestInternetPermission) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     std::vector<int32_t> appUids = {TEST_UID1, TEST_UID2};
 
     mNetd->trafficSetNetPermForUids(INetd::PERMISSION_INTERNET, appUids);
@@ -3128,9 +3181,7 @@
     }
 
     // Add test physical network
-    const auto& config = makeNativeNetworkConfig(TEST_NETID1, NativeNetworkType::PHYSICAL,
-                                                 INetd::PERMISSION_NONE, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
 
     for (const auto& td : kTestData) {
@@ -3220,16 +3271,11 @@
     sTun2.init();
 
     // Create physical network with fallthroughNetId but not set it as default network
-    auto config = makeNativeNetworkConfig(fallthroughNetId, NativeNetworkType::PHYSICAL,
-                                          INetd::PERMISSION_NONE, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
+    EXPECT_TRUE(mNetd->networkCreatePhysical(fallthroughNetId, INetd::PERMISSION_NONE).isOk());
     EXPECT_TRUE(mNetd->networkAddInterface(fallthroughNetId, sTun.name()).isOk());
 
     // Create VPN with vpnNetId
-    config.netId = vpnNetId;
-    config.networkType = NativeNetworkType::VIRTUAL;
-    config.secure = secure;
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
+    EXPECT_TRUE(mNetd->networkCreateVpn(vpnNetId, secure).isOk());
 
     // Add uid to VPN
     EXPECT_TRUE(mNetd->networkAddUidRanges(vpnNetId, {makeUidRangeParcel(uid, uid)}).isOk());
@@ -3241,68 +3287,12 @@
     EXPECT_TRUE(mNetd->networkAddRoute(TEST_NETID2, sTun2.name(), "2001:db8::/32", "").isOk());
 }
 
-void NetdBinderTest::createAndSetDefaultNetwork(int netId, const std::string& interface,
-                                                int permission) {
-    // backup current default network.
-    ASSERT_TRUE(mNetd->networkGetDefault(&mStoredDefaultNetwork).isOk());
-
-    const auto& config =
-            makeNativeNetworkConfig(netId, NativeNetworkType::PHYSICAL, permission, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
-    EXPECT_TRUE(mNetd->networkAddInterface(netId, interface).isOk());
-    EXPECT_TRUE(mNetd->networkSetDefault(netId).isOk());
-}
-
-void NetdBinderTest::createPhysicalNetwork(int netId, const std::string& interface,
-                                           int permission) {
-    const auto& config =
-            makeNativeNetworkConfig(netId, NativeNetworkType::PHYSICAL, permission, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
-    EXPECT_TRUE(mNetd->networkAddInterface(netId, interface).isOk());
-}
-
-// 1. Create a physical network on sTun, and set it as the system default network.
-// 2. Create another physical network on sTun2.
-void NetdBinderTest::createDefaultAndOtherPhysicalNetwork(int defaultNetId, int otherNetId) {
-    createAndSetDefaultNetwork(defaultNetId, sTun.name());
-    EXPECT_TRUE(mNetd->networkAddRoute(defaultNetId, sTun.name(), "::/0", "").isOk());
-
-    createPhysicalNetwork(otherNetId, sTun2.name());
-    EXPECT_TRUE(mNetd->networkAddRoute(otherNetId, sTun2.name(), "::/0", "").isOk());
-}
-
-// 1. Create a system default network and a physical network.
-// 2. Create a VPN on sTun3.
-void NetdBinderTest::createVpnAndOtherPhysicalNetwork(int systemDefaultNetId, int otherNetId,
-                                                      int vpnNetId, bool secure) {
-    createDefaultAndOtherPhysicalNetwork(systemDefaultNetId, otherNetId);
-
-    auto config = makeNativeNetworkConfig(vpnNetId, NativeNetworkType::VIRTUAL,
-                                          INetd::PERMISSION_NONE, secure);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
-    EXPECT_TRUE(mNetd->networkAddInterface(vpnNetId, sTun3.name()).isOk());
-    EXPECT_TRUE(mNetd->networkAddRoute(vpnNetId, sTun3.name(), "2001:db8::/32", "").isOk());
-}
-
-// 1. Create system default network, a physical network (for per-app default), and a VPN.
-// 2. Add per-app uid ranges and VPN ranges.
-void NetdBinderTest::createVpnAndAppDefaultNetworkWithUid(
-        int systemDefaultNetId, int appDefaultNetId, int vpnNetId, bool secure,
-        std::vector<UidRangeParcel>&& appDefaultUidRanges,
-        std::vector<UidRangeParcel>&& vpnUidRanges) {
-    createVpnAndOtherPhysicalNetwork(systemDefaultNetId, appDefaultNetId, vpnNetId, secure);
-    // add per-app uid ranges.
-    EXPECT_TRUE(mNetd->networkAddUidRanges(appDefaultNetId, appDefaultUidRanges).isOk());
-    // add VPN uid ranges.
-    EXPECT_TRUE(mNetd->networkAddUidRanges(vpnNetId, vpnUidRanges).isOk());
-}
-
 namespace {
 
 class ScopedUidChange {
   public:
     explicit ScopedUidChange(uid_t uid) : mInputUid(uid) {
-        mStoredUid = geteuid();
+        mStoredUid = getuid();
         if (mInputUid == mStoredUid) return;
         EXPECT_TRUE(seteuid(uid) == 0);
     }
@@ -3316,6 +3306,8 @@
     uid_t mStoredUid;
 };
 
+constexpr uint32_t RULE_PRIORITY_VPN_FALLTHROUGH = 21000;
+
 void clearQueue(int tunFd) {
     char buf[4096];
     int ret;
@@ -3324,18 +3316,17 @@
     } while (ret > 0);
 }
 
-void checkDataReceived(int udpSocket, int tunFd, sockaddr* dstAddr, int addrLen) {
+void checkDataReceived(int udpSocket, int tunFd) {
     char buf[4096] = {};
     // Clear tunFd's queue before write something because there might be some
     // arbitrary packets in the queue. (e.g. ICMPv6 packet)
     clearQueue(tunFd);
-    EXPECT_EQ(4, sendto(udpSocket, "foo", sizeof("foo"), 0, dstAddr, addrLen));
+    EXPECT_EQ(4, write(udpSocket, "foo", sizeof("foo")));
     // TODO: extract header and verify data
     EXPECT_GT(read(tunFd, buf, sizeof(buf)), 0);
 }
 
-bool sendIPv6PacketFromUid(uid_t uid, const in6_addr& dstAddr, Fwmark* fwmark, int tunFd,
-                           bool doConnect = true) {
+bool sendIPv6PacketFromUid(uid_t uid, const in6_addr& dstAddr, Fwmark* fwmark, int tunFd) {
     ScopedUidChange scopedUidChange(uid);
     unique_fd testSocket(socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0));
     if (testSocket < 0) return false;
@@ -3345,51 +3336,15 @@
             .sin6_port = 42,
             .sin6_addr = dstAddr,
     };
-    if (doConnect && connect(testSocket, (sockaddr*)&dst6, sizeof(dst6)) == -1) return false;
-
+    int res = connect(testSocket, (sockaddr*)&dst6, sizeof(dst6));
     socklen_t fwmarkLen = sizeof(fwmark->intValue);
     EXPECT_NE(-1, getsockopt(testSocket, SOL_SOCKET, SO_MARK, &(fwmark->intValue), &fwmarkLen));
+    if (res == -1) return false;
 
     char addr[INET6_ADDRSTRLEN];
     inet_ntop(AF_INET6, &dstAddr, addr, INET6_ADDRSTRLEN);
-    SCOPED_TRACE(StringPrintf("sendIPv6Packet, addr: %s, uid: %u, doConnect: %s", addr, uid,
-                              doConnect ? "true" : "false"));
-    if (doConnect) {
-        checkDataReceived(testSocket, tunFd, nullptr, 0);
-    } else {
-        checkDataReceived(testSocket, tunFd, (sockaddr*)&dst6, sizeof(dst6));
-    }
-    return true;
-}
-
-// Send an IPv6 packet from the uid. Expect to fail and get specified errno.
-bool sendIPv6PacketFromUidFail(uid_t uid, const in6_addr& dstAddr, Fwmark* fwmark, bool doConnect,
-                               int expectedErr) {
-    ScopedUidChange scopedUidChange(uid);
-    unique_fd s(socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0));
-    if (s < 0) return false;
-
-    const sockaddr_in6 dst6 = {
-            .sin6_family = AF_INET6,
-            .sin6_port = 42,
-            .sin6_addr = dstAddr,
-    };
-    if (doConnect) {
-        if (connect(s, (sockaddr*)&dst6, sizeof(dst6)) == 0) return false;
-        if (errno != expectedErr) return false;
-    }
-
-    socklen_t fwmarkLen = sizeof(fwmark->intValue);
-    EXPECT_NE(-1, getsockopt(s, SOL_SOCKET, SO_MARK, &(fwmark->intValue), &fwmarkLen));
-
-    char addr[INET6_ADDRSTRLEN];
-    inet_ntop(AF_INET6, &dstAddr, addr, INET6_ADDRSTRLEN);
-    SCOPED_TRACE(StringPrintf("sendIPv6PacketFail, addr: %s, uid: %u, doConnect: %s", addr, uid,
-                              doConnect ? "true" : "false"));
-    if (!doConnect) {
-        if (sendto(s, "foo", sizeof("foo"), 0, (sockaddr*)&dst6, sizeof(dst6)) == 0) return false;
-        if (errno != expectedErr) return false;
-    }
+    SCOPED_TRACE(StringPrintf("sendIPv6PacketFromUid, addr: %s, uid: %u", addr, uid));
+    checkDataReceived(testSocket, tunFd);
     return true;
 }
 
@@ -3522,24 +3477,24 @@
     // Save current default network.
     ASSERT_TRUE(mNetd->networkGetDefault(&mStoredDefaultNetwork).isOk());
 
+    in6_addr v6Addr = {
+            {// 2001:db8:cafe::8888
+             .u6_addr8 = {0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe, 0, 0, 0, 0, 0, 0, 0, 0, 0x88, 0x88}}};
     // Add test physical network 1 and set as default network.
-    auto config = makeNativeNetworkConfig(TEST_NETID1, NativeNetworkType::PHYSICAL,
-                                          INetd::PERMISSION_NONE, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
     EXPECT_TRUE(mNetd->networkAddRoute(TEST_NETID1, sTun.name(), "2001:db8::/32", "").isOk());
     EXPECT_TRUE(mNetd->networkSetDefault(TEST_NETID1).isOk());
     // Add test physical network 2
-    config.netId = TEST_NETID2;
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID2, INetd::PERMISSION_NONE).isOk());
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID2, sTun2.name()).isOk());
 
     // Get fwmark for network 1.
     MarkMaskParcel maskMarkNet1;
     ASSERT_TRUE(mNetd->getFwmarkForNetwork(TEST_NETID1, &maskMarkNet1).isOk());
 
-    uint32_t fwmarkTcp = createIpv6SocketAndCheckMark(SOCK_STREAM, V6_ADDR);
-    uint32_t fwmarkUdp = createIpv6SocketAndCheckMark(SOCK_DGRAM, V6_ADDR);
+    uint32_t fwmarkTcp = createIpv6SocketAndCheckMark(SOCK_STREAM, v6Addr);
+    uint32_t fwmarkUdp = createIpv6SocketAndCheckMark(SOCK_DGRAM, v6Addr);
     EXPECT_EQ(maskMarkNet1.mark, static_cast<int>(fwmarkTcp & maskMarkNet1.mask));
     EXPECT_EQ(maskMarkNet1.mark, static_cast<int>(fwmarkUdp & maskMarkNet1.mask));
 
@@ -3574,8 +3529,9 @@
 
 }  // namespace
 
-// TODO: probably remove the test because TetherOffload* binder calls are deprecated.
-TEST_F(NetdBinderTest, DISABLED_TetherOffloadRule) {
+TEST_F(NetdBinderTest, TetherOffloadRule) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     // TODO: Perhaps verify invalid interface index once the netd handle the error in methods.
     constexpr uint32_t kIfaceInt = 101;
     constexpr uint32_t kIfaceExt = 102;
@@ -3689,27 +3645,7 @@
     return false;
 }
 
-static bool tcQdiscExists(const std::string& interface) {
-    std::string command = StringPrintf("tc qdisc show dev %s", interface.c_str());
-    std::vector<std::string> lines = runCommand(command);
-    for (const auto& line : lines) {
-        if (StartsWith(line, "qdisc clsact ffff:")) return true;
-    }
-    return false;
-}
-
-static bool tcFilterExists(const std::string& interface) {
-    std::string command = StringPrintf("tc filter show dev %s ingress", interface.c_str());
-    std::vector<std::string> lines = runCommand(command);
-    const std::basic_regex regex("^filter .* bpf .* prog_offload_schedcls_tether_.*$");
-    for (const auto& line : lines) {
-        if (std::regex_match(Trim(line), regex)) return true;
-    }
-    return false;
-}
-
-// TODO: probably remove the test because TetherOffload* binder calls are deprecated.
-TEST_F(NetdBinderTest, DISABLED_TetherOffloadForwarding) {
+TEST_F(NetdBinderTest, TetherOffloadForwarding) {
     SKIP_IF_EXTENDED_BPF_NOT_SUPPORTED;
 
     constexpr const char* kDownstreamPrefix = "2001:db8:2::/64";
@@ -3736,12 +3672,9 @@
 
     // Use one of the test's tun interfaces as upstream.
     // It must be part of a network or it will not have the clsact attached.
-    const auto& config = makeNativeNetworkConfig(TEST_NETID1, NativeNetworkType::PHYSICAL,
-                                                 INetd::PERMISSION_NONE, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
+    EXPECT_TRUE(mNetd->networkCreatePhysical(TEST_NETID1, INetd::PERMISSION_NONE).isOk());
     EXPECT_TRUE(mNetd->networkAddInterface(TEST_NETID1, sTun.name()).isOk());
     int fd1 = sTun.getFdForTesting();
-    EXPECT_TRUE(tcQdiscExists(sTun.name()));
 
     // Create our own tap as a downstream.
     TunInterface tap;
@@ -3759,7 +3692,6 @@
     status = mNetd->tetherInterfaceAdd(tap.name());
     EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
     expectTetherInterfaceConfigureForIPv6Router(tap.name());
-    EXPECT_TRUE(tcQdiscExists(tap.name()));
 
     // Can't easily use INetd::NEXTHOP_NONE because it is a String16 constant. Use "" instead.
     status = mNetd->networkAddRoute(INetd::LOCAL_NET_ID, tap.name(), kDownstreamPrefix, "");
@@ -3770,7 +3702,6 @@
     EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
     status = mNetd->ipfwdAddInterfaceForward(tap.name(), sTun.name());
     EXPECT_TRUE(status.isOk()) << status.exceptionMessage();
-    EXPECT_TRUE(tcFilterExists(sTun.name()));
 
     std::vector<uint8_t> kDummyMac = {02, 00, 00, 00, 00, 00};
     uint8_t* daddr = reinterpret_cast<uint8_t*>(&pkt.hdr.daddr);
@@ -3822,880 +3753,3 @@
     EXPECT_TRUE(mNetd->networkRemoveInterface(INetd::LOCAL_NET_ID, tap.name()).isOk());
     EXPECT_TRUE(mNetd->networkRemoveInterface(TEST_NETID1, sTun.name()).isOk());
 }
-
-namespace {
-
-std::vector<std::string> dumpService(const sp<IBinder>& binder) {
-    unique_fd localFd, remoteFd;
-    bool success = Pipe(&localFd, &remoteFd);
-    EXPECT_TRUE(success) << "Failed to open pipe for dumping: " << strerror(errno);
-    if (!success) return {};
-
-    // dump() blocks until another thread has consumed all its output.
-    std::thread dumpThread = std::thread([binder, remoteFd{std::move(remoteFd)}]() {
-        android::status_t ret = binder->dump(remoteFd, {});
-        EXPECT_EQ(android::OK, ret) << "Error dumping service: " << android::statusToString(ret);
-    });
-
-    std::string dumpContent;
-
-    EXPECT_TRUE(ReadFdToString(localFd.get(), &dumpContent))
-            << "Error during dump: " << strerror(errno);
-    dumpThread.join();
-
-    std::stringstream dumpStream(std::move(dumpContent));
-    std::vector<std::string> lines;
-    std::string line;
-    while (std::getline(dumpStream, line)) {
-        lines.push_back(line);
-    }
-
-    return lines;
-}
-
-}  // namespace
-
-TEST_F(NetdBinderTest, TestServiceDump) {
-    sp<IBinder> binder = INetd::asBinder(mNetd);
-    ASSERT_NE(nullptr, binder);
-
-    struct TestData {
-        // Expected contents of the dump command.
-        const std::string output;
-        // A regex that might be helpful in matching relevant lines in the output.
-        // Used to make it easier to add test cases for this code.
-        const std::string hintRegex;
-    };
-    std::vector<TestData> testData;
-
-    // Send some IPCs and for each one add an element to testData telling us what to expect.
-    const auto& config = makeNativeNetworkConfig(TEST_DUMP_NETID, NativeNetworkType::PHYSICAL,
-                                                 INetd::PERMISSION_NONE, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
-    testData.push_back(
-            {"networkCreate(NativeNetworkConfig{netId: 65123, networkType: PHYSICAL, "
-             "permission: 0, secure: false, vpnType: PLATFORM})",
-             "networkCreate.*65123"});
-
-    EXPECT_EQ(EEXIST, mNetd->networkCreate(config).serviceSpecificErrorCode());
-    testData.push_back(
-            {"networkCreate(NativeNetworkConfig{netId: 65123, networkType: PHYSICAL, "
-             "permission: 0, secure: false, vpnType: PLATFORM}) "
-             "-> ServiceSpecificException(17, \"File exists\")",
-             "networkCreate.*65123.*17"});
-
-    EXPECT_TRUE(mNetd->networkAddInterface(TEST_DUMP_NETID, sTun.name()).isOk());
-    testData.push_back({StringPrintf("networkAddInterface(65123, %s)", sTun.name().c_str()),
-                        StringPrintf("networkAddInterface.*65123.*%s", sTun.name().c_str())});
-
-    android::net::RouteInfoParcel parcel;
-    parcel.ifName = sTun.name();
-    parcel.destination = "2001:db8:dead:beef::/64";
-    parcel.nextHop = "fe80::dead:beef";
-    parcel.mtu = 1234;
-    EXPECT_TRUE(mNetd->networkAddRouteParcel(TEST_DUMP_NETID, parcel).isOk());
-    testData.push_back(
-            {StringPrintf("networkAddRouteParcel(65123, RouteInfoParcel{destination:"
-                          " 2001:db8:dead:beef::/64, ifName: %s, nextHop: fe80::dead:beef,"
-                          " mtu: 1234})",
-                          sTun.name().c_str()),
-             "networkAddRouteParcel.*65123.*dead:beef"});
-
-    EXPECT_TRUE(mNetd->networkDestroy(TEST_DUMP_NETID).isOk());
-    testData.push_back({"networkDestroy(65123)", "networkDestroy.*65123"});
-
-    // Send the service dump request to netd.
-    std::vector<std::string> lines = dumpService(binder);
-
-    // Basic regexp to match dump output lines. Matches the beginning and end of the line, and
-    // puts the output of the command itself into the first match group.
-    // Example: "      11-05 00:23:39.481 myCommand(args) <2.02ms>".
-    const std::basic_regex lineRegex(
-            "^      [0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[.][0-9]{3} "
-            "(.*)"
-            " <[0-9]+[.][0-9]{2}ms>$");
-
-    // For each element of testdata, check that the expected output appears in the dump output.
-    // If not, fail the test and use hintRegex to print similar lines to assist in debugging.
-    for (const TestData& td : testData) {
-        const bool found = std::any_of(lines.begin(), lines.end(), [&](const std::string& line) {
-            std::smatch match;
-            if (!std::regex_match(line, match, lineRegex)) return false;
-            return (match.size() == 2) && (match[1].str() == td.output);
-        });
-        EXPECT_TRUE(found) << "Didn't find line '" << td.output << "' in dumpsys output.";
-        if (found) continue;
-        std::cerr << "Similar lines" << std::endl;
-        for (const auto& line : lines) {
-            if (std::regex_search(line, std::basic_regex(td.hintRegex))) {
-                std::cerr << line << std::endl;
-            }
-        }
-    }
-}
-
-TEST_F(NetdBinderTest, DeprecatedTetherOffloadRuleAdd) {
-    TetherOffloadRuleParcel emptyRule;
-    auto status = mNetd->tetherOffloadRuleAdd(emptyRule);
-    ASSERT_FALSE(status.isOk());
-    ASSERT_EQ(status.exceptionCode(), binder::Status::EX_UNSUPPORTED_OPERATION);
-}
-
-TEST_F(NetdBinderTest, DeprecatedTetherOffloadRuleRemove) {
-    TetherOffloadRuleParcel emptyRule;
-    auto status = mNetd->tetherOffloadRuleRemove(emptyRule);
-    ASSERT_FALSE(status.isOk());
-    ASSERT_EQ(status.exceptionCode(), binder::Status::EX_UNSUPPORTED_OPERATION);
-}
-
-TEST_F(NetdBinderTest, DeprecatedTetherOffloadGetStats) {
-    std::vector<TetherStatsParcel> tetherStatsList;
-    auto status = mNetd->tetherOffloadGetStats(&tetherStatsList);
-    ASSERT_FALSE(status.isOk());
-    ASSERT_EQ(status.exceptionCode(), binder::Status::EX_UNSUPPORTED_OPERATION);
-}
-
-TEST_F(NetdBinderTest, DeprecatedTetherOffloadSetInterfaceQuota) {
-    auto status = mNetd->tetherOffloadSetInterfaceQuota(0 /* ifIndex */, 0 /* quotaBytes */);
-    ASSERT_FALSE(status.isOk());
-    ASSERT_EQ(status.exceptionCode(), binder::Status::EX_UNSUPPORTED_OPERATION);
-}
-
-TEST_F(NetdBinderTest, DeprecatedTetherOffloadGetAndClearStats) {
-    TetherStatsParcel tetherStats;
-    auto status = mNetd->tetherOffloadGetAndClearStats(0 /* ifIndex */, &tetherStats);
-    ASSERT_FALSE(status.isOk());
-    ASSERT_EQ(status.exceptionCode(), binder::Status::EX_UNSUPPORTED_OPERATION);
-}
-
-namespace {
-
-// aliases for better reading
-#define SYSTEM_DEFAULT_NETID TEST_NETID1
-#define APP_DEFAULT_NETID TEST_NETID2
-#define VPN_NETID TEST_NETID3
-
-void verifyAppUidRules(std::vector<bool>&& expectedResults, std::vector<UidRangeParcel>& uidRanges,
-                       const std::string& iface, uint32_t subPriority) {
-    ASSERT_EQ(expectedResults.size(), uidRanges.size());
-    if (iface.size()) {
-        std::string action = StringPrintf("lookup %s ", iface.c_str());
-        for (unsigned long i = 0; i < uidRanges.size(); i++) {
-            EXPECT_EQ(expectedResults[i],
-                      ipRuleExistsForRange(RULE_PRIORITY_UID_EXPLICIT_NETWORK + subPriority,
-                                           uidRanges[i], action));
-            EXPECT_EQ(expectedResults[i],
-                      ipRuleExistsForRange(RULE_PRIORITY_UID_IMPLICIT_NETWORK + subPriority,
-                                           uidRanges[i], action));
-            EXPECT_EQ(expectedResults[i],
-                      ipRuleExistsForRange(RULE_PRIORITY_UID_DEFAULT_NETWORK + subPriority,
-                                           uidRanges[i], action));
-        }
-    } else {
-        std::string action = "unreachable";
-        for (unsigned long i = 0; i < uidRanges.size(); i++) {
-            EXPECT_EQ(expectedResults[i],
-                      ipRuleExistsForRange(RULE_PRIORITY_UID_EXPLICIT_NETWORK + subPriority,
-                                           uidRanges[i], action));
-            EXPECT_EQ(expectedResults[i],
-                      ipRuleExistsForRange(RULE_PRIORITY_UID_IMPLICIT_NETWORK + subPriority,
-                                           uidRanges[i], action));
-            EXPECT_EQ(expectedResults[i],
-                      ipRuleExistsForRange(RULE_PRIORITY_UID_DEFAULT_UNREACHABLE + subPriority,
-                                           uidRanges[i], action));
-        }
-    }
-}
-
-void verifyAppUidRules(std::vector<bool>&& expectedResults, NativeUidRangeConfig& uidRangeConfig,
-                       const std::string& iface) {
-    verifyAppUidRules(move(expectedResults), uidRangeConfig.uidRanges, iface,
-                      uidRangeConfig.subPriority);
-}
-
-void verifyVpnUidRules(std::vector<bool>&& expectedResults, NativeUidRangeConfig& uidRangeConfig,
-                       const std::string& iface, bool secure) {
-    ASSERT_EQ(expectedResults.size(), uidRangeConfig.uidRanges.size());
-    std::string action = StringPrintf("lookup %s ", iface.c_str());
-
-    uint32_t priority;
-    if (secure) {
-        priority = RULE_PRIORITY_SECURE_VPN;
-    } else {
-        priority = RULE_PRIORITY_BYPASSABLE_VPN;
-    }
-    for (unsigned long i = 0; i < uidRangeConfig.uidRanges.size(); i++) {
-        EXPECT_EQ(expectedResults[i], ipRuleExistsForRange(priority + uidRangeConfig.subPriority,
-                                                           uidRangeConfig.uidRanges[i], action));
-        EXPECT_EQ(expectedResults[i],
-                  ipRuleExistsForRange(RULE_PRIORITY_EXPLICIT_NETWORK + uidRangeConfig.subPriority,
-                                       uidRangeConfig.uidRanges[i], action));
-        EXPECT_EQ(expectedResults[i],
-                  ipRuleExistsForRange(RULE_PRIORITY_OUTPUT_INTERFACE + uidRangeConfig.subPriority,
-                                       uidRangeConfig.uidRanges[i], action, iface.c_str()));
-    }
-}
-
-constexpr int SUB_PRIORITY_1 = UidRanges::DEFAULT_SUB_PRIORITY + 1;
-constexpr int SUB_PRIORITY_2 = UidRanges::DEFAULT_SUB_PRIORITY + 2;
-
-constexpr int IMPLICITLY_SELECT = 0;
-constexpr int EXPLICITLY_SELECT = 1;
-constexpr int UNCONNECTED_SOCKET = 2;
-
-// 1. Send data with the specified UID, on a connected or unconnected socket.
-// 2. Verify if data is received from the specified fd. The fd should belong to a TUN, which has
-//    been assigned to the test network.
-// 3. Verify if fwmark of data is correct.
-// Note: This is a helper function used by per-app default network tests. It does not implement full
-// fwmark logic in netd, and it's currently sufficient. Extension may be required for more
-// complicated tests.
-void expectPacketSentOnNetId(uid_t uid, unsigned netId, int fd, int selectionMode) {
-    Fwmark fwmark;
-    const bool doConnect = (selectionMode != UNCONNECTED_SOCKET);
-    EXPECT_TRUE(sendIPv6PacketFromUid(uid, V6_ADDR, &fwmark, fd, doConnect));
-
-    Fwmark expected;
-    expected.netId = netId;
-    expected.explicitlySelected = (selectionMode == EXPLICITLY_SELECT);
-    if (uid == AID_ROOT && selectionMode == EXPLICITLY_SELECT) {
-        expected.protectedFromVpn = true;
-    } else {
-        expected.protectedFromVpn = false;
-    }
-    if (selectionMode == UNCONNECTED_SOCKET) {
-        expected.permission = PERMISSION_NONE;
-    } else {
-        expected.permission = (uid == AID_ROOT) ? PERMISSION_SYSTEM : PERMISSION_NONE;
-    }
-
-    EXPECT_EQ(expected.intValue, fwmark.intValue);
-}
-
-void expectUnreachableError(uid_t uid, unsigned netId, int selectionMode) {
-    Fwmark fwmark;
-    const bool doConnect = (selectionMode != UNCONNECTED_SOCKET);
-    EXPECT_TRUE(sendIPv6PacketFromUidFail(uid, V6_ADDR, &fwmark, doConnect, ENETUNREACH));
-
-    Fwmark expected;
-    expected.netId = netId;
-    expected.explicitlySelected = (selectionMode == EXPLICITLY_SELECT);
-    if (uid == AID_ROOT && selectionMode == EXPLICITLY_SELECT) {
-        expected.protectedFromVpn = true;
-    } else {
-        expected.protectedFromVpn = false;
-    }
-    if (selectionMode == UNCONNECTED_SOCKET) {
-        expected.permission = PERMISSION_NONE;
-    } else {
-        expected.permission = (uid == AID_ROOT) ? PERMISSION_SYSTEM : PERMISSION_NONE;
-    }
-
-    EXPECT_EQ(expected.intValue, fwmark.intValue);
-}
-
-}  // namespace
-
-// Verify whether API reject overlapped UID ranges
-TEST_F(NetdBinderTest, PerAppDefaultNetwork_OverlappedUidRanges) {
-    const auto& config = makeNativeNetworkConfig(APP_DEFAULT_NETID, NativeNetworkType::PHYSICAL,
-                                                 INetd::PERMISSION_NONE, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
-    EXPECT_TRUE(mNetd->networkAddInterface(APP_DEFAULT_NETID, sTun.name()).isOk());
-
-    std::vector<UidRangeParcel> uidRanges = {makeUidRangeParcel(BASE_UID + 1, BASE_UID + 1),
-                                             makeUidRangeParcel(BASE_UID + 10, BASE_UID + 12)};
-    EXPECT_TRUE(mNetd->networkAddUidRanges(APP_DEFAULT_NETID, uidRanges).isOk());
-
-    binder::Status status;
-    status = mNetd->networkAddUidRanges(APP_DEFAULT_NETID,
-                                        {makeUidRangeParcel(BASE_UID + 1, BASE_UID + 1)});
-    EXPECT_FALSE(status.isOk());
-    EXPECT_EQ(EINVAL, status.serviceSpecificErrorCode());
-
-    status = mNetd->networkAddUidRanges(APP_DEFAULT_NETID,
-                                        {makeUidRangeParcel(BASE_UID + 9, BASE_UID + 10)});
-    EXPECT_FALSE(status.isOk());
-    EXPECT_EQ(EINVAL, status.serviceSpecificErrorCode());
-
-    status = mNetd->networkAddUidRanges(APP_DEFAULT_NETID,
-                                        {makeUidRangeParcel(BASE_UID + 11, BASE_UID + 11)});
-    EXPECT_FALSE(status.isOk());
-    EXPECT_EQ(EINVAL, status.serviceSpecificErrorCode());
-
-    status = mNetd->networkAddUidRanges(APP_DEFAULT_NETID,
-                                        {makeUidRangeParcel(BASE_UID + 12, BASE_UID + 13)});
-    EXPECT_FALSE(status.isOk());
-    EXPECT_EQ(EINVAL, status.serviceSpecificErrorCode());
-
-    status = mNetd->networkAddUidRanges(APP_DEFAULT_NETID,
-                                        {makeUidRangeParcel(BASE_UID + 9, BASE_UID + 13)});
-    EXPECT_FALSE(status.isOk());
-    EXPECT_EQ(EINVAL, status.serviceSpecificErrorCode());
-
-    std::vector<UidRangeParcel> selfOverlappedUidRanges = {
-            makeUidRangeParcel(BASE_UID + 20, BASE_UID + 20),
-            makeUidRangeParcel(BASE_UID + 20, BASE_UID + 21)};
-    status = mNetd->networkAddUidRanges(APP_DEFAULT_NETID, selfOverlappedUidRanges);
-    EXPECT_FALSE(status.isOk());
-    EXPECT_EQ(EINVAL, status.serviceSpecificErrorCode());
-}
-
-// Verify whether IP rules for app default network are correctly configured.
-TEST_F(NetdBinderTest, PerAppDefaultNetwork_VerifyIpRules) {
-    const auto& config = makeNativeNetworkConfig(APP_DEFAULT_NETID, NativeNetworkType::PHYSICAL,
-                                                 INetd::PERMISSION_NONE, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
-    EXPECT_TRUE(mNetd->networkAddInterface(APP_DEFAULT_NETID, sTun.name()).isOk());
-
-    std::vector<UidRangeParcel> uidRanges = {makeUidRangeParcel(BASE_UID + 8005, BASE_UID + 8012),
-                                             makeUidRangeParcel(BASE_UID + 8090, BASE_UID + 8099)};
-
-    EXPECT_TRUE(mNetd->networkAddUidRanges(APP_DEFAULT_NETID, uidRanges).isOk());
-    verifyAppUidRules({true, true} /*expectedResults*/, uidRanges, sTun.name(),
-                      UidRanges::DEFAULT_SUB_PRIORITY);
-    EXPECT_TRUE(mNetd->networkRemoveUidRanges(APP_DEFAULT_NETID, {uidRanges.at(0)}).isOk());
-    verifyAppUidRules({false, true} /*expectedResults*/, uidRanges, sTun.name(),
-                      UidRanges::DEFAULT_SUB_PRIORITY);
-    EXPECT_TRUE(mNetd->networkRemoveUidRanges(APP_DEFAULT_NETID, {uidRanges.at(1)}).isOk());
-    verifyAppUidRules({false, false} /*expectedResults*/, uidRanges, sTun.name(),
-                      UidRanges::DEFAULT_SUB_PRIORITY);
-
-    EXPECT_TRUE(mNetd->networkAddUidRanges(INetd::UNREACHABLE_NET_ID, uidRanges).isOk());
-    verifyAppUidRules({true, true} /*expectedResults*/, uidRanges, "",
-                      UidRanges::DEFAULT_SUB_PRIORITY);
-    EXPECT_TRUE(mNetd->networkRemoveUidRanges(INetd::UNREACHABLE_NET_ID, {uidRanges.at(0)}).isOk());
-    verifyAppUidRules({false, true} /*expectedResults*/, uidRanges, "",
-                      UidRanges::DEFAULT_SUB_PRIORITY);
-    EXPECT_TRUE(mNetd->networkRemoveUidRanges(INetd::UNREACHABLE_NET_ID, {uidRanges.at(1)}).isOk());
-    verifyAppUidRules({false, false} /*expectedResults*/, uidRanges, "",
-                      UidRanges::DEFAULT_SUB_PRIORITY);
-}
-
-// Verify whether packets go through the right network with and without per-app default network.
-// Meaning of Fwmark bits (from Fwmark.h):
-// 0x0000ffff - Network ID
-// 0x00010000 - Explicit mark bit
-// 0x00020000 - VPN protect bit
-// 0x000c0000 - Permission bits
-TEST_F(NetdBinderTest, PerAppDefaultNetwork_ImplicitlySelectNetwork) {
-    createDefaultAndOtherPhysicalNetwork(SYSTEM_DEFAULT_NETID, APP_DEFAULT_NETID);
-
-    int systemDefaultFd = sTun.getFdForTesting();
-    int appDefaultFd = sTun2.getFdForTesting();
-
-    // Connections go through the system default network.
-    expectPacketSentOnNetId(AID_ROOT, SYSTEM_DEFAULT_NETID, systemDefaultFd, IMPLICITLY_SELECT);
-    expectPacketSentOnNetId(TEST_UID1, SYSTEM_DEFAULT_NETID, systemDefaultFd, IMPLICITLY_SELECT);
-
-    // Add TEST_UID1 to per-app default network.
-    EXPECT_TRUE(mNetd->networkAddUidRanges(APP_DEFAULT_NETID,
-                                           {makeUidRangeParcel(TEST_UID1, TEST_UID1)})
-                        .isOk());
-    expectPacketSentOnNetId(AID_ROOT, SYSTEM_DEFAULT_NETID, systemDefaultFd, IMPLICITLY_SELECT);
-    expectPacketSentOnNetId(TEST_UID1, APP_DEFAULT_NETID, appDefaultFd, IMPLICITLY_SELECT);
-
-    // Remove TEST_UID1 from per-app default network.
-    EXPECT_TRUE(mNetd->networkRemoveUidRanges(APP_DEFAULT_NETID,
-                                              {makeUidRangeParcel(TEST_UID1, TEST_UID1)})
-                        .isOk());
-    expectPacketSentOnNetId(AID_ROOT, SYSTEM_DEFAULT_NETID, systemDefaultFd, IMPLICITLY_SELECT);
-    expectPacketSentOnNetId(TEST_UID1, SYSTEM_DEFAULT_NETID, systemDefaultFd, IMPLICITLY_SELECT);
-
-    // Prohibit TEST_UID1 from using the default network.
-    EXPECT_TRUE(mNetd->networkAddUidRanges(INetd::UNREACHABLE_NET_ID,
-                                           {makeUidRangeParcel(TEST_UID1, TEST_UID1)})
-                        .isOk());
-    expectPacketSentOnNetId(AID_ROOT, SYSTEM_DEFAULT_NETID, systemDefaultFd, IMPLICITLY_SELECT);
-    expectUnreachableError(TEST_UID1, INetd::UNREACHABLE_NET_ID, IMPLICITLY_SELECT);
-
-    // restore IP rules
-    EXPECT_TRUE(mNetd->networkRemoveUidRanges(INetd::UNREACHABLE_NET_ID,
-                                              {makeUidRangeParcel(TEST_UID1, TEST_UID1)})
-                        .isOk());
-}
-
-// Verify whether packets go through the right network when app explicitly selects a network.
-TEST_F(NetdBinderTest, PerAppDefaultNetwork_ExplicitlySelectNetwork) {
-    createDefaultAndOtherPhysicalNetwork(SYSTEM_DEFAULT_NETID, APP_DEFAULT_NETID);
-
-    int systemDefaultFd = sTun.getFdForTesting();
-    int appDefaultFd = sTun2.getFdForTesting();
-
-    // Explicitly select the system default network.
-    setNetworkForProcess(SYSTEM_DEFAULT_NETID);
-    // Connections go through the system default network.
-    expectPacketSentOnNetId(AID_ROOT, SYSTEM_DEFAULT_NETID, systemDefaultFd, EXPLICITLY_SELECT);
-    expectPacketSentOnNetId(TEST_UID1, SYSTEM_DEFAULT_NETID, systemDefaultFd, EXPLICITLY_SELECT);
-
-    // Set TEST_UID1 to default unreachable, which won't affect the explicitly selected network.
-    // Connections go through the system default network.
-    EXPECT_TRUE(mNetd->networkAddUidRanges(INetd::UNREACHABLE_NET_ID,
-                                           {makeUidRangeParcel(TEST_UID1, TEST_UID1)})
-                        .isOk());
-    expectPacketSentOnNetId(AID_ROOT, SYSTEM_DEFAULT_NETID, systemDefaultFd, EXPLICITLY_SELECT);
-    expectPacketSentOnNetId(TEST_UID1, SYSTEM_DEFAULT_NETID, systemDefaultFd, EXPLICITLY_SELECT);
-
-    // restore IP rules
-    EXPECT_TRUE(mNetd->networkRemoveUidRanges(INetd::UNREACHABLE_NET_ID,
-                                              {makeUidRangeParcel(TEST_UID1, TEST_UID1)})
-                        .isOk());
-
-    // Add TEST_UID1 to per-app default network, which won't affect the explicitly selected network.
-    EXPECT_TRUE(mNetd->networkAddUidRanges(APP_DEFAULT_NETID,
-                                           {makeUidRangeParcel(TEST_UID1, TEST_UID1)})
-                        .isOk());
-    expectPacketSentOnNetId(AID_ROOT, SYSTEM_DEFAULT_NETID, systemDefaultFd, EXPLICITLY_SELECT);
-    expectPacketSentOnNetId(TEST_UID1, SYSTEM_DEFAULT_NETID, systemDefaultFd, EXPLICITLY_SELECT);
-
-    // Explicitly select the per-app default network.
-    setNetworkForProcess(APP_DEFAULT_NETID);
-    // Connections go through the per-app default network.
-    expectPacketSentOnNetId(AID_ROOT, APP_DEFAULT_NETID, appDefaultFd, EXPLICITLY_SELECT);
-    expectPacketSentOnNetId(TEST_UID1, APP_DEFAULT_NETID, appDefaultFd, EXPLICITLY_SELECT);
-}
-
-// Verify whether packets go through the right network if app does not implicitly or explicitly
-// select any network.
-TEST_F(NetdBinderTest, PerAppDefaultNetwork_UnconnectedSocket) {
-    createDefaultAndOtherPhysicalNetwork(SYSTEM_DEFAULT_NETID, APP_DEFAULT_NETID);
-
-    int systemDefaultFd = sTun.getFdForTesting();
-    int appDefaultFd = sTun2.getFdForTesting();
-
-    // Connections go through the system default network.
-    expectPacketSentOnNetId(AID_ROOT, NETID_UNSET, systemDefaultFd, UNCONNECTED_SOCKET);
-    expectPacketSentOnNetId(TEST_UID1, NETID_UNSET, systemDefaultFd, UNCONNECTED_SOCKET);
-
-    // Add TEST_UID1 to per-app default network. Traffic should go through the per-app default
-    // network if UID is in range. Otherwise, go through the system default network.
-    EXPECT_TRUE(mNetd->networkAddUidRanges(APP_DEFAULT_NETID,
-                                           {makeUidRangeParcel(TEST_UID1, TEST_UID1)})
-                        .isOk());
-    expectPacketSentOnNetId(AID_ROOT, NETID_UNSET, systemDefaultFd, UNCONNECTED_SOCKET);
-    expectPacketSentOnNetId(TEST_UID1, NETID_UNSET, appDefaultFd, UNCONNECTED_SOCKET);
-
-    // Set TEST_UID1's default network to unreachable. Its traffic should still go through the
-    // per-app default network. Other traffic go through the system default network.
-    // PS: per-app default network take precedence over unreachable network. This should happens
-    //     only in the transition period when both rules are briefly set.
-    EXPECT_TRUE(mNetd->networkAddUidRanges(INetd::UNREACHABLE_NET_ID,
-                                           {makeUidRangeParcel(TEST_UID1, TEST_UID1)})
-                        .isOk());
-    expectPacketSentOnNetId(AID_ROOT, NETID_UNSET, systemDefaultFd, UNCONNECTED_SOCKET);
-    expectPacketSentOnNetId(TEST_UID1, NETID_UNSET, appDefaultFd, UNCONNECTED_SOCKET);
-
-    // Remove TEST_UID1's default network from OEM-paid network. Its traffic should get ENETUNREACH
-    // error. Other traffic still go through the system default network.
-    EXPECT_TRUE(mNetd->networkRemoveUidRanges(APP_DEFAULT_NETID,
-                                              {makeUidRangeParcel(TEST_UID1, TEST_UID1)})
-                        .isOk());
-    expectPacketSentOnNetId(AID_ROOT, NETID_UNSET, systemDefaultFd, UNCONNECTED_SOCKET);
-    expectUnreachableError(TEST_UID1, NETID_UNSET, UNCONNECTED_SOCKET);
-
-    // restore IP rules
-    EXPECT_TRUE(mNetd->networkRemoveUidRanges(INetd::UNREACHABLE_NET_ID,
-                                              {makeUidRangeParcel(TEST_UID1, TEST_UID1)})
-                        .isOk());
-}
-
-TEST_F(NetdBinderTest, PerAppDefaultNetwork_PermissionCheck) {
-    createPhysicalNetwork(APP_DEFAULT_NETID, sTun2.name(), INetd::PERMISSION_SYSTEM);
-
-    {  // uid is not in app range. Can not set network for process.
-        ScopedUidChange scopedUidChange(TEST_UID1);
-        EXPECT_EQ(-EACCES, setNetworkForProcess(APP_DEFAULT_NETID));
-    }
-
-    EXPECT_TRUE(mNetd->networkAddUidRanges(APP_DEFAULT_NETID,
-                                           {makeUidRangeParcel(TEST_UID1, TEST_UID1)})
-                        .isOk());
-
-    {  // uid is in app range. Can set network for process.
-        ScopedUidChange scopedUidChange(TEST_UID1);
-        EXPECT_EQ(0, setNetworkForProcess(APP_DEFAULT_NETID));
-    }
-}
-
-class VpnParameterizedTest : public NetdBinderTest, public testing::WithParamInterface<bool> {};
-
-// Exercise secure and bypassable VPN.
-INSTANTIATE_TEST_SUITE_P(PerAppDefaultNetwork, VpnParameterizedTest, testing::Bool(),
-                         [](const testing::TestParamInfo<bool>& info) {
-                             return info.param ? "SecureVPN" : "BypassableVPN";
-                         });
-
-// Verify per-app default network + VPN.
-TEST_P(VpnParameterizedTest, ImplicitlySelectNetwork) {
-    const bool isSecureVPN = GetParam();
-    createVpnAndAppDefaultNetworkWithUid(
-            SYSTEM_DEFAULT_NETID, APP_DEFAULT_NETID, VPN_NETID, isSecureVPN,
-            {makeUidRangeParcel(TEST_UID2, TEST_UID1)} /* app range */,
-            {makeUidRangeParcel(TEST_UID3, TEST_UID2)} /* VPN range */);
-
-    int systemDefaultFd = sTun.getFdForTesting();
-    int appDefaultFd = sTun2.getFdForTesting();
-    int vpnFd = sTun3.getFdForTesting();
-
-    // uid is neither in app range, nor in VPN range. Traffic goes through system default network.
-    expectPacketSentOnNetId(AID_ROOT, SYSTEM_DEFAULT_NETID, systemDefaultFd, IMPLICITLY_SELECT);
-    // uid is in VPN range, not in app range. Traffic goes through VPN.
-    expectPacketSentOnNetId(TEST_UID3, (isSecureVPN ? SYSTEM_DEFAULT_NETID : VPN_NETID), vpnFd,
-                            IMPLICITLY_SELECT);
-    // uid is in app range, not in VPN range. Traffic goes through per-app default network.
-    expectPacketSentOnNetId(TEST_UID1, APP_DEFAULT_NETID, appDefaultFd, IMPLICITLY_SELECT);
-    // uid is in both app and VPN range. Traffic goes through VPN.
-    expectPacketSentOnNetId(TEST_UID2, (isSecureVPN ? APP_DEFAULT_NETID : VPN_NETID), vpnFd,
-                            IMPLICITLY_SELECT);
-}
-
-class VpnAndSelectNetworkParameterizedTest
-    : public NetdBinderTest,
-      public testing::WithParamInterface<std::tuple<bool, int>> {};
-
-// Exercise the combination of different VPN types and different user selected networks. e.g.
-// secure VPN + select on system default network
-// secure VPN + select on app default network
-// secure VPN + select on VPN
-// bypassable VPN + select on system default network
-// ...
-INSTANTIATE_TEST_SUITE_P(PerAppDefaultNetwork, VpnAndSelectNetworkParameterizedTest,
-                         testing::Combine(testing::Bool(),
-                                          testing::Values(SYSTEM_DEFAULT_NETID, APP_DEFAULT_NETID,
-                                                          VPN_NETID)),
-                         [](const testing::TestParamInfo<std::tuple<bool, int>>& info) {
-                             const std::string vpnType = std::get<0>(info.param)
-                                                                 ? std::string("SecureVPN")
-                                                                 : std::string("BypassableVPN");
-                             std::string selectedNetwork;
-                             switch (std::get<1>(info.param)) {
-                                 case SYSTEM_DEFAULT_NETID:
-                                     selectedNetwork = "SystemDefaultNetwork";
-                                     break;
-                                 case APP_DEFAULT_NETID:
-                                     selectedNetwork = "AppDefaultNetwork";
-                                     break;
-                                 case VPN_NETID:
-                                     selectedNetwork = "VPN";
-                                     break;
-                                 default:
-                                     selectedNetwork = "InvalidParameter";  // Should not happen.
-                             }
-                             return vpnType + "_select" + selectedNetwork;
-                         });
-
-TEST_P(VpnAndSelectNetworkParameterizedTest, ExplicitlySelectNetwork) {
-    bool isSecureVPN;
-    int selectedNetId;
-    std::tie(isSecureVPN, selectedNetId) = GetParam();
-    createVpnAndAppDefaultNetworkWithUid(
-            SYSTEM_DEFAULT_NETID, APP_DEFAULT_NETID, VPN_NETID, isSecureVPN,
-            {makeUidRangeParcel(TEST_UID2, TEST_UID1)} /* app range */,
-            {makeUidRangeParcel(TEST_UID3, TEST_UID2)} /* VPN range */);
-
-    int expectedFd = -1;
-    switch (selectedNetId) {
-        case SYSTEM_DEFAULT_NETID:
-            expectedFd = sTun.getFdForTesting();
-            break;
-        case APP_DEFAULT_NETID:
-            expectedFd = sTun2.getFdForTesting();
-            break;
-        case VPN_NETID:
-            expectedFd = sTun3.getFdForTesting();
-            break;
-        default:
-            GTEST_LOG_(ERROR) << "unexpected netId:" << selectedNetId;  // Should not happen.
-    }
-
-    // In all following permutations, Traffic should go through the specified network if a process
-    // can select network for itself. The fwmark should contain process UID and the explicit select
-    // bit.
-    {  // uid is neither in app range, nor in VPN range. Permission bits, protect bit, and explicit
-       // select bit are all set because of AID_ROOT.
-        ScopedUidChange scopedUidChange(AID_ROOT);
-        EXPECT_EQ(0, setNetworkForProcess(selectedNetId));
-        expectPacketSentOnNetId(AID_ROOT, selectedNetId, expectedFd, EXPLICITLY_SELECT);
-    }
-    {  // uid is in VPN range, not in app range.
-        ScopedUidChange scopedUidChange(TEST_UID3);
-        // Cannot select non-VPN networks when uid is subject to secure VPN.
-        if (isSecureVPN && selectedNetId != VPN_NETID) {
-            EXPECT_EQ(-EPERM, setNetworkForProcess(selectedNetId));
-        } else {
-            EXPECT_EQ(0, setNetworkForProcess(selectedNetId));
-            expectPacketSentOnNetId(TEST_UID3, selectedNetId, expectedFd, EXPLICITLY_SELECT);
-        }
-    }
-    {  // uid is in app range, not in VPN range.
-        ScopedUidChange scopedUidChange(TEST_UID1);
-        // Cannot select the VPN because the VPN does not applies to the UID.
-        if (selectedNetId == VPN_NETID) {
-            EXPECT_EQ(-EPERM, setNetworkForProcess(selectedNetId));
-        } else {
-            EXPECT_EQ(0, setNetworkForProcess(selectedNetId));
-            expectPacketSentOnNetId(TEST_UID1, selectedNetId, expectedFd, EXPLICITLY_SELECT);
-        }
-    }
-    {  // uid is in both app range and VPN range.
-        ScopedUidChange scopedUidChange(TEST_UID2);
-        // Cannot select non-VPN networks when uid is subject to secure VPN.
-        if (isSecureVPN && selectedNetId != VPN_NETID) {
-            EXPECT_EQ(-EPERM, setNetworkForProcess(selectedNetId));
-        } else {
-            EXPECT_EQ(0, setNetworkForProcess(selectedNetId));
-            expectPacketSentOnNetId(TEST_UID2, selectedNetId, expectedFd, EXPLICITLY_SELECT);
-        }
-    }
-}
-
-TEST_P(VpnParameterizedTest, UnconnectedSocket) {
-    const bool isSecureVPN = GetParam();
-    createVpnAndAppDefaultNetworkWithUid(
-            SYSTEM_DEFAULT_NETID, APP_DEFAULT_NETID, VPN_NETID, isSecureVPN,
-            {makeUidRangeParcel(TEST_UID2, TEST_UID1)} /* app range */,
-            {makeUidRangeParcel(TEST_UID3, TEST_UID2)} /* VPN range */);
-
-    int systemDefaultFd = sTun.getFdForTesting();
-    int appDefaultFd = sTun2.getFdForTesting();
-    int vpnFd = sTun3.getFdForTesting();
-
-    // uid is neither in app range, nor in VPN range. Traffic goes through system default network.
-    expectPacketSentOnNetId(AID_ROOT, NETID_UNSET, systemDefaultFd, UNCONNECTED_SOCKET);
-    // uid is in VPN range, not in app range. Traffic goes through VPN.
-    expectPacketSentOnNetId(TEST_UID3, NETID_UNSET, vpnFd, UNCONNECTED_SOCKET);
-    // uid is in app range, not in VPN range. Traffic goes through per-app default network.
-    expectPacketSentOnNetId(TEST_UID1, NETID_UNSET, appDefaultFd, UNCONNECTED_SOCKET);
-    // uid is in both app and VPN range. Traffic goes through VPN.
-    expectPacketSentOnNetId(TEST_UID2, NETID_UNSET, vpnFd, UNCONNECTED_SOCKET);
-}
-
-TEST_F(NetdBinderTest, NetworkCreate) {
-    auto config = makeNativeNetworkConfig(TEST_NETID1, NativeNetworkType::PHYSICAL,
-                                          INetd::PERMISSION_NONE, false);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
-    EXPECT_TRUE(mNetd->networkDestroy(config.netId).isOk());
-
-    config.networkType = NativeNetworkType::VIRTUAL;
-    config.secure = true;
-    config.vpnType = NativeVpnType::OEM;
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
-
-    // invalid network type
-    auto wrongConfig = makeNativeNetworkConfig(TEST_NETID2, static_cast<NativeNetworkType>(-1),
-                                               INetd::PERMISSION_NONE, false);
-    EXPECT_EQ(EINVAL, mNetd->networkCreate(wrongConfig).serviceSpecificErrorCode());
-
-    // invalid VPN type
-    wrongConfig.networkType = NativeNetworkType::VIRTUAL;
-    wrongConfig.vpnType = static_cast<NativeVpnType>(-1);
-    EXPECT_EQ(EINVAL, mNetd->networkCreate(wrongConfig).serviceSpecificErrorCode());
-}
-
-// Verifies valid and invalid inputs on networkAddUidRangesParcel method.
-TEST_F(NetdBinderTest, UidRangeSubPriority_ValidateInputs) {
-    createVpnAndOtherPhysicalNetwork(SYSTEM_DEFAULT_NETID, APP_DEFAULT_NETID, VPN_NETID,
-                                     /*isSecureVPN=*/true);
-    // Invalid priority -1 on a physical network.
-    NativeUidRangeConfig uidRangeConfig =
-            makeNativeUidRangeConfig(APP_DEFAULT_NETID, {makeUidRangeParcel(BASE_UID, BASE_UID)},
-                                     UidRanges::DEFAULT_SUB_PRIORITY - 1);
-    binder::Status status = mNetd->networkAddUidRangesParcel(uidRangeConfig);
-    EXPECT_FALSE(status.isOk());
-    EXPECT_EQ(EINVAL, status.serviceSpecificErrorCode());
-
-    // Invalid priority 1000 on a physical network.
-    uidRangeConfig.subPriority = UidRanges::LOWEST_SUB_PRIORITY + 1;
-    status = mNetd->networkAddUidRangesParcel(uidRangeConfig);
-    EXPECT_FALSE(status.isOk());
-    EXPECT_EQ(EINVAL, status.serviceSpecificErrorCode());
-
-    // Virtual networks support only default priority.
-    uidRangeConfig.netId = VPN_NETID;
-    uidRangeConfig.subPriority = SUB_PRIORITY_1;
-    status = mNetd->networkAddUidRangesParcel(uidRangeConfig);
-    EXPECT_FALSE(status.isOk());
-    EXPECT_EQ(EINVAL, status.serviceSpecificErrorCode());
-
-    // For a single network, identical UID ranges with different priorities are allowed.
-    uidRangeConfig.netId = APP_DEFAULT_NETID;
-    uidRangeConfig.subPriority = SUB_PRIORITY_1;
-    EXPECT_TRUE(mNetd->networkAddUidRangesParcel(uidRangeConfig).isOk());
-    uidRangeConfig.subPriority = SUB_PRIORITY_2;
-    EXPECT_TRUE(mNetd->networkAddUidRangesParcel(uidRangeConfig).isOk());
-
-    // For a single network, identical UID ranges with the same priority is invalid.
-    status = mNetd->networkAddUidRangesParcel(uidRangeConfig);
-    EXPECT_FALSE(status.isOk());
-    EXPECT_EQ(EINVAL, status.serviceSpecificErrorCode());
-
-    // Overlapping ranges is invalid.
-    uidRangeConfig.uidRanges = {makeUidRangeParcel(BASE_UID + 1, BASE_UID + 1),
-                                makeUidRangeParcel(BASE_UID + 1, BASE_UID + 1)};
-    status = mNetd->networkAddUidRangesParcel(uidRangeConfig);
-    EXPECT_FALSE(status.isOk());
-    EXPECT_EQ(EINVAL, status.serviceSpecificErrorCode());
-}
-
-// Examines whether IP rules for app default network with subsidiary priorities are correctly added
-// and removed.
-TEST_F(NetdBinderTest, UidRangeSubPriority_VerifyPhysicalNwIpRules) {
-    createPhysicalNetwork(TEST_NETID1, sTun.name());
-    EXPECT_TRUE(mNetd->networkAddRoute(TEST_NETID1, sTun.name(), "::/0", "").isOk());
-    createPhysicalNetwork(TEST_NETID2, sTun2.name());
-    EXPECT_TRUE(mNetd->networkAddRoute(TEST_NETID2, sTun2.name(), "::/0", "").isOk());
-
-    // Adds priority 1 setting
-    NativeUidRangeConfig uidRangeConfig1 = makeNativeUidRangeConfig(
-            TEST_NETID1, {makeUidRangeParcel(BASE_UID, BASE_UID)}, SUB_PRIORITY_1);
-    EXPECT_TRUE(mNetd->networkAddUidRangesParcel(uidRangeConfig1).isOk());
-    verifyAppUidRules({true}, uidRangeConfig1, sTun.name());
-    // Adds priority 2 setting
-    NativeUidRangeConfig uidRangeConfig2 = makeNativeUidRangeConfig(
-            TEST_NETID2, {makeUidRangeParcel(BASE_UID + 1, BASE_UID + 1)}, SUB_PRIORITY_2);
-    EXPECT_TRUE(mNetd->networkAddUidRangesParcel(uidRangeConfig2).isOk());
-    verifyAppUidRules({true}, uidRangeConfig2, sTun2.name());
-    // Adds another priority 2 setting
-    NativeUidRangeConfig uidRangeConfig3 = makeNativeUidRangeConfig(
-            INetd::UNREACHABLE_NET_ID, {makeUidRangeParcel(BASE_UID + 2, BASE_UID + 2)},
-            SUB_PRIORITY_2);
-    EXPECT_TRUE(mNetd->networkAddUidRangesParcel(uidRangeConfig3).isOk());
-    verifyAppUidRules({true}, uidRangeConfig3, "");
-
-    // Removes.
-    EXPECT_TRUE(mNetd->networkRemoveUidRangesParcel(uidRangeConfig1).isOk());
-    verifyAppUidRules({false}, uidRangeConfig1, sTun.name());
-    verifyAppUidRules({true}, uidRangeConfig2, sTun2.name());
-    verifyAppUidRules({true}, uidRangeConfig3, "");
-    EXPECT_TRUE(mNetd->networkRemoveUidRangesParcel(uidRangeConfig2).isOk());
-    verifyAppUidRules({false}, uidRangeConfig1, sTun.name());
-    verifyAppUidRules({false}, uidRangeConfig2, sTun2.name());
-    verifyAppUidRules({true}, uidRangeConfig3, "");
-    EXPECT_TRUE(mNetd->networkRemoveUidRangesParcel(uidRangeConfig3).isOk());
-    verifyAppUidRules({false}, uidRangeConfig1, sTun.name());
-    verifyAppUidRules({false}, uidRangeConfig2, sTun2.name());
-    verifyAppUidRules({false}, uidRangeConfig3, "");
-}
-
-// Verify uid range rules on virtual network.
-TEST_P(VpnParameterizedTest, UidRangeSubPriority_VerifyVpnIpRules) {
-    const bool isSecureVPN = GetParam();
-    constexpr int VPN_NETID2 = TEST_NETID2;
-
-    // Create 2 VPNs, using sTun and sTun2.
-    auto config = makeNativeNetworkConfig(VPN_NETID, NativeNetworkType::VIRTUAL,
-                                          INetd::PERMISSION_NONE, isSecureVPN);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
-    EXPECT_TRUE(mNetd->networkAddInterface(VPN_NETID, sTun.name()).isOk());
-
-    config = makeNativeNetworkConfig(VPN_NETID2, NativeNetworkType::VIRTUAL, INetd::PERMISSION_NONE,
-                                     isSecureVPN);
-    EXPECT_TRUE(mNetd->networkCreate(config).isOk());
-    EXPECT_TRUE(mNetd->networkAddInterface(VPN_NETID2, sTun2.name()).isOk());
-
-    // Assign uid ranges to different VPNs. Check if rules match.
-    NativeUidRangeConfig uidRangeConfig1 = makeNativeUidRangeConfig(
-            VPN_NETID, {makeUidRangeParcel(BASE_UID, BASE_UID)}, UidRanges::DEFAULT_SUB_PRIORITY);
-    EXPECT_TRUE(mNetd->networkAddUidRangesParcel(uidRangeConfig1).isOk());
-    verifyVpnUidRules({true}, uidRangeConfig1, sTun.name(), isSecureVPN);
-
-    NativeUidRangeConfig uidRangeConfig2 =
-            makeNativeUidRangeConfig(VPN_NETID2, {makeUidRangeParcel(BASE_UID + 1, BASE_UID + 1)},
-                                     UidRanges::DEFAULT_SUB_PRIORITY);
-    EXPECT_TRUE(mNetd->networkAddUidRangesParcel(uidRangeConfig2).isOk());
-    verifyVpnUidRules({true}, uidRangeConfig2, sTun2.name(), isSecureVPN);
-
-    // Remove uid configs one-by-one. Check if rules match.
-    EXPECT_TRUE(mNetd->networkRemoveUidRangesParcel(uidRangeConfig1).isOk());
-    verifyVpnUidRules({false}, uidRangeConfig1, sTun.name(), isSecureVPN);
-    verifyVpnUidRules({true}, uidRangeConfig2, sTun2.name(), isSecureVPN);
-    EXPECT_TRUE(mNetd->networkRemoveUidRangesParcel(uidRangeConfig2).isOk());
-    verifyVpnUidRules({false}, uidRangeConfig1, sTun.name(), isSecureVPN);
-    verifyVpnUidRules({false}, uidRangeConfig2, sTun2.name(), isSecureVPN);
-}
-
-// Verify if packets go through the right network when subsidiary priority and VPN works together.
-//
-// Test config:
-// +----------+------------------------+-------------------------------------------+
-// | Priority |          UID           |             Assigned Network              |
-// +----------+------------------------+-------------------------------------------+
-// |        0 | TEST_UID1              | VPN bypassable (VPN_NETID)                |
-// +----------+------------------------+-------------------------------------------+
-// |        1 | TEST_UID1, TEST_UID2,  | Physical Network 1 (APP_DEFAULT_1_NETID)  |
-// |        1 | TEST_UID3              | Physical Network 2 (APP_DEFAULT_2_NETID)  |
-// |        1 | TEST_UID5              | Unreachable Network (UNREACHABLE_NET_ID)  |
-// +----------+------------------------+-------------------------------------------+
-// |        2 | TEST_UID3              | Physical Network 1 (APP_DEFAULT_1_NETID)  |
-// |        2 | TEST_UID4, TEST_UID5   | Physical Network 2 (APP_DEFAULT_2_NETID)  |
-// +----------+------------------------+-------------------------------------------+
-//
-// Expected results:
-// +-----------+------------------------+
-// |    UID    |    Using Network       |
-// +-----------+------------------------+
-// | TEST_UID1 | VPN                    |
-// | TEST_UID2 | Physical Network 1     |
-// | TEST_UID3 | Physical Network 2     |
-// | TEST_UID4 | Physical Network 2     |
-// | TEST_UID5 | Unreachable Network    |
-// | TEST_UID6 | System Default Network |
-// +-----------+------------------------+
-//
-// SYSTEM_DEFAULT_NETID uses sTun.
-// APP_DEFAULT_1_NETID uses sTun2.
-// VPN_NETID uses sTun3.
-// APP_DEFAULT_2_NETID uses sTun4.
-//
-TEST_F(NetdBinderTest, UidRangeSubPriority_ImplicitlySelectNetwork) {
-    constexpr int APP_DEFAULT_1_NETID = TEST_NETID2;
-    constexpr int APP_DEFAULT_2_NETID = TEST_NETID4;
-
-    // Creates 4 networks.
-    createVpnAndOtherPhysicalNetwork(SYSTEM_DEFAULT_NETID, APP_DEFAULT_1_NETID, VPN_NETID,
-                                     /*isSecureVPN=*/false);
-    createPhysicalNetwork(APP_DEFAULT_2_NETID, sTun4.name());
-    EXPECT_TRUE(mNetd->networkAddRoute(APP_DEFAULT_2_NETID, sTun4.name(), "::/0", "").isOk());
-
-    // Adds VPN setting.
-    NativeUidRangeConfig uidRangeConfigVpn = makeNativeUidRangeConfig(
-            VPN_NETID, {makeUidRangeParcel(TEST_UID1, TEST_UID1)}, UidRanges::DEFAULT_SUB_PRIORITY);
-    EXPECT_TRUE(mNetd->networkAddUidRangesParcel(uidRangeConfigVpn).isOk());
-
-    // Adds uidRangeConfig1 setting.
-    NativeUidRangeConfig uidRangeConfig1 = makeNativeUidRangeConfig(
-            APP_DEFAULT_1_NETID,
-            {makeUidRangeParcel(TEST_UID1, TEST_UID1), makeUidRangeParcel(TEST_UID2, TEST_UID2)},
-            SUB_PRIORITY_1);
-    EXPECT_TRUE(mNetd->networkAddUidRangesParcel(uidRangeConfig1).isOk());
-    uidRangeConfig1.netId = APP_DEFAULT_2_NETID;
-    uidRangeConfig1.uidRanges = {makeUidRangeParcel(TEST_UID3, TEST_UID3)};
-    EXPECT_TRUE(mNetd->networkAddUidRangesParcel(uidRangeConfig1).isOk());
-    uidRangeConfig1.netId = INetd::UNREACHABLE_NET_ID;
-    uidRangeConfig1.uidRanges = {makeUidRangeParcel(TEST_UID5, TEST_UID5)};
-    EXPECT_TRUE(mNetd->networkAddUidRangesParcel(uidRangeConfig1).isOk());
-
-    // Adds uidRangeConfig2 setting.
-    NativeUidRangeConfig uidRangeConfig2 = makeNativeUidRangeConfig(
-            APP_DEFAULT_1_NETID, {makeUidRangeParcel(TEST_UID3, TEST_UID3)}, SUB_PRIORITY_2);
-    EXPECT_TRUE(mNetd->networkAddUidRangesParcel(uidRangeConfig2).isOk());
-    uidRangeConfig2.netId = APP_DEFAULT_2_NETID;
-    uidRangeConfig2.uidRanges = {makeUidRangeParcel(TEST_UID4, TEST_UID4),
-                                 makeUidRangeParcel(TEST_UID5, TEST_UID5)};
-    EXPECT_TRUE(mNetd->networkAddUidRangesParcel(uidRangeConfig2).isOk());
-
-    int systemDefaultFd = sTun.getFdForTesting();
-    int appDefault_1_Fd = sTun2.getFdForTesting();
-    int vpnFd = sTun3.getFdForTesting();
-    int appDefault_2_Fd = sTun4.getFdForTesting();
-    // Verify routings.
-    expectPacketSentOnNetId(TEST_UID1, VPN_NETID, vpnFd, IMPLICITLY_SELECT);
-    expectPacketSentOnNetId(TEST_UID2, APP_DEFAULT_1_NETID, appDefault_1_Fd, IMPLICITLY_SELECT);
-    expectPacketSentOnNetId(TEST_UID3, APP_DEFAULT_2_NETID, appDefault_2_Fd, IMPLICITLY_SELECT);
-    expectPacketSentOnNetId(TEST_UID4, APP_DEFAULT_2_NETID, appDefault_2_Fd, IMPLICITLY_SELECT);
-    expectUnreachableError(TEST_UID5, INetd::UNREACHABLE_NET_ID, IMPLICITLY_SELECT);
-    expectPacketSentOnNetId(TEST_UID6, SYSTEM_DEFAULT_NETID, systemDefaultFd, IMPLICITLY_SELECT);
-
-    // Remove test rules from the unreachable network.
-    EXPECT_TRUE(mNetd->networkRemoveUidRangesParcel(uidRangeConfig1).isOk());
-}
\ No newline at end of file
diff --git a/tests/bpf_base_test.cpp b/tests/bpf_base_test.cpp
index 7ab4290..f28c5f8 100644
--- a/tests/bpf_base_test.cpp
+++ b/tests/bpf_base_test.cpp
@@ -56,13 +56,25 @@
 };
 
 TEST_F(BpfBasicTest, TestCgroupMounted) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     std::string cg2_path;
+#if 0
+    // This is the correct way to fetch cg2_path, but it occasionally hits ASAN
+    // problems due to memory allocated in non ASAN code being freed later by us
     ASSERT_EQ(true, CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &cg2_path));
+#else
+    ASSERT_EQ(true, CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, nullptr));
+    // Constant derived from //system/core/libprocessgroup/profiles/cgroups.json
+    cg2_path = "/dev/cg2_bpf";
+#endif
     ASSERT_EQ(0, access(cg2_path.c_str(), R_OK));
     ASSERT_EQ(0, access((cg2_path + "/cgroup.controllers").c_str(), R_OK));
 }
 
 TEST_F(BpfBasicTest, TestTrafficControllerSetUp) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     ASSERT_EQ(0, access(BPF_EGRESS_PROG_PATH, R_OK));
     ASSERT_EQ(0, access(BPF_INGRESS_PROG_PATH, R_OK));
     ASSERT_EQ(0, access(XT_BPF_INGRESS_PROG_PATH, R_OK));
@@ -85,6 +97,8 @@
 }
 
 TEST_F(BpfBasicTest, TestTagSocket) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     BpfMap<uint64_t, UidTagValue> cookieTagMap(COOKIE_TAG_MAP_PATH);
     ASSERT_LE(0, cookieTagMap.getMap());
     int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
@@ -103,6 +117,8 @@
 }
 
 TEST_F(BpfBasicTest, TestCloseSocketWithoutUntag) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     BpfMap<uint64_t, UidTagValue> cookieTagMap(COOKIE_TAG_MAP_PATH);
     ASSERT_LE(0, cookieTagMap.getMap());
     int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
@@ -128,6 +144,8 @@
 }
 
 TEST_F(BpfBasicTest, TestChangeCounterSet) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     BpfMap<uint32_t, uint8_t> uidCounterSetMap(UID_COUNTERSET_MAP_PATH);
     ASSERT_LE(0, uidCounterSetMap.getMap());
     ASSERT_EQ(0, qtaguid_setCounterSet(TEST_COUNTERSET, TEST_UID));
@@ -142,6 +160,8 @@
 }
 
 TEST_F(BpfBasicTest, TestDeleteTagData) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     BpfMap<StatsKey, StatsValue> statsMapA(STATS_MAP_A_PATH);
     ASSERT_LE(0, statsMapA.getMap());
     BpfMap<StatsKey, StatsValue> statsMapB(STATS_MAP_B_PATH);
diff --git a/tests/netd_test.cpp b/tests/netd_test.cpp
index fcd538e..dd25f96 100644
--- a/tests/netd_test.cpp
+++ b/tests/netd_test.cpp
@@ -48,22 +48,6 @@
     ASSERT_EQ(ENODATA, errno);
 }
 
-// If this test fails most likely your device is lacking device/oem specific
-// selinux genfscon rules, something like .../vendor/.../genfs_contexts:
-//   genfscon sysfs /devices/platform/.../net u:object_r:sysfs_net:s0
-// Easiest debugging is via:
-//   adb root && sleep 1 && adb shell 'ls -Z /sys/class/net/*/mtu'
-// and look for the mislabeled item(s).
-// Everything should be 'u:object_r:sysfs_net:s0'
-//
-// Another useful command is:
-//   adb root && sleep 1 && adb shell find /sys > dump.out
-// or in particular:
-//   adb root && sleep 1 && adb shell find /sys | egrep '/net$'
-// which might (among other things) print out something like:
-//   /sys/devices/platform/11110000.usb/11110000.dwc3/gadget/net
-// which means you need to add:
-//   genfscon sysfs /devices/platform/11110000.usb/11110000.dwc3/gadget/net u:object_r:sysfs_net:s0
 TEST(NetdSELinuxTest, CheckProperMTULabels) {
     // Since we expect the egrep regexp to filter everything out,
     // we thus expect no matches and thus a return code of 1
@@ -121,5 +105,195 @@
     nsTest(CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWNET, true, thread);
 }
 
+// Test for presence of kernel patch:
+//   ANDROID: net: bpf: permit redirect from ingress L3 to egress L2 devices at near max mtu
+// on 4.14+ kernels.
+TEST(NetdBpfTest, testBpfSkbChangeHeadAboveMTU) {
+    SKIP_IF_EXTENDED_BPF_NOT_SUPPORTED;
+
+    constexpr int mtu = 1500;
+
+    errno = 0;
+
+    // Amusingly can't use SIOC... on tun/tap fds.
+    int rv = socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0);
+    ASSERT_EQ(errno, 0);
+    ASSERT_GE(rv, 3);
+    unique_fd unixfd(rv);
+
+    rv = open("/dev/net/tun", O_RDWR | O_NONBLOCK);
+    ASSERT_EQ(errno, 0);
+    ASSERT_GE(rv, 3);
+    unique_fd tun(rv);
+
+    rv = open("/dev/net/tun", O_RDWR | O_NONBLOCK);
+    ASSERT_EQ(errno, 0);
+    ASSERT_GE(rv, 3);
+    unique_fd tap(rv);
+
+    struct ifreq tun_ifr = {
+            .ifr_flags = IFF_TUN | IFF_NO_PI,
+            .ifr_name = "tun_bpftest",
+    };
+
+    struct ifreq tap_ifr = {
+            .ifr_flags = IFF_TAP | IFF_NO_PI,
+            .ifr_name = "tap_bpftest",
+    };
+
+    rv = ioctl(tun, TUNSETIFF, &tun_ifr);
+    ASSERT_EQ(errno, 0);
+    ASSERT_EQ(rv, 0);
+
+    rv = ioctl(tap, TUNSETIFF, &tap_ifr);
+    ASSERT_EQ(errno, 0);
+    ASSERT_EQ(rv, 0);
+
+    // prevents kernel from sending us spurious ipv6 packets
+    rv = open("/proc/sys/net/ipv6/conf/tap_bpftest/disable_ipv6", O_WRONLY);
+    ASSERT_EQ(errno, 0);
+    ASSERT_GE(rv, 3);
+    unique_fd f(rv);
+
+    rv = write(f, "1\n", 2);
+    ASSERT_EQ(errno, 0);
+    ASSERT_EQ(rv, 2);
+
+    rv = close(f.release());
+    ASSERT_EQ(errno, 0);
+    ASSERT_EQ(rv, 0);
+
+    int tunif = if_nametoindex(tun_ifr.ifr_name);
+    ASSERT_GE(tunif, 2);
+
+    int tapif = if_nametoindex(tap_ifr.ifr_name);
+    ASSERT_GE(tapif, 2);
+
+    tun_ifr.ifr_mtu = mtu;
+    rv = ioctl(unixfd, SIOCSIFMTU, &tun_ifr);
+    ASSERT_EQ(errno, 0);
+    ASSERT_EQ(rv, 0);
+
+    tap_ifr.ifr_mtu = mtu;
+    rv = ioctl(unixfd, SIOCSIFMTU, &tap_ifr);
+    ASSERT_EQ(errno, 0);
+    ASSERT_EQ(rv, 0);
+
+    rv = ioctl(unixfd, SIOCGIFFLAGS, &tun_ifr);
+    ASSERT_EQ(errno, 0);
+    ASSERT_EQ(rv, 0);
+
+    rv = ioctl(unixfd, SIOCGIFFLAGS, &tap_ifr);
+    ASSERT_EQ(errno, 0);
+    ASSERT_EQ(rv, 0);
+
+    tun_ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
+
+    tap_ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
+
+    rv = ioctl(unixfd, SIOCSIFFLAGS, &tun_ifr);
+    ASSERT_EQ(errno, 0);
+    ASSERT_EQ(rv, 0);
+
+    rv = ioctl(unixfd, SIOCSIFFLAGS, &tap_ifr);
+    ASSERT_EQ(errno, 0);
+    ASSERT_EQ(rv, 0);
+
+    rv = tcQdiscAddDevClsact(tunif);
+    ASSERT_EQ(rv, 0);
+
+    int bpfFd = getTetherIngressProgFd(/* ethernet */ false);
+    ASSERT_EQ(errno, 0);
+    ASSERT_GE(bpfFd, 3);
+
+    rv = tcFilterAddDevIngressTether(tunif, bpfFd, /* ethernet*/ false);
+    ASSERT_EQ(rv, 0);
+
+    bpf::BpfMap<TetherIngressKey, TetherIngressValue> bpfIngressMap;
+    bpf::BpfMap<uint32_t, TetherStatsValue> bpfStatsMap;
+    bpf::BpfMap<uint32_t, uint64_t> bpfLimitMap;
+
+    rv = getTetherIngressMapFd();
+    ASSERT_GE(rv, 3);
+    bpfIngressMap.reset(rv);
+
+    rv = getTetherStatsMapFd();
+    ASSERT_GE(rv, 3);
+    bpfStatsMap.reset(rv);
+
+    rv = getTetherLimitMapFd();
+    ASSERT_GE(rv, 3);
+    bpfLimitMap.reset(rv);
+
+    TetherIngressKey key = {
+            .iif = static_cast<uint32_t>(tunif),
+            //.neigh6 = ,
+    };
+
+    ethhdr hdr = {
+            .h_proto = htons(ETH_P_IPV6),
+    };
+
+    TetherIngressValue value = {
+            .oif = static_cast<uint32_t>(tapif),
+            .macHeader = hdr,
+            .pmtu = mtu,
+    };
+
+#define ASSERT_OK(status) ASSERT_TRUE((status).ok())
+
+    ASSERT_OK(bpfIngressMap.writeValue(key, value, BPF_ANY));
+
+    uint32_t k = tunif;
+    TetherStatsValue stats = {};
+    ASSERT_OK(bpfStatsMap.writeValue(k, stats, BPF_NOEXIST));
+
+    uint64_t limit = ~0uLL;
+    ASSERT_OK(bpfLimitMap.writeValue(k, limit, BPF_NOEXIST));
+
+    // minimal 'acceptable' 40-byte hoplimit 255 IPv6 packet, src ip 2000::
+    uint8_t pkt[mtu] = {
+            0x60, 0, 0, 0, 0, 40, 0, 255, 0x20,
+    };
+
+    // Iterate over all packet sizes from minimal ipv6 packet to mtu.
+    // Tethering ebpf program should forward the packet from tun to tap interface.
+    // TUN is L3, TAP is L2, so it will add a 14 byte ethernet header.
+    for (int pkt_size = 40; pkt_size <= mtu; ++pkt_size) {
+        rv = write(tun, pkt, pkt_size);
+        ASSERT_EQ(errno, 0);
+        ASSERT_EQ(rv, pkt_size);
+
+        struct pollfd p = {
+                .fd = tap,
+                .events = POLLIN,
+        };
+
+        rv = poll(&p, 1, 1000 /*milliseconds*/);
+        if (rv == 0) {
+            // we hit a timeout at this packet size, log it
+            EXPECT_EQ(pkt_size, -1);
+            // this particular packet size is where it fails without the oneline kernel fix
+            if (pkt_size + ETH_HLEN == mtu + 1) EXPECT_EQ("detected missing kernel patch", "");
+            break;
+        }
+        EXPECT_EQ(errno, 0);
+        EXPECT_EQ(rv, 1);
+        EXPECT_EQ(p.revents, POLLIN);
+
+        // use a buffer 1 byte larger then what we expect so we don't simply get truncated down
+        uint8_t buf[ETH_HLEN + mtu + 1];
+        rv = read(tap, buf, sizeof(buf));
+        EXPECT_EQ(errno, 0);
+        EXPECT_EQ(rv, ETH_HLEN + pkt_size);
+        errno = 0;
+        if (rv < 0) break;
+    }
+
+    ASSERT_OK(bpfIngressMap.deleteValue(key));
+    ASSERT_OK(bpfStatsMap.deleteValue(k));
+    ASSERT_OK(bpfLimitMap.deleteValue(k));
+}
+
 }  // namespace net
 }  // namespace android
diff --git a/tests/netlink_listener_test.cpp b/tests/netlink_listener_test.cpp
index 249bdfb..46394ca 100644
--- a/tests/netlink_listener_test.cpp
+++ b/tests/netlink_listener_test.cpp
@@ -45,7 +45,7 @@
 // A test tag arbitrarily selected.
 constexpr uint32_t TEST_TAG = 0xFF0F0F0F;
 
-constexpr uint32_t SOCK_CLOSE_WAIT_US = 30 * 1000;
+constexpr uint32_t SOCK_CLOSE_WAIT_US = 20 * 1000;
 constexpr uint32_t ENOBUFS_POLL_WAIT_US = 10 * 1000;
 
 using android::base::Result;
@@ -67,11 +67,15 @@
     BpfMap<uint64_t, UidTagValue> mCookieTagMap;
 
     void SetUp() {
+        SKIP_IF_BPF_NOT_SUPPORTED;
+
         mCookieTagMap.reset(android::bpf::mapRetrieveRW(COOKIE_TAG_MAP_PATH));
         ASSERT_TRUE(mCookieTagMap.isValid());
     }
 
     void TearDown() {
+        SKIP_IF_BPF_NOT_SUPPORTED;
+
         const auto deleteTestCookieEntries = [](const uint64_t& key, const UidTagValue& value,
                                                 BpfMap<uint64_t, UidTagValue>& map) {
             if ((value.uid == TEST_UID) && (value.tag == TEST_TAG)) {
@@ -99,7 +103,7 @@
         return mCookieTagMap.iterateWithValue(checkGarbageTags);
     }
 
-    bool checkMassiveSocketDestroy(int totalNumber, bool expectError) {
+    void checkMassiveSocketDestroy(int totalNumber, bool expectError) {
         std::unique_ptr<android::net::NetlinkListenerInterface> skDestroyListener;
         auto result = android::net::TrafficController::makeSkDestroyListener();
         if (!isOk(result)) {
@@ -137,36 +141,25 @@
             // If ENOBUFS triggered, check it only called into the handler once, ie.
             // that the netlink handler is not spinning.
             int currentErrorCount = rxErrorCount;
-            // 0 error count is acceptable because the system has chances to close all sockets
-            // normally.
-            EXPECT_LE(0, rxErrorCount);
-            if (!rxErrorCount) return true;
-
+            EXPECT_LT(0, rxErrorCount);
             usleep(ENOBUFS_POLL_WAIT_US);
             EXPECT_EQ(currentErrorCount, rxErrorCount);
         } else {
             EXPECT_RESULT_OK(checkNoGarbageTagsExist());
             EXPECT_EQ(0, rxErrorCount);
         }
-        return false;
     }
 };
 
 TEST_F(NetlinkListenerTest, TestAllSocketUntagged) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     checkMassiveSocketDestroy(10, false);
     checkMassiveSocketDestroy(100, false);
 }
 
-// Disabled because flaky on blueline-userdebug; this test relies on the main thread
-// winning a race against the NetlinkListener::run() thread. There's no way to ensure
-// things will be scheduled the same way across all architectures and test environments.
-TEST_F(NetlinkListenerTest, DISABLED_TestSkDestroyError) {
-    bool needRetry = false;
-    int retryCount = 0;
-    do {
-        needRetry = checkMassiveSocketDestroy(32500, true);
-        if (needRetry) retryCount++;
-    } while (needRetry && retryCount < 3);
-    // Should review test if it can always close all sockets correctly.
-    EXPECT_GT(3, retryCount);
+TEST_F(NetlinkListenerTest, TestSkDestroyError) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    checkMassiveSocketDestroy(32500, true);
 }