Merge "[DU03-2]Remove INetworkStatsService from BatteryStatsImpl"
diff --git a/OWNERS_core_networking_xts b/OWNERS_core_networking_xts
new file mode 100644
index 0000000..a6627fe
--- /dev/null
+++ b/OWNERS_core_networking_xts
@@ -0,0 +1,2 @@
+lorenzo@google.com
+satk@google.com
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 6996ad9..9f2ea35 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -32,6 +32,9 @@
     // TODO: move to presubmit when known green.
     {
       "name": "bpf_existence_test"
+    },
+    {
+      "name": "libclat_test"
     }
   ],
   "mainline-presubmit": [
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 4703f1d..4c64e98 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -61,7 +61,9 @@
     binaries: [
         "clatd",
     ],
+    canned_fs_config: "canned_fs_config",
     bpfs: [
+        "netd.o_mainline",
         "offload.o",
         "test.o",
     ],
@@ -94,6 +96,7 @@
     name: "com.android.tethering-bootclasspath-fragment",
     contents: [
         "framework-connectivity",
+        "framework-connectivity-tiramisu",
         "framework-tethering",
     ],
     apex_available: ["com.android.tethering"],
@@ -115,7 +118,13 @@
     // Additional hidden API flag files to override the defaults. This must only be
     // modified by the Soong or platform compat team.
     hidden_api: {
-        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+        max_target_r_low_priority: [
+            "hiddenapi/hiddenapi-max-target-r-loprio.txt",
+	],
+        max_target_o_low_priority: [
+            "hiddenapi/hiddenapi-max-target-o-low-priority.txt",
+            "hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt",
+	],
         unsupported: ["hiddenapi/hiddenapi-unsupported.txt"],
     },
 }
diff --git a/Tethering/apex/canned_fs_config b/Tethering/apex/canned_fs_config
new file mode 100644
index 0000000..44c57ab
--- /dev/null
+++ b/Tethering/apex/canned_fs_config
@@ -0,0 +1,2 @@
+/bin/for-system 0 1000 0550
+/bin/for-system/clatd 1029 1029 06555
diff --git a/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt b/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt
new file mode 100644
index 0000000..88c77f2
--- /dev/null
+++ b/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt
@@ -0,0 +1,87 @@
+Landroid/net/nsd/DnsSdTxtRecord;-><init>()V
+Landroid/net/nsd/DnsSdTxtRecord;-><init>(Landroid/net/nsd/DnsSdTxtRecord;)V
+Landroid/net/nsd/DnsSdTxtRecord;-><init>([B)V
+Landroid/net/nsd/DnsSdTxtRecord;->contains(Ljava/lang/String;)Z
+Landroid/net/nsd/DnsSdTxtRecord;->CREATOR:Landroid/os/Parcelable$Creator;
+Landroid/net/nsd/DnsSdTxtRecord;->get(Ljava/lang/String;)Ljava/lang/String;
+Landroid/net/nsd/DnsSdTxtRecord;->getKey(I)Ljava/lang/String;
+Landroid/net/nsd/DnsSdTxtRecord;->getRawData()[B
+Landroid/net/nsd/DnsSdTxtRecord;->getValue(I)[B
+Landroid/net/nsd/DnsSdTxtRecord;->getValue(Ljava/lang/String;)[B
+Landroid/net/nsd/DnsSdTxtRecord;->getValueAsString(I)Ljava/lang/String;
+Landroid/net/nsd/DnsSdTxtRecord;->insert([B[BI)V
+Landroid/net/nsd/DnsSdTxtRecord;->keyCount()I
+Landroid/net/nsd/DnsSdTxtRecord;->mData:[B
+Landroid/net/nsd/DnsSdTxtRecord;->mSeperator:B
+Landroid/net/nsd/DnsSdTxtRecord;->remove(Ljava/lang/String;)I
+Landroid/net/nsd/DnsSdTxtRecord;->set(Ljava/lang/String;Ljava/lang/String;)V
+Landroid/net/nsd/DnsSdTxtRecord;->size()I
+Landroid/net/nsd/INsdManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
+Landroid/net/nsd/INsdManager$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
+Landroid/net/nsd/INsdManager$Stub$Proxy;->getMessenger()Landroid/os/Messenger;
+Landroid/net/nsd/INsdManager$Stub$Proxy;->mRemote:Landroid/os/IBinder;
+Landroid/net/nsd/INsdManager$Stub$Proxy;->setEnabled(Z)V
+Landroid/net/nsd/INsdManager$Stub;-><init>()V
+Landroid/net/nsd/INsdManager$Stub;->DESCRIPTOR:Ljava/lang/String;
+Landroid/net/nsd/INsdManager$Stub;->TRANSACTION_getMessenger:I
+Landroid/net/nsd/INsdManager$Stub;->TRANSACTION_setEnabled:I
+Landroid/net/nsd/INsdManager;->setEnabled(Z)V
+Landroid/net/nsd/NsdManager;-><init>(Landroid/content/Context;Landroid/net/nsd/INsdManager;)V
+Landroid/net/nsd/NsdManager;->BASE:I
+Landroid/net/nsd/NsdManager;->checkListener(Ljava/lang/Object;)V
+Landroid/net/nsd/NsdManager;->checkProtocol(I)V
+Landroid/net/nsd/NsdManager;->checkServiceInfo(Landroid/net/nsd/NsdServiceInfo;)V
+Landroid/net/nsd/NsdManager;->DBG:Z
+Landroid/net/nsd/NsdManager;->DISABLE:I
+Landroid/net/nsd/NsdManager;->disconnect()V
+Landroid/net/nsd/NsdManager;->DISCOVER_SERVICES:I
+Landroid/net/nsd/NsdManager;->DISCOVER_SERVICES_FAILED:I
+Landroid/net/nsd/NsdManager;->DISCOVER_SERVICES_STARTED:I
+Landroid/net/nsd/NsdManager;->ENABLE:I
+Landroid/net/nsd/NsdManager;->EVENT_NAMES:Landroid/util/SparseArray;
+Landroid/net/nsd/NsdManager;->fatal(Ljava/lang/String;)V
+Landroid/net/nsd/NsdManager;->FIRST_LISTENER_KEY:I
+Landroid/net/nsd/NsdManager;->getListenerKey(Ljava/lang/Object;)I
+Landroid/net/nsd/NsdManager;->getMessenger()Landroid/os/Messenger;
+Landroid/net/nsd/NsdManager;->getNsdServiceInfoType(Landroid/net/nsd/NsdServiceInfo;)Ljava/lang/String;
+Landroid/net/nsd/NsdManager;->init()V
+Landroid/net/nsd/NsdManager;->mAsyncChannel:Lcom/android/internal/util/AsyncChannel;
+Landroid/net/nsd/NsdManager;->mConnected:Ljava/util/concurrent/CountDownLatch;
+Landroid/net/nsd/NsdManager;->mContext:Landroid/content/Context;
+Landroid/net/nsd/NsdManager;->mHandler:Landroid/net/nsd/NsdManager$ServiceHandler;
+Landroid/net/nsd/NsdManager;->mListenerKey:I
+Landroid/net/nsd/NsdManager;->mListenerMap:Landroid/util/SparseArray;
+Landroid/net/nsd/NsdManager;->mMapLock:Ljava/lang/Object;
+Landroid/net/nsd/NsdManager;->mService:Landroid/net/nsd/INsdManager;
+Landroid/net/nsd/NsdManager;->mServiceMap:Landroid/util/SparseArray;
+Landroid/net/nsd/NsdManager;->nameOf(I)Ljava/lang/String;
+Landroid/net/nsd/NsdManager;->NATIVE_DAEMON_EVENT:I
+Landroid/net/nsd/NsdManager;->nextListenerKey()I
+Landroid/net/nsd/NsdManager;->putListener(Ljava/lang/Object;Landroid/net/nsd/NsdServiceInfo;)I
+Landroid/net/nsd/NsdManager;->REGISTER_SERVICE:I
+Landroid/net/nsd/NsdManager;->REGISTER_SERVICE_FAILED:I
+Landroid/net/nsd/NsdManager;->REGISTER_SERVICE_SUCCEEDED:I
+Landroid/net/nsd/NsdManager;->removeListener(I)V
+Landroid/net/nsd/NsdManager;->RESOLVE_SERVICE:I
+Landroid/net/nsd/NsdManager;->RESOLVE_SERVICE_FAILED:I
+Landroid/net/nsd/NsdManager;->RESOLVE_SERVICE_SUCCEEDED:I
+Landroid/net/nsd/NsdManager;->SERVICE_FOUND:I
+Landroid/net/nsd/NsdManager;->SERVICE_LOST:I
+Landroid/net/nsd/NsdManager;->setEnabled(Z)V
+Landroid/net/nsd/NsdManager;->STOP_DISCOVERY:I
+Landroid/net/nsd/NsdManager;->STOP_DISCOVERY_FAILED:I
+Landroid/net/nsd/NsdManager;->STOP_DISCOVERY_SUCCEEDED:I
+Landroid/net/nsd/NsdManager;->TAG:Ljava/lang/String;
+Landroid/net/nsd/NsdManager;->UNREGISTER_SERVICE:I
+Landroid/net/nsd/NsdManager;->UNREGISTER_SERVICE_FAILED:I
+Landroid/net/nsd/NsdManager;->UNREGISTER_SERVICE_SUCCEEDED:I
+Landroid/net/nsd/NsdServiceInfo;-><init>(Ljava/lang/String;Ljava/lang/String;)V
+Landroid/net/nsd/NsdServiceInfo;->getTxtRecord()[B
+Landroid/net/nsd/NsdServiceInfo;->getTxtRecordSize()I
+Landroid/net/nsd/NsdServiceInfo;->mHost:Ljava/net/InetAddress;
+Landroid/net/nsd/NsdServiceInfo;->mPort:I
+Landroid/net/nsd/NsdServiceInfo;->mServiceName:Ljava/lang/String;
+Landroid/net/nsd/NsdServiceInfo;->mServiceType:Ljava/lang/String;
+Landroid/net/nsd/NsdServiceInfo;->mTxtRecord:Landroid/util/ArrayMap;
+Landroid/net/nsd/NsdServiceInfo;->setTxtRecords(Ljava/lang/String;)V
+Landroid/net/nsd/NsdServiceInfo;->TAG:Ljava/lang/String;
diff --git a/Tethering/apex/hiddenapi/hiddenapi-max-target-r-loprio.txt b/Tethering/apex/hiddenapi/hiddenapi-max-target-r-loprio.txt
new file mode 100644
index 0000000..211b847
--- /dev/null
+++ b/Tethering/apex/hiddenapi/hiddenapi-max-target-r-loprio.txt
@@ -0,0 +1 @@
+Landroid/net/nsd/INsdManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/nsd/INsdManager;
diff --git a/Tethering/proguard.flags b/Tethering/proguard.flags
index f62df7f..6735317 100644
--- a/Tethering/proguard.flags
+++ b/Tethering/proguard.flags
@@ -8,6 +8,10 @@
     native <methods>;
 }
 
+-keep class com.android.networkstack.tethering.util.TcUtils {
+    native <methods>;
+}
+
 -keepclassmembers public class * extends com.android.networkstack.tethering.util.Struct {
     *;
 }
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index d015ef6..4fa288b 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -71,3 +71,16 @@
         "-Werror",
     ],
 }
+
+bpf {
+    name: "netd.o_mainline",
+    srcs: ["netd.c"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    include_dirs: [
+        "frameworks/libs/net/common/netd/libnetdutils/include",
+    ],
+    sub_dir: "net_shared",
+}
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
new file mode 100644
index 0000000..72ee431
--- /dev/null
+++ b/bpf_progs/netd.c
@@ -0,0 +1,392 @@
+/*
+ * 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 <bpf_helpers.h>
+#include <linux/bpf.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/pkt_cls.h>
+#include <linux/tcp.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include "bpf_net_helpers.h"
+#include "bpf_shared.h"
+
+// This is defined for cgroup bpf filter only.
+#define BPF_DROP_UNLESS_DNS 2
+#define BPF_PASS 1
+#define BPF_DROP 0
+
+// This is used for xt_bpf program only.
+#define BPF_NOMATCH 0
+#define BPF_MATCH 1
+
+#define BPF_EGRESS 0
+#define BPF_INGRESS 1
+
+#define IP_PROTO_OFF offsetof(struct iphdr, protocol)
+#define IPV6_PROTO_OFF offsetof(struct ipv6hdr, nexthdr)
+#define IPPROTO_IHL_OFF 0
+#define TCP_FLAG_OFF 13
+#define RST_OFFSET 2
+
+DEFINE_BPF_MAP_GRW(cookie_tag_map, HASH, uint64_t, UidTagValue, COOKIE_UID_MAP_SIZE,
+                   AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(uid_counterset_map, HASH, uint32_t, uint8_t, UID_COUNTERSET_MAP_SIZE,
+                   AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(app_uid_stats_map, HASH, uint32_t, StatsValue, APP_STATS_MAP_SIZE,
+                   AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(stats_map_A, HASH, StatsKey, StatsValue, STATS_MAP_SIZE, AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(stats_map_B, HASH, StatsKey, StatsValue, STATS_MAP_SIZE, AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(iface_stats_map, HASH, uint32_t, StatsValue, IFACE_STATS_MAP_SIZE,
+                   AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(configuration_map, HASH, uint32_t, uint8_t, CONFIGURATION_MAP_SIZE,
+                   AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(uid_owner_map, HASH, uint32_t, UidOwnerValue, UID_OWNER_MAP_SIZE,
+                   AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(uid_permission_map, HASH, uint32_t, uint8_t, UID_OWNER_MAP_SIZE, AID_NET_BW_ACCT)
+
+/* never actually used from ebpf */
+DEFINE_BPF_MAP_GRW(iface_index_name_map, HASH, uint32_t, IfaceValue, IFACE_INDEX_NAME_MAP_SIZE,
+                   AID_NET_BW_ACCT)
+
+static __always_inline int is_system_uid(uint32_t uid) {
+    return (uid <= MAX_SYSTEM_UID) && (uid >= MIN_SYSTEM_UID);
+}
+
+/*
+ * Note: this blindly assumes an MTU of 1500, and that packets > MTU are always TCP,
+ * and that TCP is using the Linux default settings with TCP timestamp option enabled
+ * which uses 12 TCP option bytes per frame.
+ *
+ * These are not unreasonable assumptions:
+ *
+ * The internet does not really support MTUs greater than 1500, so most TCP traffic will
+ * be at that MTU, or slightly below it (worst case our upwards adjustment is too small).
+ *
+ * The chance our traffic isn't IP at all is basically zero, so the IP overhead correction
+ * is bound to be needed.
+ *
+ * Furthermore, the likelyhood that we're having to deal with GSO (ie. > MTU) packets that
+ * are not IP/TCP is pretty small (few other things are supported by Linux) and worse case
+ * our extra overhead will be slightly off, but probably still better than assuming none.
+ *
+ * Most servers are also Linux and thus support/default to using TCP timestamp option
+ * (and indeed TCP timestamp option comes from RFC 1323 titled "TCP Extensions for High
+ * Performance" which also defined TCP window scaling and are thus absolutely ancient...).
+ *
+ * All together this should be more correct than if we simply ignored GSO frames
+ * (ie. counted them as single packets with no extra overhead)
+ *
+ * Especially since the number of packets is important for any future clat offload correction.
+ * (which adjusts upward by 20 bytes per packet to account for ipv4 -> ipv6 header conversion)
+ */
+#define DEFINE_UPDATE_STATS(the_stats_map, TypeOfKey)                                          \
+    static __always_inline inline void update_##the_stats_map(struct __sk_buff* skb,           \
+                                                              int direction, TypeOfKey* key) { \
+        StatsValue* value = bpf_##the_stats_map##_lookup_elem(key);                            \
+        if (!value) {                                                                          \
+            StatsValue newValue = {};                                                          \
+            bpf_##the_stats_map##_update_elem(key, &newValue, BPF_NOEXIST);                    \
+            value = bpf_##the_stats_map##_lookup_elem(key);                                    \
+        }                                                                                      \
+        if (value) {                                                                           \
+            const int mtu = 1500;                                                              \
+            uint64_t packets = 1;                                                              \
+            uint64_t bytes = skb->len;                                                         \
+            if (bytes > mtu) {                                                                 \
+                bool is_ipv6 = (skb->protocol == htons(ETH_P_IPV6));                           \
+                int ip_overhead = (is_ipv6 ? sizeof(struct ipv6hdr) : sizeof(struct iphdr));   \
+                int tcp_overhead = ip_overhead + sizeof(struct tcphdr) + 12;                   \
+                int mss = mtu - tcp_overhead;                                                  \
+                uint64_t payload = bytes - tcp_overhead;                                       \
+                packets = (payload + mss - 1) / mss;                                           \
+                bytes = tcp_overhead * packets + payload;                                      \
+            }                                                                                  \
+            if (direction == BPF_EGRESS) {                                                     \
+                __sync_fetch_and_add(&value->txPackets, packets);                              \
+                __sync_fetch_and_add(&value->txBytes, bytes);                                  \
+            } else if (direction == BPF_INGRESS) {                                             \
+                __sync_fetch_and_add(&value->rxPackets, packets);                              \
+                __sync_fetch_and_add(&value->rxBytes, bytes);                                  \
+            }                                                                                  \
+        }                                                                                      \
+    }
+
+DEFINE_UPDATE_STATS(app_uid_stats_map, uint32_t)
+DEFINE_UPDATE_STATS(iface_stats_map, uint32_t)
+DEFINE_UPDATE_STATS(stats_map_A, StatsKey)
+DEFINE_UPDATE_STATS(stats_map_B, StatsKey)
+
+static inline bool skip_owner_match(struct __sk_buff* skb) {
+    int offset = -1;
+    int ret = 0;
+    if (skb->protocol == htons(ETH_P_IP)) {
+        offset = IP_PROTO_OFF;
+        uint8_t proto, ihl;
+        uint8_t flag;
+        ret = bpf_skb_load_bytes(skb, offset, &proto, 1);
+        if (!ret) {
+            if (proto == IPPROTO_ESP) {
+                return true;
+            } else if (proto == IPPROTO_TCP) {
+                ret = bpf_skb_load_bytes(skb, IPPROTO_IHL_OFF, &ihl, 1);
+                ihl = ihl & 0x0F;
+                ret = bpf_skb_load_bytes(skb, ihl * 4 + TCP_FLAG_OFF, &flag, 1);
+                if (ret == 0 && (flag >> RST_OFFSET & 1)) {
+                    return true;
+                }
+            }
+        }
+    } else if (skb->protocol == htons(ETH_P_IPV6)) {
+        offset = IPV6_PROTO_OFF;
+        uint8_t proto;
+        ret = bpf_skb_load_bytes(skb, offset, &proto, 1);
+        if (!ret) {
+            if (proto == IPPROTO_ESP) {
+                return true;
+            } else if (proto == IPPROTO_TCP) {
+                uint8_t flag;
+                ret = bpf_skb_load_bytes(skb, sizeof(struct ipv6hdr) + TCP_FLAG_OFF, &flag, 1);
+                if (ret == 0 && (flag >> RST_OFFSET & 1)) {
+                    return true;
+                }
+            }
+        }
+    }
+    return false;
+}
+
+static __always_inline BpfConfig getConfig(uint32_t configKey) {
+    uint32_t mapSettingKey = configKey;
+    BpfConfig* config = bpf_configuration_map_lookup_elem(&mapSettingKey);
+    if (!config) {
+        // Couldn't read configuration entry. Assume everything is disabled.
+        return DEFAULT_CONFIG;
+    }
+    return *config;
+}
+
+static inline int bpf_owner_match(struct __sk_buff* skb, uint32_t uid, int direction) {
+    if (skip_owner_match(skb)) return BPF_PASS;
+
+    if (is_system_uid(uid)) return BPF_PASS;
+
+    BpfConfig enabledRules = getConfig(UID_RULES_CONFIGURATION_KEY);
+
+    UidOwnerValue* uidEntry = bpf_uid_owner_map_lookup_elem(&uid);
+    uint8_t uidRules = uidEntry ? uidEntry->rule : 0;
+    uint32_t allowed_iif = uidEntry ? uidEntry->iif : 0;
+
+    if (enabledRules) {
+        if ((enabledRules & DOZABLE_MATCH) && !(uidRules & DOZABLE_MATCH)) {
+            return BPF_DROP;
+        }
+        if ((enabledRules & STANDBY_MATCH) && (uidRules & STANDBY_MATCH)) {
+            return BPF_DROP;
+        }
+        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
+        if (allowed_iif && skb->ifindex != 1 && skb->ifindex != allowed_iif) {
+            return BPF_DROP_UNLESS_DNS;
+        }
+    }
+    return BPF_PASS;
+}
+
+static __always_inline inline void update_stats_with_config(struct __sk_buff* skb, int direction,
+                                                            StatsKey* key, uint8_t selectedMap) {
+    if (selectedMap == SELECT_MAP_A) {
+        update_stats_map_A(skb, direction, key);
+    } else if (selectedMap == SELECT_MAP_B) {
+        update_stats_map_B(skb, direction, key);
+    }
+}
+
+static __always_inline inline int bpf_traffic_account(struct __sk_buff* skb, int direction) {
+    uint32_t sock_uid = bpf_get_socket_uid(skb);
+    uint64_t cookie = bpf_get_socket_cookie(skb);
+    UidTagValue* utag = bpf_cookie_tag_map_lookup_elem(&cookie);
+    uint32_t uid, tag;
+    if (utag) {
+        uid = utag->uid;
+        tag = utag->tag;
+    } else {
+        uid = sock_uid;
+        tag = 0;
+    }
+
+    // Always allow and never count clat traffic. Only the IPv4 traffic on the stacked
+    // interface is accounted for and subject to usage restrictions.
+    // TODO: remove sock_uid check once Nat464Xlat javaland adds the socket tag AID_CLAT for clat.
+    if (sock_uid == AID_CLAT || uid == AID_CLAT) {
+        return BPF_PASS;
+    }
+
+    int match = bpf_owner_match(skb, sock_uid, direction);
+    if ((direction == BPF_EGRESS) && (match == BPF_DROP)) {
+        // If an outbound packet is going to be dropped, we do not count that
+        // traffic.
+        return match;
+    }
+
+// Workaround for secureVPN with VpnIsolation enabled, refer to b/159994981 for details.
+// Keep TAG_SYSTEM_DNS in sync with DnsResolver/include/netd_resolv/resolv.h
+// and TrafficStatsConstants.java
+#define TAG_SYSTEM_DNS 0xFFFFFF82
+    if (tag == TAG_SYSTEM_DNS && uid == AID_DNS) {
+        uid = sock_uid;
+        if (match == BPF_DROP_UNLESS_DNS) match = BPF_PASS;
+    } else {
+        if (match == BPF_DROP_UNLESS_DNS) match = BPF_DROP;
+    }
+
+    StatsKey key = {.uid = uid, .tag = tag, .counterSet = 0, .ifaceIndex = skb->ifindex};
+
+    uint8_t* counterSet = bpf_uid_counterset_map_lookup_elem(&uid);
+    if (counterSet) key.counterSet = (uint32_t)*counterSet;
+
+    uint32_t mapSettingKey = CURRENT_STATS_MAP_CONFIGURATION_KEY;
+    uint8_t* selectedMap = bpf_configuration_map_lookup_elem(&mapSettingKey);
+
+    // Use asm("%0 &= 1" : "+r"(match)) before return match,
+    // to help kernel's bpf verifier, so that it can be 100% certain
+    // that the returned value is always BPF_NOMATCH(0) or BPF_MATCH(1).
+    if (!selectedMap) {
+        asm("%0 &= 1" : "+r"(match));
+        return match;
+    }
+
+    if (key.tag) {
+        update_stats_with_config(skb, direction, &key, *selectedMap);
+        key.tag = 0;
+    }
+
+    update_stats_with_config(skb, direction, &key, *selectedMap);
+    update_app_uid_stats_map(skb, direction, &uid);
+    asm("%0 &= 1" : "+r"(match));
+    return match;
+}
+
+DEFINE_BPF_PROG("cgroupskb/ingress/stats", AID_ROOT, AID_ROOT, 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) {
+    return bpf_traffic_account(skb, BPF_EGRESS);
+}
+
+DEFINE_BPF_PROG("skfilter/egress/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_egress_prog)
+(struct __sk_buff* skb) {
+    // Clat daemon does not generate new traffic, all its traffic is accounted for already
+    // on the v4-* interfaces (except for the 20 (or 28) extra bytes of IPv6 vs IPv4 overhead,
+    // but that can be corrected for later when merging v4-foo stats into interface foo's).
+    // TODO: remove sock_uid check once Nat464Xlat javaland adds the socket tag AID_CLAT for clat.
+    uint32_t sock_uid = bpf_get_socket_uid(skb);
+    if (sock_uid == AID_CLAT) return BPF_NOMATCH;
+    if (sock_uid == AID_SYSTEM) {
+        uint64_t cookie = bpf_get_socket_cookie(skb);
+        UidTagValue* utag = bpf_cookie_tag_map_lookup_elem(&cookie);
+        if (utag && utag->uid == AID_CLAT) return BPF_NOMATCH;
+    }
+
+    uint32_t key = skb->ifindex;
+    update_iface_stats_map(skb, BPF_EGRESS, &key);
+    return BPF_MATCH;
+}
+
+DEFINE_BPF_PROG("skfilter/ingress/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_ingress_prog)
+(struct __sk_buff* skb) {
+    // Clat daemon traffic is not accounted by virtue of iptables raw prerouting drop rule
+    // (in clat_raw_PREROUTING chain), which triggers before this (in bw_raw_PREROUTING chain).
+    // It will be accounted for on the v4-* clat interface instead.
+    // Keep that in mind when moving this out of iptables xt_bpf and into tc ingress (or xdp).
+
+    uint32_t key = skb->ifindex;
+    update_iface_stats_map(skb, BPF_INGRESS, &key);
+    return BPF_MATCH;
+}
+
+DEFINE_BPF_PROG("schedact/ingress/account", AID_ROOT, AID_NET_ADMIN, tc_bpf_ingress_account_prog)
+(struct __sk_buff* skb) {
+    // Account for ingress traffic before tc drops it.
+    uint32_t key = skb->ifindex;
+    update_iface_stats_map(skb, BPF_INGRESS, &key);
+    return TC_ACT_UNSPEC;
+}
+
+DEFINE_BPF_PROG("skfilter/allowlist/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_allowlist_prog)
+(struct __sk_buff* skb) {
+    uint32_t sock_uid = bpf_get_socket_uid(skb);
+    if (is_system_uid(sock_uid)) return BPF_MATCH;
+
+    // 65534 is the overflow 'nobody' uid, usually this being returned means
+    // that skb->sk is NULL during RX (early decap socket lookup failure),
+    // which commonly happens for incoming packets to an unconnected udp socket.
+    // Additionally bpf_get_socket_cookie() returns 0 if skb->sk is NULL
+    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;
+    return BPF_NOMATCH;
+}
+
+DEFINE_BPF_PROG("skfilter/denylist/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_denylist_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;
+    return BPF_NOMATCH;
+}
+
+DEFINE_BPF_PROG_KVER("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create,
+                     KVER(4, 14, 0))
+(struct bpf_sock* sk) {
+    uint64_t gid_uid = bpf_get_current_uid_gid();
+    /*
+     * A given app is guaranteed to have the same app ID in all the profiles in
+     * which it is installed, and install permission is granted to app for all
+     * user at install time so we only check the appId part of a request uid at
+     * run time. See UserHandle#isSameApp for detail.
+     */
+    uint32_t appId = (gid_uid & 0xffffffff) % PER_USER_RANGE;
+    uint8_t* permissions = bpf_uid_permission_map_lookup_elem(&appId);
+    if (!permissions) {
+        // UID not in map. Default to just INTERNET permission.
+        return 1;
+    }
+
+    // A return value of 1 means allow, everything else means deny.
+    return (*permissions & BPF_PERMISSION_INTERNET) == BPF_PERMISSION_INTERNET;
+}
+
+LICENSE("Apache 2.0");
+CRITICAL("netd");
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
new file mode 100644
index 0000000..abcfbeb
--- /dev/null
+++ b/framework-t/Android.bp
@@ -0,0 +1,71 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_defaults {
+    name: "enable-framework-connectivity-t-targets",
+    enabled: true,
+}
+// The above defaults can be used to disable framework-connectivity t
+// targets while minimizing merge conflicts in the build rules.
+
+
+java_sdk_library {
+    name: "framework-connectivity-tiramisu",
+    sdk_version: "module_current",
+    min_sdk_version: "Tiramisu",
+    defaults: [
+        "framework-module-defaults",
+        "enable-framework-connectivity-t-targets",
+    ],
+    srcs: [
+        ":framework-connectivity-tiramisu-updatable-sources",
+    ],
+    libs: [
+        "unsupportedappusage",
+        "app-compat-annotations",
+    ],
+    permitted_packages: [
+        "android.net",
+        "android.net.nsd",
+    ],
+    apex_available: [
+        "com.android.tethering",
+    ],
+    impl_library_visibility: [
+        "//packages/modules/Connectivity/Tethering/apex",
+        // In preparation for future move
+        "//packages/modules/Connectivity/apex",
+        "//packages/modules/Connectivity/service-t",
+        "//frameworks/base",
+
+        // Tests using hidden APIs
+        "//cts/tests/netlegacy22.api",
+        "//external/sl4a:__subpackages__",
+        "//frameworks/libs/net/common/testutils",
+        "//frameworks/libs/net/common/tests:__subpackages__",
+        "//frameworks/opt/telephony/tests/telephonytests",
+        "//packages/modules/CaptivePortalLogin/tests",
+        "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
+        "//packages/modules/Connectivity/tests:__subpackages__",
+        "//packages/modules/NetworkStack/tests:__subpackages__",
+        "//packages/modules/Wifi/service/tests/wifitests",
+    ],
+}
diff --git a/framework-t/api/current.txt b/framework-t/api/current.txt
new file mode 100644
index 0000000..0443456
--- /dev/null
+++ b/framework-t/api/current.txt
@@ -0,0 +1,60 @@
+// Signature format: 2.0
+package android.net.nsd {
+
+  public final class NsdManager {
+    method public void discoverServices(String, int, android.net.nsd.NsdManager.DiscoveryListener);
+    method public void registerService(android.net.nsd.NsdServiceInfo, int, android.net.nsd.NsdManager.RegistrationListener);
+    method public void resolveService(android.net.nsd.NsdServiceInfo, android.net.nsd.NsdManager.ResolveListener);
+    method public void stopServiceDiscovery(android.net.nsd.NsdManager.DiscoveryListener);
+    method public void unregisterService(android.net.nsd.NsdManager.RegistrationListener);
+    field public static final String ACTION_NSD_STATE_CHANGED = "android.net.nsd.STATE_CHANGED";
+    field public static final String EXTRA_NSD_STATE = "nsd_state";
+    field public static final int FAILURE_ALREADY_ACTIVE = 3; // 0x3
+    field public static final int FAILURE_INTERNAL_ERROR = 0; // 0x0
+    field public static final int FAILURE_MAX_LIMIT = 4; // 0x4
+    field public static final int NSD_STATE_DISABLED = 1; // 0x1
+    field public static final int NSD_STATE_ENABLED = 2; // 0x2
+    field public static final int PROTOCOL_DNS_SD = 1; // 0x1
+  }
+
+  public static interface NsdManager.DiscoveryListener {
+    method public void onDiscoveryStarted(String);
+    method public void onDiscoveryStopped(String);
+    method public void onServiceFound(android.net.nsd.NsdServiceInfo);
+    method public void onServiceLost(android.net.nsd.NsdServiceInfo);
+    method public void onStartDiscoveryFailed(String, int);
+    method public void onStopDiscoveryFailed(String, int);
+  }
+
+  public static interface NsdManager.RegistrationListener {
+    method public void onRegistrationFailed(android.net.nsd.NsdServiceInfo, int);
+    method public void onServiceRegistered(android.net.nsd.NsdServiceInfo);
+    method public void onServiceUnregistered(android.net.nsd.NsdServiceInfo);
+    method public void onUnregistrationFailed(android.net.nsd.NsdServiceInfo, int);
+  }
+
+  public static interface NsdManager.ResolveListener {
+    method public void onResolveFailed(android.net.nsd.NsdServiceInfo, int);
+    method public void onServiceResolved(android.net.nsd.NsdServiceInfo);
+  }
+
+  public final class NsdServiceInfo implements android.os.Parcelable {
+    ctor public NsdServiceInfo();
+    method public int describeContents();
+    method public java.util.Map<java.lang.String,byte[]> getAttributes();
+    method public java.net.InetAddress getHost();
+    method public int getPort();
+    method public String getServiceName();
+    method public String getServiceType();
+    method public void removeAttribute(String);
+    method public void setAttribute(String, String);
+    method public void setHost(java.net.InetAddress);
+    method public void setPort(int);
+    method public void setServiceName(String);
+    method public void setServiceType(String);
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.nsd.NsdServiceInfo> CREATOR;
+  }
+
+}
+
diff --git a/framework-t/api/module-lib-current.txt b/framework-t/api/module-lib-current.txt
new file mode 100644
index 0000000..81d89c6
--- /dev/null
+++ b/framework-t/api/module-lib-current.txt
@@ -0,0 +1,9 @@
+// Signature format: 2.0
+package android.net {
+
+  public final class ConnectivityFrameworkInitializerTiramisu {
+    method public static void registerServiceWrappers();
+  }
+
+}
+
diff --git a/framework-t/api/module-lib-removed.txt b/framework-t/api/module-lib-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/framework-t/api/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/framework-t/api/removed.txt b/framework-t/api/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/framework-t/api/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/framework-t/api/system-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/framework-t/api/system-removed.txt b/framework-t/api/system-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/framework-t/api/system-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/framework/Android.bp b/framework/Android.bp
index 028701a..de505c7 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -94,6 +94,7 @@
         // In preparation for future move
         "//packages/modules/Connectivity/apex",
         "//packages/modules/Connectivity/service",
+        "//packages/modules/Connectivity/service-t",
         "//frameworks/base/packages/Connectivity/service",
         "//frameworks/base",
 
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 7d65f8c..009890c 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -5612,7 +5612,7 @@
      * background data is restricted.
      *
      * @param uid uid of target app
-     * @throws IllegalStateException if update allow list failed.
+     * @throws IllegalStateException if updating allow list failed.
      * @hide
      */
     @SystemApi(client = MODULE_LIBRARIES)
@@ -5634,7 +5634,7 @@
      * Takes precedence over {@link #updateMeteredNetworkAllowList}.
      *
      * @param uid uid of target app
-     * @throws IllegalStateException if update deny list failed.
+     * @throws IllegalStateException if updating deny list failed.
      * @hide
      */
     @SystemApi(client = MODULE_LIBRARIES)
@@ -5656,8 +5656,8 @@
      *
      * @param chain target chain.
      * @param uid uid to allow/deny.
-     * @param allow either add or remove rule.
-     * @throws IllegalStateException if update firewall rule failed.
+     * @param allow whether networking is allowed or denied.
+     * @throws IllegalStateException if updating firewall rule failed.
      * @hide
      */
     @SystemApi(client = MODULE_LIBRARIES)
@@ -5680,7 +5680,7 @@
      *
      * @param chain target chain.
      * @param enable whether the chain should be enabled.
-     * @throws IllegalStateException if set firewall chain failed.
+     * @throws IllegalStateException if enabling or disabling the firewall chain failed.
      * @hide
      */
     @SystemApi(client = MODULE_LIBRARIES)
@@ -5702,7 +5702,7 @@
      *
      * @param chain target chain to replace.
      * @param uids The list of UIDs to be placed into chain.
-     * @throws IllegalStateException if replace firewall chain failed.
+     * @throws IllegalStateException if replacing the firewall chain failed.
      * @throws IllegalArgumentException if {@code chain} is not a valid chain.
      * @hide
      */
@@ -5727,7 +5727,7 @@
      * NetworkStatsFactory which is platform code but will be moved into connectivity (tethering)
      * mainline module.
      *
-     * @throws IllegalStateException if swap active stats map failed.
+     * @throws IllegalStateException if swapping active stats map failed.
      * @hide
      */
     @SystemApi(client = MODULE_LIBRARIES)
diff --git a/service-t/Android.bp b/service-t/Android.bp
new file mode 100644
index 0000000..48c74c6
--- /dev/null
+++ b/service-t/Android.bp
@@ -0,0 +1,56 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// This builds T+ services depending on framework-connectivity-tiramisu
+// hidden symbols separately from the S+ services, to ensure that S+
+// services cannot accidentally depend on T+ hidden symbols from
+// framework-connectivity-tiramisu.
+java_library {
+    name: "service-connectivity-tiramisu-pre-jarjar",
+    sdk_version: "system_server_current",
+    // TODO(b/210962470): Bump this to at least S, and then T.
+    min_sdk_version: "30",
+    srcs: [
+        "src/**/*.java",
+        // TODO: This is necessary just for LocalLog, remove after removing NativeDaemonConnector.
+        ":framework-connectivity-shared-srcs",
+        ":services.connectivity-tiramisu-updatable-sources",
+    ],
+    libs: [
+        "framework-annotations-lib",
+        "framework-connectivity.impl",
+        "framework-connectivity-tiramisu.impl",
+        "service-connectivity-pre-jarjar",
+        "unsupportedappusage",
+    ],
+    static_libs: [
+        "modules-utils-build",
+        "modules-utils-statemachine",
+        "net-utils-framework-common",
+    ],
+    apex_available: [
+        "com.android.tethering",
+    ],
+    visibility: [
+        "//packages/modules/Connectivity/service",
+        "//packages/modules/Connectivity/tests:__subpackages__",
+    ],
+}
diff --git a/service/src/com/android/server/ConnectivityServiceInitializer.java b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
similarity index 68%
rename from service/src/com/android/server/ConnectivityServiceInitializer.java
rename to service-t/src/com/android/server/ConnectivityServiceInitializer.java
index b1a56ae..23d8bdc 100644
--- a/service/src/com/android/server/ConnectivityServiceInitializer.java
+++ b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.util.Log;
 
+import com.android.modules.utils.build.SdkLevel;
+
 /**
  * Connectivity service initializer for core networking. This is called by system server to create
  * a new instance of ConnectivityService.
@@ -26,12 +28,14 @@
 public final class ConnectivityServiceInitializer extends SystemService {
     private static final String TAG = ConnectivityServiceInitializer.class.getSimpleName();
     private final ConnectivityService mConnectivity;
+    private final NsdService mNsdService;
 
     public ConnectivityServiceInitializer(Context context) {
         super(context);
         // Load JNI libraries used by ConnectivityService and its dependencies
         System.loadLibrary("service-connectivity");
         mConnectivity = new ConnectivityService(context);
+        mNsdService = createNsdService(context);
     }
 
     @Override
@@ -39,5 +43,20 @@
         Log.i(TAG, "Registering " + Context.CONNECTIVITY_SERVICE);
         publishBinderService(Context.CONNECTIVITY_SERVICE, mConnectivity,
                 /* allowIsolated= */ false);
+        if (mNsdService != null) {
+            Log.i(TAG, "Registering " + Context.NSD_SERVICE);
+            publishBinderService(Context.NSD_SERVICE, mNsdService, /* allowIsolated= */ false);
+        }
+    }
+
+    /** Return NsdService instance or null if current SDK is lower than T */
+    private NsdService createNsdService(final Context context) {
+        if (!SdkLevel.isAtLeastT()) return null;
+        try {
+            return NsdService.create(context);
+        } catch (InterruptedException e) {
+            Log.d(TAG, "Unable to get NSD service", e);
+            return null;
+        }
     }
 }
diff --git a/service/Android.bp b/service/Android.bp
index 1ec7daa..e376ff7 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -57,12 +57,18 @@
     ],
     srcs: [
         "jni/com_android_server_TestNetworkService.cpp",
+        "jni/com_android_server_connectivity_ClatCoordinator.cpp",
         "jni/onload.cpp",
     ],
     stl: "libc++_static",
     header_libs: [
         "libbase_headers",
     ],
+    static_libs: [
+        "libclat",
+        "libip_checksum",
+        "libnetjniutils",
+    ],
     shared_libs: [
         "liblog",
         "libnativehelper",
@@ -108,6 +114,10 @@
         "com.android.tethering",
     ],
     lint: { strict_updatability_linting: true },
+    visibility: [
+        "//packages/modules/Connectivity/service-t",
+        "//packages/modules/Connectivity/tests:__subpackages__",
+    ],
 }
 
 java_library {
@@ -132,8 +142,13 @@
     sdk_version: "system_server_current",
     min_sdk_version: "30",
     installable: true,
+    // This library combines system server jars that have access to different bootclasspath jars.
+    // Lower SDK service jars must not depend on higher SDK jars as that would let them
+    // transitively depend on the wrong bootclasspath jars. Sources also cannot be added here as
+    // they would transitively depend on bootclasspath jars that may not be available.
     static_libs: [
         "service-connectivity-pre-jarjar",
+        "service-connectivity-tiramisu-pre-jarjar",
     ],
     jarjar_rules: "jarjar-rules.txt",
     apex_available: [
@@ -147,3 +162,11 @@
     srcs: ["jarjar-rules.txt"],
     visibility: ["//packages/modules/Connectivity:__subpackages__"],
 }
+
+// TODO: This filegroup temporary exposes for NetworkStats. It should be
+// removed right after NetworkStats moves into mainline module.
+filegroup {
+    name: "traffic-controller-utils",
+    srcs: ["src/com/android/server/BpfNetMaps.java"],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
new file mode 100644
index 0000000..a9d7946
--- /dev/null
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/if_tun.h>
+#include <linux/ioctl.h>
+#include <nativehelper/JNIHelp.h>
+#include <net/if.h>
+
+#include <netjniutils/netjniutils.h>
+
+#include "libclat/clatutils.h"
+#include "nativehelper/scoped_utf_chars.h"
+
+// Sync from system/netd/include/netid_client.h
+#define MARK_UNSET 0u
+
+namespace android {
+static void throwIOException(JNIEnv* env, const char* msg, int error) {
+    jniThrowExceptionFmt(env, "java/io/IOException", "%s: %s", msg, strerror(error));
+}
+
+jstring com_android_server_connectivity_ClatCoordinator_selectIpv4Address(JNIEnv* env,
+                                                                          jobject clazz,
+                                                                          jstring v4addr,
+                                                                          jint prefixlen) {
+    ScopedUtfChars address(env, v4addr);
+    in_addr ip;
+    if (inet_pton(AF_INET, address.c_str(), &ip) != 1) {
+        throwIOException(env, "invalid address", EINVAL);
+        return nullptr;
+    }
+
+    // Pick an IPv4 address.
+    // TODO: this picks the address based on other addresses that are assigned to interfaces, but
+    // the address is only actually assigned to an interface once clatd starts up. So we could end
+    // up with two clatd instances with the same IPv4 address.
+    // Stop doing this and instead pick a free one from the kV4Addr pool.
+    in_addr v4 = {net::clat::selectIpv4Address(ip, prefixlen)};
+    if (v4.s_addr == INADDR_NONE) {
+        jniThrowExceptionFmt(env, "java/io/IOException", "No free IPv4 address in %s/%d",
+                             address.c_str(), prefixlen);
+        return nullptr;
+    }
+
+    char addrstr[INET_ADDRSTRLEN];
+    if (!inet_ntop(AF_INET, (void*)&v4, addrstr, sizeof(addrstr))) {
+        throwIOException(env, "invalid address", EADDRNOTAVAIL);
+        return nullptr;
+    }
+    return env->NewStringUTF(addrstr);
+}
+
+// Picks a random interface ID that is checksum neutral with the IPv4 address and the NAT64 prefix.
+jstring com_android_server_connectivity_ClatCoordinator_generateIpv6Address(
+        JNIEnv* env, jobject clazz, jstring ifaceStr, jstring v4Str, jstring prefix64Str) {
+    ScopedUtfChars iface(env, ifaceStr);
+    ScopedUtfChars addr4(env, v4Str);
+    ScopedUtfChars prefix64(env, prefix64Str);
+
+    if (iface.c_str() == nullptr) {
+        jniThrowExceptionFmt(env, "java/io/IOException", "Invalid null interface name");
+        return nullptr;
+    }
+
+    in_addr v4;
+    if (inet_pton(AF_INET, addr4.c_str(), &v4) != 1) {
+        jniThrowExceptionFmt(env, "java/io/IOException", "Invalid clat v4 address %s",
+                             addr4.c_str());
+        return nullptr;
+    }
+
+    in6_addr nat64Prefix;
+    if (inet_pton(AF_INET6, prefix64.c_str(), &nat64Prefix) != 1) {
+        jniThrowExceptionFmt(env, "java/io/IOException", "Invalid prefix %s", prefix64.c_str());
+        return nullptr;
+    }
+
+    in6_addr v6;
+    if (net::clat::generateIpv6Address(iface.c_str(), v4, nat64Prefix, &v6)) {
+        jniThrowExceptionFmt(env, "java/io/IOException",
+                             "Unable to find global source address on %s for %s", iface.c_str(),
+                             prefix64.c_str());
+        return nullptr;
+    }
+
+    char addrstr[INET6_ADDRSTRLEN];
+    if (!inet_ntop(AF_INET6, (void*)&v6, addrstr, sizeof(addrstr))) {
+        throwIOException(env, "invalid address", EADDRNOTAVAIL);
+        return nullptr;
+    }
+    return env->NewStringUTF(addrstr);
+}
+
+static jint com_android_server_connectivity_ClatCoordinator_createTunInterface(JNIEnv* env,
+                                                                               jobject clazz,
+                                                                               jstring tuniface) {
+    ScopedUtfChars v4interface(env, tuniface);
+
+    // open the tun device in non blocking mode as required by clatd
+    jint fd = open("/dev/net/tun", O_RDWR | O_NONBLOCK | O_CLOEXEC);
+    if (fd == -1) {
+        jniThrowExceptionFmt(env, "java/io/IOException", "open tun device failed (%s)",
+                             strerror(errno));
+        return -1;
+    }
+
+    struct ifreq ifr = {
+            .ifr_flags = IFF_TUN,
+    };
+    strlcpy(ifr.ifr_name, v4interface.c_str(), sizeof(ifr.ifr_name));
+
+    if (ioctl(fd, TUNSETIFF, &ifr, sizeof(ifr))) {
+        close(fd);
+        jniThrowExceptionFmt(env, "java/io/IOException", "ioctl(TUNSETIFF) failed (%s)",
+                             strerror(errno));
+        return -1;
+    }
+
+    return fd;
+}
+
+static jint com_android_server_connectivity_ClatCoordinator_detectMtu(JNIEnv* env, jobject clazz,
+                                                                      jstring platSubnet,
+                                                                      jint plat_suffix, jint mark) {
+    ScopedUtfChars platSubnetStr(env, platSubnet);
+
+    in6_addr plat_subnet;
+    if (inet_pton(AF_INET6, platSubnetStr.c_str(), &plat_subnet) != 1) {
+        jniThrowExceptionFmt(env, "java/io/IOException", "Invalid plat prefix address %s",
+                             platSubnetStr.c_str());
+        return -1;
+    }
+
+    int ret = net::clat::detect_mtu(&plat_subnet, plat_suffix, mark);
+    if (ret < 0) {
+        jniThrowExceptionFmt(env, "java/io/IOException", "detect mtu failed: %s", strerror(-ret));
+        return -1;
+    }
+
+    return ret;
+}
+
+static jint com_android_server_connectivity_ClatCoordinator_openPacketSocket(JNIEnv* env,
+                                                                              jobject clazz) {
+    // Will eventually be bound to htons(ETH_P_IPV6) protocol,
+    // but only after appropriate bpf filter is attached.
+    int sock = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+    if (sock < 0) {
+        throwIOException(env, "packet socket failed", errno);
+        return -1;
+    }
+    return sock;
+}
+
+static jint com_android_server_connectivity_ClatCoordinator_openRawSocket6(JNIEnv* env,
+                                                                           jobject clazz,
+                                                                           jint mark) {
+    int sock = socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_RAW);
+    if (sock < 0) {
+        throwIOException(env, "raw socket failed", errno);
+        return -1;
+    }
+
+    // TODO: check the mark validation
+    if (mark != MARK_UNSET && setsockopt(sock, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) < 0) {
+        throwIOException(env, "could not set mark on raw socket", errno);
+        close(sock);
+        return -1;
+    }
+
+    return sock;
+}
+
+static void com_android_server_connectivity_ClatCoordinator_addAnycastSetsockopt(
+        JNIEnv* env, jobject clazz, jobject javaFd, jstring addr6, jint ifindex) {
+    int sock = netjniutils::GetNativeFileDescriptor(env, javaFd);
+    if (sock < 0) {
+        jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
+        return;
+    }
+
+    ScopedUtfChars addrStr(env, addr6);
+
+    in6_addr addr;
+    if (inet_pton(AF_INET6, addrStr.c_str(), &addr) != 1) {
+        jniThrowExceptionFmt(env, "java/io/IOException", "Invalid IPv6 address %s",
+                             addrStr.c_str());
+        return;
+    }
+
+    struct ipv6_mreq mreq = {addr, ifindex};
+    int ret = setsockopt(sock, SOL_IPV6, IPV6_JOIN_ANYCAST, &mreq, sizeof(mreq));
+    if (ret) {
+        jniThrowExceptionFmt(env, "java/io/IOException", "setsockopt IPV6_JOIN_ANYCAST failed: %s",
+                             strerror(errno));
+        return;
+    }
+}
+
+static void com_android_server_connectivity_ClatCoordinator_configurePacketSocket(
+        JNIEnv* env, jobject clazz, jobject javaFd, jstring addr6, jint ifindex) {
+    ScopedUtfChars addrStr(env, addr6);
+
+    int sock = netjniutils::GetNativeFileDescriptor(env, javaFd);
+    if (sock < 0) {
+        jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
+        return;
+    }
+
+    in6_addr addr;
+    if (inet_pton(AF_INET6, addrStr.c_str(), &addr) != 1) {
+        jniThrowExceptionFmt(env, "java/io/IOException", "Invalid IPv6 address %s",
+                             addrStr.c_str());
+        return;
+    }
+
+    int ret = net::clat::configure_packet_socket(sock, &addr, ifindex);
+    if (ret < 0) {
+        throwIOException(env, "configure packet socket failed", -ret);
+        return;
+    }
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+        /* name, signature, funcPtr */
+        {"native_selectIpv4Address", "(Ljava/lang/String;I)Ljava/lang/String;",
+         (void*)com_android_server_connectivity_ClatCoordinator_selectIpv4Address},
+        {"native_generateIpv6Address",
+         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
+         (void*)com_android_server_connectivity_ClatCoordinator_generateIpv6Address},
+        {"native_createTunInterface", "(Ljava/lang/String;)I",
+         (void*)com_android_server_connectivity_ClatCoordinator_createTunInterface},
+        {"native_detectMtu", "(Ljava/lang/String;II)I",
+         (void*)com_android_server_connectivity_ClatCoordinator_detectMtu},
+        {"native_openPacketSocket", "()I",
+         (void*)com_android_server_connectivity_ClatCoordinator_openPacketSocket},
+        {"native_openRawSocket6", "(I)I",
+         (void*)com_android_server_connectivity_ClatCoordinator_openRawSocket6},
+        {"native_addAnycastSetsockopt", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V",
+         (void*)com_android_server_connectivity_ClatCoordinator_addAnycastSetsockopt},
+        {"native_configurePacketSocket", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V",
+         (void*)com_android_server_connectivity_ClatCoordinator_configurePacketSocket},
+};
+
+int register_android_server_connectivity_ClatCoordinator(JNIEnv* env) {
+    return jniRegisterNativeMethods(env, "com/android/server/connectivity/ClatCoordinator",
+                                    gMethods, NELEM(gMethods));
+}
+
+};  // namespace android
diff --git a/service/jni/onload.cpp b/service/jni/onload.cpp
index 0012879..04d9671 100644
--- a/service/jni/onload.cpp
+++ b/service/jni/onload.cpp
@@ -20,6 +20,7 @@
 namespace android {
 
 int register_android_server_TestNetworkService(JNIEnv* env);
+int register_android_server_connectivity_ClatCoordinator(JNIEnv* env);
 
 extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
     JNIEnv *env;
@@ -32,6 +33,10 @@
         return JNI_ERR;
     }
 
+    if (register_android_server_connectivity_ClatCoordinator(env) < 0) {
+        return JNI_ERR;
+    }
+
     return JNI_VERSION_1_6;
 }
 
diff --git a/service/native/libs/libclat/Android.bp b/service/native/libs/libclat/Android.bp
new file mode 100644
index 0000000..8540787
--- /dev/null
+++ b/service/native/libs/libclat/Android.bp
@@ -0,0 +1,49 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+    name: "libclat",
+    defaults: ["netd_defaults"],
+    srcs: ["clatutils.cpp"],
+    stl: "libc++_static",
+    static_libs: ["libip_checksum"],
+    shared_libs: ["liblog"],
+    export_include_dirs: ["include"],
+    min_sdk_version: "30",
+    apex_available: ["com.android.tethering"],
+}
+
+cc_test {
+    name: "libclat_test",
+    defaults: ["netd_defaults"],
+    test_suites: ["device-tests"],
+    srcs: [
+        "clatutils_test.cpp",
+    ],
+    static_libs: [
+        "libbase",
+        "libclat",
+        "libip_checksum",
+        "libnetd_test_tun_interface",
+    ],
+    shared_libs: [
+        "liblog",
+        "libnetutils",
+    ],
+    require_root: true,
+}
\ No newline at end of file
diff --git a/service/native/libs/libclat/clatutils.cpp b/service/native/libs/libclat/clatutils.cpp
new file mode 100644
index 0000000..4a125ba
--- /dev/null
+++ b/service/native/libs/libclat/clatutils.cpp
@@ -0,0 +1,268 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#define LOG_TAG "clatutils"
+
+#include "libclat/clatutils.h"
+
+#include <errno.h>
+#include <linux/filter.h>
+#include <linux/if_packet.h>
+#include <linux/if_tun.h>
+#include <log/log.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+extern "C" {
+#include "checksum.h"
+}
+
+// Sync from external/android-clat/clatd.h
+#define MAXMTU 65536
+#define PACKETLEN (MAXMTU + sizeof(struct tun_pi))
+
+// Sync from system/netd/include/netid_client.h.
+#define MARK_UNSET 0u
+
+namespace android {
+namespace net {
+namespace clat {
+
+bool isIpv4AddressFree(in_addr_t addr) {
+    int s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+    if (s == -1) {
+        return 0;
+    }
+
+    // Attempt to connect to the address. If the connection succeeds and getsockname returns the
+    // same then the address is already assigned to the system and we can't use it.
+    struct sockaddr_in sin = {
+            .sin_family = AF_INET,
+            .sin_port = htons(53),
+            .sin_addr = {addr},
+    };
+    socklen_t len = sizeof(sin);
+    bool inuse = connect(s, (struct sockaddr*)&sin, sizeof(sin)) == 0 &&
+                 getsockname(s, (struct sockaddr*)&sin, &len) == 0 && (size_t)len >= sizeof(sin) &&
+                 sin.sin_addr.s_addr == addr;
+
+    close(s);
+    return !inuse;
+}
+
+// Picks a free IPv4 address, starting from ip and trying all addresses in the prefix in order.
+//   ip        - the IP address from the configuration file
+//   prefixlen - the length of the prefix from which addresses may be selected.
+//   returns: the IPv4 address, or INADDR_NONE if no addresses were available
+in_addr_t selectIpv4Address(const in_addr ip, int16_t prefixlen) {
+    return selectIpv4AddressInternal(ip, prefixlen, isIpv4AddressFree);
+}
+
+// Only allow testing to use this function directly. Otherwise call selectIpv4Address(ip, pfxlen)
+// which has applied valid isIpv4AddressFree function pointer.
+in_addr_t selectIpv4AddressInternal(const in_addr ip, int16_t prefixlen,
+                                    isIpv4AddrFreeFn isIpv4AddressFreeFunc) {
+    // Impossible! Only test allows to apply fn.
+    if (isIpv4AddressFreeFunc == nullptr) {
+        return INADDR_NONE;
+    }
+
+    // Don't accept prefixes that are too large because we scan addresses one by one.
+    if (prefixlen < 16 || prefixlen > 32) {
+        return INADDR_NONE;
+    }
+
+    // All these are in host byte order.
+    in_addr_t mask = 0xffffffff >> (32 - prefixlen) << (32 - prefixlen);
+    in_addr_t ipv4 = ntohl(ip.s_addr);
+    in_addr_t first_ipv4 = ipv4;
+    in_addr_t prefix = ipv4 & mask;
+
+    // Pick the first IPv4 address in the pool, wrapping around if necessary.
+    // So, for example, 192.0.0.4 -> 192.0.0.5 -> 192.0.0.6 -> 192.0.0.7 -> 192.0.0.0.
+    do {
+        if (isIpv4AddressFreeFunc(htonl(ipv4))) {
+            return htonl(ipv4);
+        }
+        ipv4 = prefix | ((ipv4 + 1) & ~mask);
+    } while (ipv4 != first_ipv4);
+
+    return INADDR_NONE;
+}
+
+// Alters the bits in the IPv6 address to make them checksum neutral with v4 and nat64Prefix.
+void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Prefix) {
+    // Fill last 8 bytes of IPv6 address with random bits.
+    arc4random_buf(&v6->s6_addr[8], 8);
+
+    // Make the IID checksum-neutral. That is, make it so that:
+    //   checksum(Local IPv4 | Remote IPv4) = checksum(Local IPv6 | Remote IPv6)
+    // in other words (because remote IPv6 = NAT64 prefix | Remote IPv4):
+    //   checksum(Local IPv4) = checksum(Local IPv6 | NAT64 prefix)
+    // Do this by adjusting the two bytes in the middle of the IID.
+
+    uint16_t middlebytes = (v6->s6_addr[11] << 8) + v6->s6_addr[12];
+
+    uint32_t c1 = ip_checksum_add(0, &v4, sizeof(v4));
+    uint32_t c2 = ip_checksum_add(0, &nat64Prefix, sizeof(nat64Prefix)) +
+                  ip_checksum_add(0, v6, sizeof(*v6));
+
+    uint16_t delta = ip_checksum_adjust(middlebytes, c1, c2);
+    v6->s6_addr[11] = delta >> 8;
+    v6->s6_addr[12] = delta & 0xff;
+}
+
+// Picks a random interface ID that is checksum neutral with the IPv4 address and the NAT64 prefix.
+int generateIpv6Address(const char* iface, const in_addr v4, const in6_addr& nat64Prefix,
+                        in6_addr* v6) {
+    int s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+    if (s == -1) return -errno;
+
+    if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, iface, strlen(iface) + 1) == -1) {
+        close(s);
+        return -errno;
+    }
+
+    sockaddr_in6 sin6 = {.sin6_family = AF_INET6, .sin6_addr = nat64Prefix};
+    if (connect(s, reinterpret_cast<struct sockaddr*>(&sin6), sizeof(sin6)) == -1) {
+        close(s);
+        return -errno;
+    }
+
+    socklen_t len = sizeof(sin6);
+    if (getsockname(s, reinterpret_cast<struct sockaddr*>(&sin6), &len) == -1) {
+        close(s);
+        return -errno;
+    }
+
+    *v6 = sin6.sin6_addr;
+
+    if (IN6_IS_ADDR_UNSPECIFIED(v6) || IN6_IS_ADDR_LOOPBACK(v6) || IN6_IS_ADDR_LINKLOCAL(v6) ||
+        IN6_IS_ADDR_SITELOCAL(v6) || IN6_IS_ADDR_ULA(v6)) {
+        close(s);
+        return -ENETUNREACH;
+    }
+
+    makeChecksumNeutral(v6, v4, nat64Prefix);
+    close(s);
+
+    return 0;
+}
+
+int detect_mtu(const struct in6_addr* plat_subnet, uint32_t plat_suffix, uint32_t mark) {
+    // Create an IPv6 UDP socket.
+    int s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+    if (s < 0) {
+        int ret = errno;
+        ALOGE("socket(AF_INET6, SOCK_DGRAM, 0) failed: %s", strerror(errno));
+        return -ret;
+    }
+
+    // Socket's mark affects routing decisions (network selection)
+    if ((mark != MARK_UNSET) && setsockopt(s, SOL_SOCKET, SO_MARK, &mark, sizeof(mark))) {
+        int ret = errno;
+        ALOGE("setsockopt(SOL_SOCKET, SO_MARK) failed: %s", strerror(errno));
+        close(s);
+        return -ret;
+    }
+
+    // Try to connect udp socket to plat_subnet(96 bits):plat_suffix(32 bits)
+    struct sockaddr_in6 dst = {
+            .sin6_family = AF_INET6,
+            .sin6_addr = *plat_subnet,
+    };
+    dst.sin6_addr.s6_addr32[3] = plat_suffix;
+    if (connect(s, (struct sockaddr*)&dst, sizeof(dst))) {
+        int ret = errno;
+        ALOGE("connect() failed: %s", strerror(errno));
+        close(s);
+        return -ret;
+    }
+
+    // Fetch the socket's IPv6 mtu - this is effectively fetching mtu from routing table
+    int mtu;
+    socklen_t sz_mtu = sizeof(mtu);
+    if (getsockopt(s, SOL_IPV6, IPV6_MTU, &mtu, &sz_mtu)) {
+        int ret = errno;
+        ALOGE("getsockopt(SOL_IPV6, IPV6_MTU) failed: %s", strerror(errno));
+        close(s);
+        return -ret;
+    }
+    if (sz_mtu != sizeof(mtu)) {
+        ALOGE("getsockopt(SOL_IPV6, IPV6_MTU) returned unexpected size: %d", sz_mtu);
+        close(s);
+        return -EFAULT;
+    }
+    close(s);
+
+    return mtu;
+}
+
+/* function: configure_packet_socket
+ * Binds the packet socket and attaches the receive filter to it.
+ *   sock    - the socket to configure
+ *   addr    - the IP address to filter
+ *   ifindex - index of interface to add the filter to
+ * returns: 0 on success, -errno on failure
+ */
+int configure_packet_socket(int sock, in6_addr* addr, int ifindex) {
+    uint32_t* ipv6 = addr->s6_addr32;
+
+    // clang-format off
+    struct sock_filter filter_code[] = {
+    // Load the first four bytes of the IPv6 destination address (starts 24 bytes in).
+    // Compare it against the first four bytes of our IPv6 address, in host byte order (BPF loads
+    // are always in host byte order). If it matches, continue with next instruction (JMP 0). If it
+    // doesn't match, jump ahead to statement that returns 0 (ignore packet). Repeat for the other
+    // three words of the IPv6 address, and if they all match, return PACKETLEN (accept packet).
+        BPF_STMT(BPF_LD  | BPF_W   | BPF_ABS,  24),
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    htonl(ipv6[0]), 0, 7),
+        BPF_STMT(BPF_LD  | BPF_W   | BPF_ABS,  28),
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    htonl(ipv6[1]), 0, 5),
+        BPF_STMT(BPF_LD  | BPF_W   | BPF_ABS,  32),
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    htonl(ipv6[2]), 0, 3),
+        BPF_STMT(BPF_LD  | BPF_W   | BPF_ABS,  36),
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    htonl(ipv6[3]), 0, 1),
+        BPF_STMT(BPF_RET | BPF_K,              PACKETLEN),
+        BPF_STMT(BPF_RET | BPF_K,              0),
+    };
+    // clang-format on
+    struct sock_fprog filter = {sizeof(filter_code) / sizeof(filter_code[0]), filter_code};
+
+    if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))) {
+        int res = errno;
+        ALOGE("attach packet filter failed: %s", strerror(errno));
+        return -res;
+    }
+
+    struct sockaddr_ll sll = {
+            .sll_family = AF_PACKET,
+            .sll_protocol = htons(ETH_P_IPV6),
+            .sll_ifindex = ifindex,
+            .sll_pkttype =
+                    PACKET_OTHERHOST,  // The 464xlat IPv6 address is not assigned to the kernel.
+    };
+    if (bind(sock, (struct sockaddr*)&sll, sizeof(sll))) {
+        int res = errno;
+        ALOGE("binding packet socket: %s", strerror(errno));
+        return -res;
+    }
+
+    return 0;
+}
+
+}  // namespace clat
+}  // namespace net
+}  // namespace android
diff --git a/service/native/libs/libclat/clatutils_test.cpp b/service/native/libs/libclat/clatutils_test.cpp
new file mode 100644
index 0000000..4153e19
--- /dev/null
+++ b/service/native/libs/libclat/clatutils_test.cpp
@@ -0,0 +1,187 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "libclat/clatutils.h"
+
+#include <android-base/stringprintf.h>
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+#include <linux/if_packet.h>
+#include <linux/if_tun.h>
+#include "tun_interface.h"
+
+extern "C" {
+#include "checksum.h"
+}
+
+// Default translation parameters.
+static const char kIPv4LocalAddr[] = "192.0.0.4";
+
+namespace android {
+namespace net {
+namespace clat {
+
+using android::net::TunInterface;
+using base::StringPrintf;
+
+class ClatUtils : public ::testing::Test {};
+
+// Mock functions for isIpv4AddressFree.
+bool neverFree(in_addr_t /* addr */) {
+    return 0;
+}
+bool alwaysFree(in_addr_t /* addr */) {
+    return 1;
+}
+bool only2Free(in_addr_t addr) {
+    return (ntohl(addr) & 0xff) == 2;
+}
+bool over6Free(in_addr_t addr) {
+    return (ntohl(addr) & 0xff) >= 6;
+}
+bool only10Free(in_addr_t addr) {
+    return (ntohl(addr) & 0xff) == 10;
+}
+
+// Apply mocked isIpv4AddressFree function for selectIpv4Address test.
+in_addr_t selectIpv4Address(const in_addr ip, int16_t prefixlen,
+                            isIpv4AddrFreeFn fn /* mocked function */) {
+    // Call internal function to replace isIpv4AddressFreeFn for testing.
+    return selectIpv4AddressInternal(ip, prefixlen, fn);
+}
+
+TEST_F(ClatUtils, SelectIpv4Address) {
+    struct in_addr addr;
+
+    inet_pton(AF_INET, kIPv4LocalAddr, &addr);
+
+    // If no addresses are free, return INADDR_NONE.
+    EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 29, neverFree));
+    EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 16, neverFree));
+
+    // If the configured address is free, pick that. But a prefix that's too big is invalid.
+    EXPECT_EQ(inet_addr(kIPv4LocalAddr), selectIpv4Address(addr, 29, alwaysFree));
+    EXPECT_EQ(inet_addr(kIPv4LocalAddr), selectIpv4Address(addr, 20, alwaysFree));
+    EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 15, alwaysFree));
+
+    // A prefix length of 32 works, but anything above it is invalid.
+    EXPECT_EQ(inet_addr(kIPv4LocalAddr), selectIpv4Address(addr, 32, alwaysFree));
+    EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 33, alwaysFree));
+
+    // If another address is free, pick it.
+    EXPECT_EQ(inet_addr("192.0.0.6"), selectIpv4Address(addr, 29, over6Free));
+
+    // Check that we wrap around to addresses that are lower than the first address.
+    EXPECT_EQ(inet_addr("192.0.0.2"), selectIpv4Address(addr, 29, only2Free));
+    EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 30, only2Free));
+
+    // If a free address exists outside the prefix, we don't pick it.
+    EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 29, only10Free));
+    EXPECT_EQ(inet_addr("192.0.0.10"), selectIpv4Address(addr, 24, only10Free));
+
+    // Now try using the real function which sees if IP addresses are free using bind().
+    // Assume that the machine running the test has the address 127.0.0.1, but not 8.8.8.8.
+    addr.s_addr = inet_addr("8.8.8.8");
+    EXPECT_EQ(inet_addr("8.8.8.8"), selectIpv4Address(addr, 29));
+
+    addr.s_addr = inet_addr("127.0.0.1");
+    EXPECT_EQ(inet_addr("127.0.0.2"), selectIpv4Address(addr, 29));
+}
+
+TEST_F(ClatUtils, MakeChecksumNeutral) {
+    // We can't test generateIPv6Address here since it requires manipulating routing, which we can't
+    // do without talking to the real netd on the system.
+    uint32_t rand = arc4random_uniform(0xffffffff);
+    uint16_t rand1 = rand & 0xffff;
+    uint16_t rand2 = (rand >> 16) & 0xffff;
+    std::string v6PrefixStr = StringPrintf("2001:db8:%x:%x", rand1, rand2);
+    std::string v6InterfaceAddrStr = StringPrintf("%s::%x:%x", v6PrefixStr.c_str(), rand2, rand1);
+    std::string nat64PrefixStr = StringPrintf("2001:db8:%x:%x::", rand2, rand1);
+
+    in_addr v4 = {inet_addr(kIPv4LocalAddr)};
+    in6_addr v6InterfaceAddr;
+    ASSERT_TRUE(inet_pton(AF_INET6, v6InterfaceAddrStr.c_str(), &v6InterfaceAddr));
+    in6_addr nat64Prefix;
+    ASSERT_TRUE(inet_pton(AF_INET6, nat64PrefixStr.c_str(), &nat64Prefix));
+
+    // Generate a boatload of random IIDs.
+    int onebits = 0;
+    uint64_t prev_iid = 0;
+    for (int i = 0; i < 100000; i++) {
+        in6_addr v6 = v6InterfaceAddr;
+        makeChecksumNeutral(&v6, v4, nat64Prefix);
+
+        // Check the generated IP address is in the same prefix as the interface IPv6 address.
+        EXPECT_EQ(0, memcmp(&v6, &v6InterfaceAddr, 8));
+
+        // Check that consecutive IIDs are not the same.
+        uint64_t iid = *(uint64_t*)(&v6.s6_addr[8]);
+        ASSERT_TRUE(iid != prev_iid)
+                << "Two consecutive random IIDs are the same: " << std::showbase << std::hex << iid
+                << "\n";
+        prev_iid = iid;
+
+        // Check that the IID is checksum-neutral with the NAT64 prefix and the
+        // local prefix.
+        uint16_t c1 = ip_checksum_finish(ip_checksum_add(0, &v4, sizeof(v4)));
+        uint16_t c2 = ip_checksum_finish(ip_checksum_add(0, &nat64Prefix, sizeof(nat64Prefix)) +
+                                         ip_checksum_add(0, &v6, sizeof(v6)));
+
+        if (c1 != c2) {
+            char v6Str[INET6_ADDRSTRLEN];
+            inet_ntop(AF_INET6, &v6, v6Str, sizeof(v6Str));
+            FAIL() << "Bad IID: " << v6Str << " not checksum-neutral with " << kIPv4LocalAddr
+                   << " and " << nat64PrefixStr.c_str() << std::showbase << std::hex
+                   << "\n  IPv4 checksum: " << c1 << "\n  IPv6 checksum: " << c2 << "\n";
+        }
+
+        // Check that IIDs are roughly random and use all the bits by counting the
+        // total number of bits set to 1 in a random sample of 100000 generated IIDs.
+        onebits += __builtin_popcountll(*(uint64_t*)&iid);
+    }
+    EXPECT_LE(3190000, onebits);
+    EXPECT_GE(3210000, onebits);
+}
+
+TEST_F(ClatUtils, DetectMtu) {
+    // ::1 with bottom 32 bits set to 1 is still ::1 which routes via lo with mtu of 64KiB
+    ASSERT_EQ(detect_mtu(&in6addr_loopback, htonl(1), 0 /*MARK_UNSET*/), 65536);
+}
+
+TEST_F(ClatUtils, ConfigurePacketSocket) {
+    // Create an interface for configure_packet_socket to attach socket filter to.
+    TunInterface v6Iface;
+    ASSERT_EQ(0, v6Iface.init());
+
+    int s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IPV6));
+    EXPECT_LE(0, s);
+    struct in6_addr addr6;
+    EXPECT_EQ(1, inet_pton(AF_INET6, "2001:db8::f00", &addr6));
+    EXPECT_EQ(0, configure_packet_socket(s, &addr6, v6Iface.ifindex()));
+
+    // Check that the packet socket is bound to the interface. We can't check the socket filter
+    // because there is no way to fetch it from the kernel.
+    sockaddr_ll sll;
+    socklen_t len = sizeof(sll);
+    ASSERT_EQ(0, getsockname(s, reinterpret_cast<sockaddr*>(&sll), &len));
+    EXPECT_EQ(htons(ETH_P_IPV6), sll.sll_protocol);
+    EXPECT_EQ(sll.sll_ifindex, v6Iface.ifindex());
+
+    close(s);
+    v6Iface.destroy();
+}
+
+}  // namespace clat
+}  // namespace net
+}  // namespace android
diff --git a/service/native/libs/libclat/include/libclat/clatutils.h b/service/native/libs/libclat/include/libclat/clatutils.h
new file mode 100644
index 0000000..812c86e
--- /dev/null
+++ b/service/native/libs/libclat/include/libclat/clatutils.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+#include <netinet/in.h>
+#include <netinet/in6.h>
+
+namespace android {
+namespace net {
+namespace clat {
+
+bool isIpv4AddressFree(in_addr_t addr);
+in_addr_t selectIpv4Address(const in_addr ip, int16_t prefixlen);
+void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Prefix);
+int generateIpv6Address(const char* iface, const in_addr v4, const in6_addr& nat64Prefix,
+                        in6_addr* v6);
+int detect_mtu(const struct in6_addr* plat_subnet, uint32_t plat_suffix, uint32_t mark);
+int configure_packet_socket(int sock, in6_addr* addr, int ifindex);
+
+// For testing
+typedef bool (*isIpv4AddrFreeFn)(in_addr_t);
+in_addr_t selectIpv4AddressInternal(const in_addr ip, int16_t prefixlen, isIpv4AddrFreeFn fn);
+
+}  // namespace clat
+}  // namespace net
+}  // namespace android
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
new file mode 100644
index 0000000..bc63eef
--- /dev/null
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.os.ServiceSpecificException;
+import android.system.Os;
+import android.util.Log;
+
+/**
+ * BpfNetMaps is responsible for providing traffic controller relevant functionality.
+ *
+ * {@hide}
+ */
+public class BpfNetMaps {
+    private static final String TAG = "BpfNetMaps";
+
+   /**
+    * 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.
+    */
+    public void addNaughtyApp(final int uid) {
+        final int err = native_addNaughtyApp(uid);
+        if (err != 0) {
+            throw new ServiceSpecificException(-err, "Unable to add naughty app: "
+                            + Os.strerror(-err));
+        }
+    }
+
+   /**
+    * 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.
+    */
+    public void removeNaughtyApp(final int uid) {
+        final int err = native_removeNaughtyApp(uid);
+        if (err != 0) {
+            throw new ServiceSpecificException(-err, "Unable to remove naughty app: "
+                            + Os.strerror(-err));
+        }
+    }
+
+   /**
+    * 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.
+    */
+    public void addNiceApp(final int uid) {
+        final int err = native_addNiceApp(uid);
+        if (err != 0) {
+            throw new ServiceSpecificException(-err, "Unable to add nice app: "
+                            + Os.strerror(-err));
+        }
+    }
+
+   /**
+    * 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.
+    */
+    public void removeNiceApp(final int uid) {
+        final int err = native_removeNiceApp(uid);
+        if (err != 0) {
+            throw new ServiceSpecificException(-err, "Unable to remove nice app: "
+                            + Os.strerror(-err));
+        }
+    }
+
+   /**
+    * Set 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.
+    */
+    public void setChildChain(final int childChain, final boolean enable) {
+        final int err = native_setChildChain(childChain, enable);
+        if (err != 0) {
+            throw new ServiceSpecificException(-err, "Unable to set child chain: "
+                            + Os.strerror(-err));
+        }
+    }
+
+    /**
+     * Replaces the contents of the specified UID-based firewall chain.
+     *
+     * The chain may be an allowlist chain or a denylist chain. A denylist chain contains DROP
+     * rules for the specified UIDs and a RETURN rule at the end. An allowlist 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 isAllowlist Whether this is an allowlist or denylist chain.
+     * @param uids The list of UIDs to allow/deny.
+     * @return true if the chain was successfully replaced, false otherwise.
+     */
+    public int replaceUidChain(final String chainName, final boolean isAllowlist,
+            final int[] uids) {
+        final int err = native_replaceUidChain(chainName, isAllowlist, uids);
+        if (err != 0) {
+            Log.e(TAG, "replaceUidChain failed: " + Os.strerror(-err));
+        }
+        return -err;
+    }
+
+   /**
+    * 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.
+    */
+    public void setUidRule(final int childChain, final int uid,
+            final int firewallRule) {
+        final int err = native_setUidRule(childChain, uid, firewallRule);
+        if (err != 0) {
+            throw new ServiceSpecificException(-err, "Unable to set uid rule: "
+                            + Os.strerror(-err));
+        }
+    }
+
+    /**
+     * 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
+     * allowed 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.
+     */
+    public void addUidInterfaceRules(final String ifName, final int[] uids) {
+        final int err = native_addUidInterfaceRules(ifName, uids);
+        if (err != 0) {
+            throw new ServiceSpecificException(-err, "Unable to add uid interface rules: "
+                            + Os.strerror(-err));
+        }
+    }
+
+    /**
+     * 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 addUidInterfaceRules(). 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.
+     */
+    public void removeUidInterfaceRules(final int[] uids) {
+        final int err = native_removeUidInterfaceRules(uids);
+        if (err != 0) {
+            throw new ServiceSpecificException(-err, "Unable to remove uid interface rules: "
+                            + Os.strerror(-err));
+        }
+    }
+
+   /**
+    * 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.
+    */
+    public void swapActiveStatsMap() {
+        final int err = native_swapActiveStatsMap();
+        if (err != 0) {
+            throw new ServiceSpecificException(-err, "Unable to swap active stats map: "
+                            + Os.strerror(-err));
+        }
+    }
+
+   /**
+    * 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
+    */
+    public void setNetPermForUids(final int permission, final int[] uids) {
+        native_setPermissionForUids(permission, uids);
+    }
+
+    /**
+     * Set counter set for uid
+     *
+     * @param counterSet either SET_DEFAULT or SET_FOREGROUND
+     * @param uid uid to foreground/background
+     */
+    public int setCounterSet(final int counterSet, final int uid) {
+        final int err = native_setCounterSet(counterSet, uid);
+        if (err != 0) {
+            Log.e(TAG, "setCounterSet failed: " + Os.strerror(-err));
+        }
+        return -err;
+    }
+
+    /**
+     * Reset Uid stats
+     * @param tag default 0
+     * @param uid given uid to be clear
+     */
+    public int deleteTagData(final int tag, final int uid) {
+        final int err = native_deleteTagData(tag, uid);
+        if (err != 0) {
+            Log.e(TAG, "deleteTagData failed: " + Os.strerror(-err));
+        }
+        return -err;
+    }
+
+    private native int native_addNaughtyApp(int uid);
+    private native int native_removeNaughtyApp(int uid);
+    private native int native_addNiceApp(int uid);
+    private native int native_removeNiceApp(int uid);
+    private native int native_setChildChain(int childChain, boolean enable);
+    private native int native_replaceUidChain(String name, boolean isAllowlist, int[] uids);
+    private native int native_setUidRule(int childChain, int uid, int firewallRule);
+    private native int native_addUidInterfaceRules(String ifName, int[] uids);
+    private native int native_removeUidInterfaceRules(int[] uids);
+    private native int native_swapActiveStatsMap();
+    private native void native_setPermissionForUids(int permission, int[] uids);
+    private native int native_setCounterSet(int counterSet, int uid);
+    private native int native_deleteTagData(int tag, int uid);
+}
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
new file mode 100644
index 0000000..4d243c4
--- /dev/null
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import static android.net.INetd.IF_STATE_UP;
+import static android.net.INetd.PERMISSION_SYSTEM;
+
+import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.INetd;
+import android.net.InterfaceConfigurationParcel;
+import android.net.IpPrefix;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.InterfaceParams;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+
+/**
+ * This coordinator is responsible for providing clat relevant functionality.
+ *
+ * {@hide}
+ */
+public class ClatCoordinator {
+    private static final String TAG = ClatCoordinator.class.getSimpleName();
+
+    // Sync from external/android-clat/clatd.c
+    // 40 bytes IPv6 header - 20 bytes IPv4 header + 8 bytes fragment header.
+    @VisibleForTesting
+    static final int MTU_DELTA = 28;
+    @VisibleForTesting
+    static final int CLAT_MAX_MTU = 65536;
+
+    // This must match the interface prefix in clatd.c.
+    private static final String CLAT_PREFIX = "v4-";
+
+    // For historical reasons, start with 192.0.0.4, and after that, use all subsequent addresses
+    // in 192.0.0.0/29 (RFC 7335).
+    @VisibleForTesting
+    static final String INIT_V4ADDR_STRING = "192.0.0.4";
+    @VisibleForTesting
+    static final int INIT_V4ADDR_PREFIX_LEN = 29;
+    private static final InetAddress GOOGLE_DNS_4 = InetAddress.parseNumericAddress("8.8.8.8");
+
+    private static final int INVALID_IFINDEX = 0;
+    private static final int INVALID_PID = 0;
+
+    @NonNull
+    private final INetd mNetd;
+    @NonNull
+    private final Dependencies mDeps;
+    @Nullable
+    private String mIface = null;
+    private int mPid = INVALID_PID;
+
+    @VisibleForTesting
+    abstract static class Dependencies {
+        /**
+          * Get netd.
+          */
+        @NonNull
+        public abstract INetd getNetd();
+
+        /**
+         * @see ParcelFileDescriptor#adoptFd(int).
+         */
+        @NonNull
+        public ParcelFileDescriptor adoptFd(int fd) {
+            return ParcelFileDescriptor.adoptFd(fd);
+        }
+
+        /**
+         * Get interface index for a given interface.
+         */
+        public int getInterfaceIndex(String ifName) {
+            final InterfaceParams params = InterfaceParams.getByName(ifName);
+            return params != null ? params.index : INVALID_IFINDEX;
+        }
+
+        /**
+         * Create tun interface for a given interface name.
+         */
+        public int createTunInterface(@NonNull String tuniface) throws IOException {
+            return native_createTunInterface(tuniface);
+        }
+
+        /**
+         * Pick an IPv4 address for clat.
+         */
+        @NonNull
+        public String selectIpv4Address(@NonNull String v4addr, int prefixlen)
+                throws IOException {
+            return native_selectIpv4Address(v4addr, prefixlen);
+        }
+
+        /**
+         * Generate a checksum-neutral IID.
+         */
+        @NonNull
+        public String generateIpv6Address(@NonNull String iface, @NonNull String v4,
+                @NonNull String prefix64) throws IOException {
+            return native_generateIpv6Address(iface, v4, prefix64);
+        }
+
+        /**
+         * Detect MTU.
+         */
+        public int detectMtu(@NonNull String platSubnet, int platSuffix, int mark)
+                throws IOException {
+            return native_detectMtu(platSubnet, platSuffix, mark);
+        }
+
+        /**
+         * Open packet socket.
+         */
+        public int openPacketSocket() throws IOException {
+            return native_openPacketSocket();
+        }
+
+        /**
+         * Open IPv6 raw socket and set SO_MARK.
+         */
+        public int openRawSocket6(int mark) throws IOException {
+            return native_openRawSocket6(mark);
+        }
+
+        /**
+         * Add anycast setsockopt.
+         */
+        public void addAnycastSetsockopt(@NonNull FileDescriptor sock, String v6, int ifindex)
+                throws IOException {
+            native_addAnycastSetsockopt(sock, v6, ifindex);
+        }
+
+        /**
+         * Configure packet socket.
+         */
+        public void configurePacketSocket(@NonNull FileDescriptor sock, String v6, int ifindex)
+                throws IOException {
+            native_configurePacketSocket(sock, v6, ifindex);
+        }
+    }
+
+    @VisibleForTesting
+    static int getFwmark(int netId) {
+        // See union Fwmark in system/netd/include/Fwmark.h
+        return (netId & 0xffff)
+                | 0x1 << 16  // protectedFromVpn: true
+                | 0x1 << 17  // explicitlySelected: true
+                | (PERMISSION_SYSTEM & 0x3) << 18;
+    }
+
+    @VisibleForTesting
+    static int adjustMtu(int mtu) {
+        // clamp to minimum ipv6 mtu - this probably cannot ever trigger
+        if (mtu < IPV6_MIN_MTU) mtu = IPV6_MIN_MTU;
+        // clamp to buffer size
+        if (mtu > CLAT_MAX_MTU) mtu = CLAT_MAX_MTU;
+        // decrease by ipv6(40) + ipv6 fragmentation header(8) vs ipv4(20) overhead of 28 bytes
+        mtu -= MTU_DELTA;
+
+        return mtu;
+    }
+
+    public ClatCoordinator(@NonNull Dependencies deps) {
+        mDeps = deps;
+        mNetd = mDeps.getNetd();
+    }
+
+    /**
+     * Start clatd for a given interface and NAT64 prefix.
+     */
+    public String clatStart(final String iface, final int netId,
+            @NonNull final IpPrefix nat64Prefix)
+            throws IOException {
+        if (nat64Prefix.getPrefixLength() != 96) {
+            throw new IOException("Prefix must be 96 bits long: " + nat64Prefix);
+        }
+
+        // [1] Pick an IPv4 address from 192.0.0.4, 192.0.0.5, 192.0.0.6 ..
+        final String v4;
+        try {
+            v4 = mDeps.selectIpv4Address(INIT_V4ADDR_STRING, INIT_V4ADDR_PREFIX_LEN);
+        } catch (IOException e) {
+            throw new IOException("no IPv4 addresses were available for clat: " + e);
+        }
+
+        // [2] Generate a checksum-neutral IID.
+        final String pfx96 = nat64Prefix.getAddress().getHostAddress();
+        final String v6;
+        try {
+            v6 = mDeps.generateIpv6Address(iface, v4, pfx96);
+        } catch (IOException e) {
+            throw new IOException("no IPv6 addresses were available for clat: " + e);
+        }
+
+        // [3] Open, configure and bring up the tun interface.
+        // Create the v4-... tun interface.
+        final String tunIface = CLAT_PREFIX + iface;
+        final ParcelFileDescriptor tunFd;
+        try {
+            tunFd = mDeps.adoptFd(mDeps.createTunInterface(tunIface));
+        } catch (IOException e) {
+            throw new IOException("Create tun interface " + tunIface + " failed: " + e);
+        }
+
+        // disable IPv6 on it - failing to do so is not a critical error
+        try {
+            mNetd.interfaceSetEnableIPv6(tunIface, false /* enabled */);
+        } catch (RemoteException | ServiceSpecificException e) {
+            Log.e(TAG, "Disable IPv6 on " + tunIface + " failed: " + e);
+        }
+
+        // Detect ipv4 mtu.
+        final Integer fwmark = getFwmark(netId);
+        final int detectedMtu = mDeps.detectMtu(pfx96,
+                ByteBuffer.wrap(GOOGLE_DNS_4.getAddress()).getInt(), fwmark);
+        final int mtu = adjustMtu(detectedMtu);
+        Log.i(TAG, "ipv4 mtu is " + mtu);
+
+        // TODO: add setIptablesDropRule
+
+        // Config tun interface mtu, address and bring up.
+        try {
+            mNetd.interfaceSetMtu(tunIface, mtu);
+        } catch (RemoteException | ServiceSpecificException e) {
+            throw new IOException("Set MTU " + mtu + " on " + tunIface + " failed: " + e);
+        }
+        final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
+        ifConfig.ifName = tunIface;
+        ifConfig.ipv4Addr = v4;
+        ifConfig.prefixLength = 32;
+        ifConfig.hwAddr = "";
+        ifConfig.flags = new String[] {IF_STATE_UP};
+        try {
+            mNetd.interfaceSetCfg(ifConfig);
+        } catch (RemoteException | ServiceSpecificException e) {
+            throw new IOException("Setting IPv4 address to " + ifConfig.ipv4Addr + "/"
+                    + ifConfig.prefixLength + " failed on " + ifConfig.ifName + ": " + e);
+        }
+
+        // [4] Open and configure local 464xlat read/write sockets.
+        // Opens a packet socket to receive IPv6 packets in clatd.
+        final ParcelFileDescriptor readSock6;
+        try {
+            // Use a JNI call to get native file descriptor instead of Os.socket() because we would
+            // like to use ParcelFileDescriptor to close file descriptor automatically. But ctor
+            // ParcelFileDescriptor(FileDescriptor fd) is a @hide function. Need to use native file
+            // descriptor to initialize ParcelFileDescriptor object instead.
+            readSock6 = mDeps.adoptFd(mDeps.openPacketSocket());
+        } catch (IOException e) {
+            throw new IOException("Open packet socket failed: " + e);
+        }
+
+        // Opens a raw socket with a given fwmark to send IPv6 packets in clatd.
+        final ParcelFileDescriptor writeSock6;
+        try {
+            // Use a JNI call to get native file descriptor instead of Os.socket(). See above
+            // reason why we use jniOpenPacketSocket6().
+            writeSock6 = mDeps.adoptFd(mDeps.openRawSocket6(fwmark));
+        } catch (IOException e) {
+            throw new IOException("Open raw socket failed: " + e);
+        }
+
+        final int ifaceIndex = mDeps.getInterfaceIndex(iface);
+        if (ifaceIndex == INVALID_IFINDEX) {
+            throw new IOException("Fail to get interface index for interface " + iface);
+        }
+
+        // Start translating packets to the new prefix.
+        try {
+            mDeps.addAnycastSetsockopt(writeSock6.getFileDescriptor(), v6, ifaceIndex);
+        } catch (IOException e) {
+            throw new IOException("add anycast sockopt failed: " + e);
+        }
+
+        // Update our packet socket filter to reflect the new 464xlat IP address.
+        try {
+            mDeps.configurePacketSocket(readSock6.getFileDescriptor(), v6, ifaceIndex);
+        } catch (IOException e) {
+            throw new IOException("configure packet socket failed: " + e);
+        }
+
+        // TODO: start clatd and returns local xlat464 v6 address.
+        return null;
+    }
+
+    private static native String native_selectIpv4Address(String v4addr, int prefixlen)
+            throws IOException;
+    private static native String native_generateIpv6Address(String iface, String v4,
+            String prefix64) throws IOException;
+    private static native int native_createTunInterface(String tuniface) throws IOException;
+    private static native int native_detectMtu(String platSubnet, int platSuffix, int mark)
+            throws IOException;
+    private static native int native_openPacketSocket() throws IOException;
+    private static native int native_openRawSocket6(int mark) throws IOException;
+    private static native void native_addAnycastSetsockopt(FileDescriptor sock, String v6,
+            int ifindex) throws IOException;
+    private static native void native_configurePacketSocket(FileDescriptor sock, String v6,
+            int ifindex) throws IOException;
+}
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index c533dab..acf04bf 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -114,6 +114,7 @@
         // meaning @hide APIs in framework-connectivity are resolved before @SystemApi
         // stubs in framework
         "framework-connectivity.impl",
+        "framework-connectivity-tiramisu.impl",
         "framework-tethering.impl",
         "framework",
 
@@ -121,3 +122,25 @@
         "framework-res",
     ],
 }
+
+// Defaults for tests that want to run in mainline-presubmit.
+// Not widely used because many of our tests have AndroidTest.xml files and
+// use the mainline-param config-descriptor metadata in AndroidTest.xml.
+
+// test_mainline_modules is an array of strings. Each element in the array is a list of modules
+// separated by "+". The modules in this list must be in alphabetical order.
+// See SuiteModuleLoader.java.
+// TODO: why are the modules separated by + instead of being separate entries in the array?
+mainline_presubmit_modules = [
+        "CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex",
+]
+
+cc_defaults {
+    name: "connectivity-mainline-presubmit-cc-defaults",
+    test_mainline_modules: mainline_presubmit_modules,
+}
+
+java_defaults {
+    name: "connectivity-mainline-presubmit-java-defaults",
+    test_mainline_modules: mainline_presubmit_modules,
+}
diff --git a/tests/common/java/ParseExceptionTest.kt b/tests/common/java/ParseExceptionTest.kt
index b702d61..ca01c76 100644
--- a/tests/common/java/ParseExceptionTest.kt
+++ b/tests/common/java/ParseExceptionTest.kt
@@ -18,6 +18,7 @@
 import android.os.Build
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
 import com.android.testutils.DevSdkIgnoreRule
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertNull
@@ -27,6 +28,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@ConnectivityModuleTest
 class ParseExceptionTest {
     @get:Rule
     val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.R)
diff --git a/tests/common/java/android/net/CaptivePortalDataTest.kt b/tests/common/java/android/net/CaptivePortalDataTest.kt
index 18a9331..f927380 100644
--- a/tests/common/java/android/net/CaptivePortalDataTest.kt
+++ b/tests/common/java/android/net/CaptivePortalDataTest.kt
@@ -19,7 +19,6 @@
 import android.os.Build
 import androidx.test.filters.SmallTest
 import com.android.modules.utils.build.SdkLevel
-import com.android.testutils.assertParcelSane
 import com.android.testutils.assertParcelingIsLossless
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
@@ -71,9 +70,8 @@
 
     @Test
     fun testParcelUnparcel() {
-        val fieldCount = if (SdkLevel.isAtLeastS()) 10 else 7
-        assertParcelSane(data, fieldCount)
-        assertParcelSane(dataFromPasspoint, fieldCount)
+        assertParcelingIsLossless(data)
+        assertParcelingIsLossless(dataFromPasspoint)
 
         assertParcelingIsLossless(makeBuilder().setUserPortalUrl(null).build())
         assertParcelingIsLossless(makeBuilder().setVenueInfoUrl(null).build())
diff --git a/tests/common/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/common/java/android/net/ConnectivityDiagnosticsManagerTest.java
index 294ed10..03a9a80 100644
--- a/tests/common/java/android/net/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/common/java/android/net/ConnectivityDiagnosticsManagerTest.java
@@ -21,7 +21,7 @@
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
 import static android.net.ConnectivityDiagnosticsManager.DataStallReport;
 
-import static com.android.testutils.ParcelUtils.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -202,7 +202,7 @@
 
     @Test
     public void testConnectivityReportParcelUnparcel() {
-        assertParcelSane(createSampleConnectivityReport(), 5);
+        assertParcelingIsLossless(createSampleConnectivityReport());
     }
 
     private DataStallReport createSampleDataStallReport() {
@@ -303,7 +303,7 @@
 
     @Test
     public void testDataStallReportParcelUnparcel() {
-        assertParcelSane(createSampleDataStallReport(), 6);
+        assertParcelingIsLossless(createSampleDataStallReport());
     }
 
     @Test
diff --git a/tests/common/java/android/net/DhcpInfoTest.java b/tests/common/java/android/net/DhcpInfoTest.java
index ab4726b..b42e183 100644
--- a/tests/common/java/android/net/DhcpInfoTest.java
+++ b/tests/common/java/android/net/DhcpInfoTest.java
@@ -17,7 +17,6 @@
 package android.net;
 
 import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTL;
-import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
 import static com.android.testutils.ParcelUtils.parcelingRoundTrip;
 
 import static org.junit.Assert.assertEquals;
@@ -101,7 +100,6 @@
         // Cannot use assertParcelSane() here because this requires .equals() to work as
         // defined, but DhcpInfo has a different legacy behavior that we cannot change.
         final DhcpInfo dhcpInfo = createDhcpInfoObject();
-        assertFieldCountEquals(7, DhcpInfo.class);
 
         final DhcpInfo dhcpInfoRoundTrip = parcelingRoundTrip(dhcpInfo);
         assertTrue(dhcpInfoEquals(null, null));
diff --git a/tests/common/java/android/net/IpPrefixTest.java b/tests/common/java/android/net/IpPrefixTest.java
index f61c8c3..fef6416 100644
--- a/tests/common/java/android/net/IpPrefixTest.java
+++ b/tests/common/java/android/net/IpPrefixTest.java
@@ -17,7 +17,6 @@
 package android.net;
 
 import static com.android.testutils.MiscAsserts.assertEqualBothWays;
-import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
 import static com.android.testutils.MiscAsserts.assertNotEqualEitherWay;
 import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
@@ -31,6 +30,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.testutils.ConnectivityModuleTest;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -39,6 +40,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@ConnectivityModuleTest
 public class IpPrefixTest {
 
     private static InetAddress address(String addr) {
@@ -371,7 +373,5 @@
         p = new IpPrefix("192.0.2.0/25");
         assertParcelingIsLossless(p);
         assertTrue(p.isIPv4());
-
-        assertFieldCountEquals(2, IpPrefix.class);
     }
 }
diff --git a/tests/common/java/android/net/LinkAddressTest.java b/tests/common/java/android/net/LinkAddressTest.java
index 2cf3cf9..6b04fee 100644
--- a/tests/common/java/android/net/LinkAddressTest.java
+++ b/tests/common/java/android/net/LinkAddressTest.java
@@ -28,7 +28,6 @@
 import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
 
 import static com.android.testutils.MiscAsserts.assertEqualBothWays;
-import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
 import static com.android.testutils.MiscAsserts.assertNotEqualEitherWay;
 import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
@@ -44,8 +43,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.testutils.ConnectivityModuleTest;
 import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 
 import org.junit.Rule;
@@ -63,6 +62,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@ConnectivityModuleTest
 public class LinkAddressTest {
     @Rule
     public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
@@ -352,17 +352,6 @@
         assertParcelingIsLossless(l);
     }
 
-    @Test @IgnoreAfter(Build.VERSION_CODES.Q)
-    public void testFieldCount_Q() {
-        assertFieldCountEquals(4, LinkAddress.class);
-    }
-
-    @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
-    public void testFieldCount() {
-        // Make sure any new field is covered by the above parceling tests when changing this number
-        assertFieldCountEquals(6, LinkAddress.class);
-    }
-
     @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
     public void testDeprecationTime() {
         try {
diff --git a/tests/common/java/android/net/LinkPropertiesTest.java b/tests/common/java/android/net/LinkPropertiesTest.java
index 550953d..4d85a57 100644
--- a/tests/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/common/java/android/net/LinkPropertiesTest.java
@@ -20,7 +20,6 @@
 import static android.net.RouteInfo.RTN_UNICAST;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 
-import static com.android.testutils.ParcelUtils.assertParcelSane;
 import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 import static com.android.testutils.ParcelUtils.parcelingRoundTrip;
 
@@ -41,6 +40,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
+import com.android.testutils.ConnectivityModuleTest;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -60,6 +60,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@ConnectivityModuleTest
 public class LinkPropertiesTest {
     @Rule
     public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
@@ -1006,7 +1007,7 @@
     @Test @IgnoreAfter(Build.VERSION_CODES.Q)
     public void testLinkPropertiesParcelable_Q() throws Exception {
         final LinkProperties source = makeLinkPropertiesForParceling();
-        assertParcelSane(source, 14 /* fieldCount */);
+        assertParcelingIsLossless(source);
     }
 
     @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
@@ -1017,8 +1018,7 @@
         source.setCaptivePortalApiUrl(CAPPORT_API_URL);
         source.setCaptivePortalData((CaptivePortalData) getCaptivePortalData());
         source.setDhcpServerAddress((Inet4Address) GATEWAY1);
-        assertParcelSane(new LinkProperties(source, true /* parcelSensitiveFields */),
-                18 /* fieldCount */);
+        assertParcelingIsLossless(new LinkProperties(source, true /* parcelSensitiveFields */));
 
         // Verify that without using a sensitiveFieldsParcelingCopy, sensitive fields are cleared.
         final LinkProperties sanitized = new LinkProperties(source);
diff --git a/tests/common/java/android/net/MatchAllNetworkSpecifierTest.kt b/tests/common/java/android/net/MatchAllNetworkSpecifierTest.kt
index a5e44d5..4a4859d 100644
--- a/tests/common/java/android/net/MatchAllNetworkSpecifierTest.kt
+++ b/tests/common/java/android/net/MatchAllNetworkSpecifierTest.kt
@@ -22,14 +22,11 @@
 import android.os.Build
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-
-import com.android.testutils.assertParcelSane
+import com.android.testutils.ConnectivityModuleTest
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
-
-import java.lang.IllegalStateException
-
+import com.android.testutils.assertParcelingIsLossless
 import org.junit.Assert.assertFalse
 import org.junit.Rule
 import org.junit.Test
@@ -38,6 +35,7 @@
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
+@ConnectivityModuleTest
 class MatchAllNetworkSpecifierTest {
     @Rule @JvmField
     val ignoreRule: DevSdkIgnoreRule = DevSdkIgnoreRule()
@@ -50,7 +48,7 @@
 
     @Test
     fun testParcel() {
-        assertParcelSane(MatchAllNetworkSpecifier(), 0)
+        assertParcelingIsLossless(MatchAllNetworkSpecifier())
     }
 
     @Test
diff --git a/tests/common/java/android/net/NattKeepalivePacketDataTest.kt b/tests/common/java/android/net/NattKeepalivePacketDataTest.kt
index 46f39dd..ad7a526 100644
--- a/tests/common/java/android/net/NattKeepalivePacketDataTest.kt
+++ b/tests/common/java/android/net/NattKeepalivePacketDataTest.kt
@@ -23,10 +23,9 @@
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
 import com.android.testutils.assertEqualBothWays
-import com.android.testutils.assertFieldCountEquals
-import com.android.testutils.assertParcelSane
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.assertParcelingIsLossless
 import com.android.testutils.parcelingRoundTrip
 import java.net.InetAddress
 import org.junit.Assert.assertEquals
@@ -93,7 +92,7 @@
 
     @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
     fun testParcel() {
-        assertParcelSane(nattKeepalivePacket(), 0)
+        assertParcelingIsLossless(nattKeepalivePacket())
     }
 
     @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
@@ -103,8 +102,6 @@
         assertNotEquals(nattKeepalivePacket(srcAddress = TEST_DST_ADDRV4), nattKeepalivePacket())
         // Test src port only because dst port have to be NATT_PORT
         assertNotEquals(nattKeepalivePacket(srcPort = TEST_PORT2), nattKeepalivePacket())
-        // Make sure the parceling test is updated if fields are added in the base class.
-        assertFieldCountEquals(5, KeepalivePacketData::class.java)
     }
 
     @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
diff --git a/tests/common/java/android/net/NetworkAgentConfigTest.kt b/tests/common/java/android/net/NetworkAgentConfigTest.kt
index ed9995c..b339a27 100644
--- a/tests/common/java/android/net/NetworkAgentConfigTest.kt
+++ b/tests/common/java/android/net/NetworkAgentConfigTest.kt
@@ -20,9 +20,10 @@
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
 import com.android.modules.utils.build.SdkLevel.isAtLeastS
+import com.android.testutils.ConnectivityModuleTest
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -32,6 +33,7 @@
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
+@ConnectivityModuleTest
 class NetworkAgentConfigTest {
     @Rule @JvmField
     val ignoreRule = DevSdkIgnoreRule()
@@ -48,20 +50,7 @@
                 setBypassableVpn(true)
             }
         }.build()
-        // This test can be run as unit test against the latest system image, as CTS to verify
-        // an Android release that is as recent as the test, or as MTS to verify the
-        // Connectivity module. In the first two cases NetworkAgentConfig will be as recent
-        // as the test. In the last case, starting from S NetworkAgentConfig is updated as part
-        // of Connectivity, so it is also as recent as the test. For MTS on Q and R,
-        // NetworkAgentConfig is not updatable, so it may have a different number of fields.
-        if (isAtLeastS()) {
-            // When this test is run on S+, NetworkAgentConfig is as recent as the test,
-            // so this should be the most recent known number of fields.
-            assertParcelSane(config, 13)
-        } else {
-            // For R or below, the config will have 10 items
-            assertParcelSane(config, 10)
-        }
+        assertParcelingIsLossless(config)
     }
 
     @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index 2a4df7a..09d36e5 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -57,9 +57,9 @@
 import static com.android.modules.utils.build.SdkLevel.isAtLeastR;
 import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
 import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 import static com.android.testutils.MiscAsserts.assertEmpty;
 import static com.android.testutils.MiscAsserts.assertThrows;
-import static com.android.testutils.ParcelUtils.assertParcelSane;
 import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -344,21 +344,7 @@
     }
 
     private void testParcelSane(NetworkCapabilities cap) {
-        // This test can be run as unit test against the latest system image, as CTS to verify
-        // an Android release that is as recent as the test, or as MTS to verify the
-        // Connectivity module. In the first two cases NetworkCapabilities will be as recent
-        // as the test. In the last case, starting from S NetworkCapabilities is updated as part
-        // of Connectivity, so it is also as recent as the test. For MTS on Q and R,
-        // NetworkCapabilities is not updatable, so it may have a different number of fields.
-        if (isAtLeastS()) {
-            // When this test is run on S+, NetworkCapabilities is as recent as the test,
-            // so this should be the most recent known number of fields.
-            assertParcelSane(cap, 18);
-        } else if (isAtLeastR()) {
-            assertParcelSane(cap, 15);
-        } else {
-            assertParcelSane(cap, 11);
-        }
+        assertParcelingIsLossless(cap);
     }
 
     private static NetworkCapabilities createNetworkCapabilitiesWithTransportInfo() {
@@ -803,7 +789,7 @@
         } catch (IllegalStateException expected) { }
     }
 
-    @Test @IgnoreUpTo(Build.VERSION_CODES.S)
+    @Test @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
     public void testEnterpriseCapabilitySubLevel() {
         final NetworkCapabilities nc1 = new NetworkCapabilities.Builder()
                 .addCapability(NET_CAPABILITY_ENTERPRISE)
diff --git a/tests/common/java/android/net/NetworkProviderTest.kt b/tests/common/java/android/net/NetworkProviderTest.kt
index ff5de1d..3ceacf8 100644
--- a/tests/common/java/android/net/NetworkProviderTest.kt
+++ b/tests/common/java/android/net/NetworkProviderTest.kt
@@ -32,6 +32,7 @@
 import androidx.test.InstrumentationRegistry
 import com.android.net.module.util.ArrayTrackRecord
 import com.android.testutils.CompatUtil
+import com.android.testutils.ConnectivityModuleTest
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
@@ -62,6 +63,7 @@
 
 @RunWith(DevSdkIgnoreRunner::class)
 @IgnoreUpTo(Build.VERSION_CODES.Q)
+@ConnectivityModuleTest
 class NetworkProviderTest {
     @Rule @JvmField
     val mIgnoreRule = DevSdkIgnoreRule()
diff --git a/tests/common/java/android/net/NetworkSpecifierTest.kt b/tests/common/java/android/net/NetworkSpecifierTest.kt
index f3409f5..b960417 100644
--- a/tests/common/java/android/net/NetworkSpecifierTest.kt
+++ b/tests/common/java/android/net/NetworkSpecifierTest.kt
@@ -17,18 +17,20 @@
 
 import android.os.Build
 import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.DevSdkIgnoreRunner
-import kotlin.test.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
 import kotlin.test.assertEquals
 import kotlin.test.assertFalse
 import kotlin.test.assertNotEquals
-import org.junit.Test
-import org.junit.runner.RunWith
+import kotlin.test.assertTrue
 
 @SmallTest
 @RunWith(DevSdkIgnoreRunner::class)
 @IgnoreUpTo(Build.VERSION_CODES.Q)
+@ConnectivityModuleTest
 class NetworkSpecifierTest {
     private class TestNetworkSpecifier(
         val intData: Int = 123,
diff --git a/tests/common/java/android/net/NetworkStateSnapshotTest.kt b/tests/common/java/android/net/NetworkStateSnapshotTest.kt
index 0ca4d95..0dad6a8 100644
--- a/tests/common/java/android/net/NetworkStateSnapshotTest.kt
+++ b/tests/common/java/android/net/NetworkStateSnapshotTest.kt
@@ -22,9 +22,10 @@
 import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.os.Build
 import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
 import org.junit.Test
 import org.junit.runner.RunWith
 import java.net.Inet4Address
@@ -59,6 +60,7 @@
 @SmallTest
 @RunWith(DevSdkIgnoreRunner::class)
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@ConnectivityModuleTest
 class NetworkStateSnapshotTest {
 
     @Test
@@ -67,7 +69,7 @@
                 LinkProperties(), null, TYPE_NONE)
         val snapshot = NetworkStateSnapshot(
                 Network(TEST_NETID), TEST_CAPABILITIES, TEST_LINK_PROPERTIES, TEST_IMSI, TYPE_WIFI)
-        assertParcelSane(emptySnapshot, 5)
-        assertParcelSane(snapshot, 5)
+        assertParcelingIsLossless(emptySnapshot)
+        assertParcelingIsLossless(snapshot)
     }
 }
diff --git a/tests/common/java/android/net/NetworkTest.java b/tests/common/java/android/net/NetworkTest.java
index 7423c73..c102cb3 100644
--- a/tests/common/java/android/net/NetworkTest.java
+++ b/tests/common/java/android/net/NetworkTest.java
@@ -28,6 +28,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.testutils.ConnectivityModuleTest;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -46,6 +47,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@ConnectivityModuleTest
 public class NetworkTest {
     final Network mNetwork = new Network(99);
 
diff --git a/tests/common/java/android/net/OemNetworkPreferencesTest.java b/tests/common/java/android/net/OemNetworkPreferencesTest.java
index fd29a95..d96f80c 100644
--- a/tests/common/java/android/net/OemNetworkPreferencesTest.java
+++ b/tests/common/java/android/net/OemNetworkPreferencesTest.java
@@ -17,7 +17,7 @@
 package android.net;
 
 import static com.android.testutils.MiscAsserts.assertThrows;
-import static com.android.testutils.ParcelUtils.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -27,6 +27,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.testutils.ConnectivityModuleTest;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.DevSdkIgnoreRunner;
 
@@ -38,6 +39,7 @@
 @IgnoreUpTo(Build.VERSION_CODES.R)
 @RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@ConnectivityModuleTest
 public class OemNetworkPreferencesTest {
 
     private static final int TEST_PREF = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED;
@@ -101,7 +103,7 @@
 
         final OemNetworkPreferences prefs = mBuilder.build();
 
-        assertParcelSane(prefs, 1 /* fieldCount */);
+        assertParcelingIsLossless(prefs);
     }
 
     @Test
diff --git a/tests/common/java/android/net/RouteInfoTest.java b/tests/common/java/android/net/RouteInfoTest.java
index b69b045..5b28b84 100644
--- a/tests/common/java/android/net/RouteInfoTest.java
+++ b/tests/common/java/android/net/RouteInfoTest.java
@@ -21,7 +21,6 @@
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 
 import static com.android.testutils.MiscAsserts.assertEqualBothWays;
-import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
 import static com.android.testutils.MiscAsserts.assertNotEqualEitherWay;
 import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
@@ -38,8 +37,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.testutils.ConnectivityModuleTest;
 import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 
 import org.junit.Rule;
@@ -52,6 +51,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@ConnectivityModuleTest
 public class RouteInfoTest {
     @Rule
     public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
@@ -383,17 +383,6 @@
         assertParcelingIsLossless(r);
     }
 
-    @Test @IgnoreAfter(Build.VERSION_CODES.Q)
-    public void testFieldCount_Q() {
-        assertFieldCountEquals(6, RouteInfo.class);
-    }
-
-    @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
-    public void testFieldCount() {
-        // Make sure any new field is covered by the above parceling tests when changing this number
-        assertFieldCountEquals(7, RouteInfo.class);
-    }
-
     @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
     public void testMtu() {
         RouteInfo r;
diff --git a/tests/common/java/android/net/TcpKeepalivePacketDataTest.kt b/tests/common/java/android/net/TcpKeepalivePacketDataTest.kt
index 7a18bb0..063ea23 100644
--- a/tests/common/java/android/net/TcpKeepalivePacketDataTest.kt
+++ b/tests/common/java/android/net/TcpKeepalivePacketDataTest.kt
@@ -20,8 +20,7 @@
 import android.os.Build
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.assertFieldCountEquals
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
 import org.junit.Test
 import org.junit.runner.RunWith
 import java.net.InetAddress
@@ -68,15 +67,11 @@
         assertNotEquals(makeData(tcpWndScale = 3), makeData())
         assertNotEquals(makeData(ipTos = 0x14), makeData())
         assertNotEquals(makeData(ipTtl = 11), makeData())
-
-        // Update above assertions if field is added
-        assertFieldCountEquals(5, KeepalivePacketData::class.java)
-        assertFieldCountEquals(6, TcpKeepalivePacketData::class.java)
     }
 
     @Test
     fun testParcelUnparcel() {
-        assertParcelSane(makeData(), fieldCount = 6) { a, b ->
+        assertParcelingIsLossless(makeData()) { a, b ->
             // .equals() does not verify .packet
             a == b && a.packet contentEquals b.packet
         }
@@ -98,9 +93,5 @@
         assertTrue(str.contains(data.getTcpWindowScale().toString()))
         assertTrue(str.contains(data.getIpTos().toString()))
         assertTrue(str.contains(data.getIpTtl().toString()))
-
-        // Update above assertions if field is added
-        assertFieldCountEquals(5, KeepalivePacketData::class.java)
-        assertFieldCountEquals(6, TcpKeepalivePacketData::class.java)
     }
 }
\ No newline at end of file
diff --git a/tests/common/java/android/net/UidRangeTest.java b/tests/common/java/android/net/UidRangeTest.java
index a435119..d46fdc9 100644
--- a/tests/common/java/android/net/UidRangeTest.java
+++ b/tests/common/java/android/net/UidRangeTest.java
@@ -35,6 +35,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.testutils.ConnectivityModuleTest;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 
@@ -46,6 +47,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@ConnectivityModuleTest
 public class UidRangeTest {
 
     /*
diff --git a/tests/common/java/android/net/UnderlyingNetworkInfoTest.kt b/tests/common/java/android/net/UnderlyingNetworkInfoTest.kt
index f23ba26..a041c4e 100644
--- a/tests/common/java/android/net/UnderlyingNetworkInfoTest.kt
+++ b/tests/common/java/android/net/UnderlyingNetworkInfoTest.kt
@@ -18,9 +18,10 @@
 
 import android.os.Build
 import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
 import org.junit.Test
 import org.junit.runner.RunWith
 import kotlin.test.assertEquals
@@ -32,6 +33,7 @@
 @SmallTest
 @RunWith(DevSdkIgnoreRunner::class)
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@ConnectivityModuleTest
 class UnderlyingNetworkInfoTest {
     @Test
     fun testParcelUnparcel() {
@@ -39,12 +41,12 @@
         assertEquals(TEST_OWNER_UID, testInfo.getOwnerUid())
         assertEquals(TEST_IFACE, testInfo.getInterface())
         assertEquals(TEST_IFACE_LIST, testInfo.getUnderlyingInterfaces())
-        assertParcelSane(testInfo, 3)
+        assertParcelingIsLossless(testInfo)
 
         val emptyInfo = UnderlyingNetworkInfo(0, String(), listOf())
         assertEquals(0, emptyInfo.getOwnerUid())
         assertEquals(String(), emptyInfo.getInterface())
         assertEquals(listOf(), emptyInfo.getUnderlyingInterfaces())
-        assertParcelSane(emptyInfo, 3)
+        assertParcelingIsLossless(emptyInfo)
     }
 }
\ No newline at end of file
diff --git a/tests/common/java/android/net/apf/ApfCapabilitiesTest.java b/tests/common/java/android/net/apf/ApfCapabilitiesTest.java
index 88996d9..fa4adcb 100644
--- a/tests/common/java/android/net/apf/ApfCapabilitiesTest.java
+++ b/tests/common/java/android/net/apf/ApfCapabilitiesTest.java
@@ -16,7 +16,7 @@
 
 package android.net.apf;
 
-import static com.android.testutils.ParcelUtils.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -62,7 +62,7 @@
         assertEquals(456, caps.maximumApfProgramSize);
         assertEquals(789, caps.apfPacketFormat);
 
-        assertParcelSane(caps, 3);
+        assertParcelingIsLossless(caps);
     }
 
     @Test
diff --git a/tests/common/java/android/net/metrics/ApfProgramEventTest.kt b/tests/common/java/android/net/metrics/ApfProgramEventTest.kt
index 0b7b740..1c175da 100644
--- a/tests/common/java/android/net/metrics/ApfProgramEventTest.kt
+++ b/tests/common/java/android/net/metrics/ApfProgramEventTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -48,7 +48,7 @@
         assertEquals(5, apfProgramEvent.programLength)
         assertEquals(ApfProgramEvent.flagsFor(true, true), apfProgramEvent.flags)
 
-        assertParcelSane(apfProgramEvent, 6)
+        assertParcelingIsLossless(apfProgramEvent)
     }
 
     @Test
diff --git a/tests/common/java/android/net/metrics/ApfStatsTest.kt b/tests/common/java/android/net/metrics/ApfStatsTest.kt
index 46a8c8e..610e674 100644
--- a/tests/common/java/android/net/metrics/ApfStatsTest.kt
+++ b/tests/common/java/android/net/metrics/ApfStatsTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -52,6 +52,6 @@
         assertEquals(8, apfStats.programUpdatesAllowingMulticast)
         assertEquals(9, apfStats.maxProgramSize)
 
-        assertParcelSane(apfStats, 10)
+        assertParcelingIsLossless(apfStats)
     }
 }
diff --git a/tests/common/java/android/net/metrics/DhcpClientEventTest.kt b/tests/common/java/android/net/metrics/DhcpClientEventTest.kt
index 8d7a9c4..4c70e11 100644
--- a/tests/common/java/android/net/metrics/DhcpClientEventTest.kt
+++ b/tests/common/java/android/net/metrics/DhcpClientEventTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -38,6 +38,6 @@
         assertEquals(FAKE_MESSAGE, dhcpClientEvent.msg)
         assertEquals(Integer.MAX_VALUE, dhcpClientEvent.durationMs)
 
-        assertParcelSane(dhcpClientEvent, 2)
+        assertParcelingIsLossless(dhcpClientEvent)
     }
 }
diff --git a/tests/common/java/android/net/metrics/IpManagerEventTest.kt b/tests/common/java/android/net/metrics/IpManagerEventTest.kt
index 64be508..bb21dca 100644
--- a/tests/common/java/android/net/metrics/IpManagerEventTest.kt
+++ b/tests/common/java/android/net/metrics/IpManagerEventTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -33,7 +33,7 @@
             assertEquals(it, ipManagerEvent.eventType)
             assertEquals(Long.MAX_VALUE, ipManagerEvent.durationMs)
 
-            assertParcelSane(ipManagerEvent, 2)
+            assertParcelingIsLossless(ipManagerEvent)
         }
     }
 }
diff --git a/tests/common/java/android/net/metrics/IpReachabilityEventTest.kt b/tests/common/java/android/net/metrics/IpReachabilityEventTest.kt
index 55b5e49..3d21b81 100644
--- a/tests/common/java/android/net/metrics/IpReachabilityEventTest.kt
+++ b/tests/common/java/android/net/metrics/IpReachabilityEventTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -32,7 +32,7 @@
             val ipReachabilityEvent = IpReachabilityEvent(it)
             assertEquals(it, ipReachabilityEvent.eventType)
 
-            assertParcelSane(ipReachabilityEvent, 1)
+            assertParcelingIsLossless(ipReachabilityEvent)
         }
     }
 }
diff --git a/tests/common/java/android/net/metrics/NetworkEventTest.kt b/tests/common/java/android/net/metrics/NetworkEventTest.kt
index 41430b0..17b5e2d 100644
--- a/tests/common/java/android/net/metrics/NetworkEventTest.kt
+++ b/tests/common/java/android/net/metrics/NetworkEventTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -37,7 +37,7 @@
             assertEquals(it, networkEvent.eventType)
             assertEquals(Long.MAX_VALUE, networkEvent.durationMs)
 
-            assertParcelSane(networkEvent, 2)
+            assertParcelingIsLossless(networkEvent)
         }
     }
 }
diff --git a/tests/common/java/android/net/metrics/RaEventTest.kt b/tests/common/java/android/net/metrics/RaEventTest.kt
index d9b7203..e9daa0f 100644
--- a/tests/common/java/android/net/metrics/RaEventTest.kt
+++ b/tests/common/java/android/net/metrics/RaEventTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -67,6 +67,6 @@
         assertEquals(5, raEvent.rdnssLifetime)
         assertEquals(6, raEvent.dnsslLifetime)
 
-        assertParcelSane(raEvent, 6)
+        assertParcelingIsLossless(raEvent)
     }
 }
diff --git a/tests/common/java/android/net/metrics/ValidationProbeEventTest.kt b/tests/common/java/android/net/metrics/ValidationProbeEventTest.kt
index 51c0d41..7dfa7e1 100644
--- a/tests/common/java/android/net/metrics/ValidationProbeEventTest.kt
+++ b/tests/common/java/android/net/metrics/ValidationProbeEventTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
 import java.lang.reflect.Modifier
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
@@ -51,7 +51,7 @@
         assertTrue(validationProbeEvent.probeType hasType FIRST_VALIDATION)
         assertEquals(ValidationProbeEvent.DNS_SUCCESS, validationProbeEvent.returnCode)
 
-        assertParcelSane(validationProbeEvent, 3)
+        assertParcelingIsLossless(validationProbeEvent)
     }
 
     @Test
diff --git a/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt b/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt
index 7b22e45..c90b1aa 100644
--- a/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt
+++ b/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt
@@ -31,7 +31,6 @@
 import android.os.Build
 import androidx.test.filters.SmallTest
 import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.assertFieldCountEquals
 import com.android.testutils.assertNetworkStatsEquals
 import com.android.testutils.assertParcelingIsLossless
 import org.junit.Before
@@ -176,7 +175,6 @@
         assertParcelingIsLossless(testStatsEmpty)
         assertParcelingIsLossless(testStats1)
         assertParcelingIsLossless(testStats2)
-        assertFieldCountEquals(15, NetworkStats::class.java)
     }
 
     @Test
diff --git a/tests/cts/OWNERS b/tests/cts/OWNERS
index 4264345..8dfa455 100644
--- a/tests/cts/OWNERS
+++ b/tests/cts/OWNERS
@@ -1,4 +1,3 @@
 # Bug component: 31808
 set noparent
-lorenzo@google.com
-satk@google.com
\ No newline at end of file
+file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking_xts
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index 916b566..5778b0d 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -36,6 +36,7 @@
 
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 import static com.android.testutils.Cleanup.testAndCleanup;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -71,7 +72,6 @@
 import android.net.VpnTransportInfo;
 import android.net.cts.util.CtsNetUtils;
 import android.net.wifi.WifiManager;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.ParcelFileDescriptor;
@@ -830,7 +830,7 @@
                                 .getCaps().getUnderlyingNetworks())));
     }
 
-    @Test @IgnoreUpTo(Build.VERSION_CODES.S)
+    @Test @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
     public void testChangeUnderlyingNetworks() throws Exception {
         assumeTrue(supportedHardware());
         assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 81c30b1..f66231d 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -49,6 +49,7 @@
         "FrameworksNetCommonTests",
         "core-tests-support",
         "cts-net-utils",
+        "CtsNetTestsNonUpdatableLib",
         "ctstestrunner-axt",
         "junit",
         "junit-params",
diff --git a/tests/cts/net/src/android/net/cts/DhcpOptionTest.kt b/tests/cts/net/src/android/net/cts/DhcpOptionTest.kt
index 1a62560..555dd87 100644
--- a/tests/cts/net/src/android/net/cts/DhcpOptionTest.kt
+++ b/tests/cts/net/src/android/net/cts/DhcpOptionTest.kt
@@ -16,11 +16,11 @@
 
 package android.net.cts
 
-import android.os.Build
 import android.net.DhcpOption
 import androidx.test.filters.SmallTest
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.SC_V2
 import org.junit.Assert.assertArrayEquals
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNull
@@ -28,7 +28,7 @@
 import org.junit.Test
 
 @SmallTest
-@IgnoreUpTo(Build.VERSION_CODES.S)
+@IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
 @RunWith(DevSdkIgnoreRunner::class)
 class DhcpOptionTest {
     private val DHCP_OPTION_TYPE: Byte = 2
diff --git a/tests/cts/net/src/android/net/cts/DnsResolverTest.java b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
index 4992795..c6fc38f 100644
--- a/tests/cts/net/src/android/net/cts/DnsResolverTest.java
+++ b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
@@ -25,6 +25,8 @@
 import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
 import static android.system.OsConstants.ETIMEDOUT;
 
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -43,7 +45,6 @@
 import android.net.NetworkRequest;
 import android.net.ParseException;
 import android.net.cts.util.CtsNetUtils;
-import android.os.Build;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.Looper;
@@ -814,7 +815,7 @@
     }
 
     /** Verifies that DnsResolver.DnsException can be subclassed and its constructor re-used. */
-    @Test @IgnoreUpTo(Build.VERSION_CODES.S)
+    @Test @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
     public void testDnsExceptionConstructor() throws InterruptedException {
         class TestDnsException extends DnsResolver.DnsException {
             TestDnsException(int code, @Nullable Throwable cause) {
diff --git a/tests/cts/net/src/android/net/cts/IpConfigurationTest.java b/tests/cts/net/src/android/net/cts/IpConfigurationTest.java
index 56ab2a7..385bf9e 100644
--- a/tests/cts/net/src/android/net/cts/IpConfigurationTest.java
+++ b/tests/cts/net/src/android/net/cts/IpConfigurationTest.java
@@ -16,7 +16,7 @@
 
 package android.net.cts;
 
-import static com.android.testutils.ParcelUtils.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
@@ -118,6 +118,6 @@
     @Test
     public void testParcel() {
         final IpConfiguration config = new IpConfiguration();
-        assertParcelSane(config, 4);
+        assertParcelingIsLossless(config);
     }
 }
diff --git a/tests/cts/net/src/android/net/cts/LocalSocketTest.java b/tests/cts/net/src/android/net/cts/LocalSocketTest.java
deleted file mode 100644
index 6e61705..0000000
--- a/tests/cts/net/src/android/net/cts/LocalSocketTest.java
+++ /dev/null
@@ -1,470 +0,0 @@
-/*
- * Copyright (C) 2008 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.cts;
-
-import junit.framework.TestCase;
-
-import android.net.Credentials;
-import android.net.LocalServerSocket;
-import android.net.LocalSocket;
-import android.net.LocalSocketAddress;
-import android.system.Os;
-import android.system.OsConstants;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-public class LocalSocketTest extends TestCase {
-    private final static String ADDRESS_PREFIX = "com.android.net.LocalSocketTest";
-
-    public void testLocalConnections() throws IOException {
-        String address = ADDRESS_PREFIX + "_testLocalConnections";
-        // create client and server socket
-        LocalServerSocket localServerSocket = new LocalServerSocket(address);
-        LocalSocket clientSocket = new LocalSocket();
-
-        // establish connection between client and server
-        LocalSocketAddress locSockAddr = new LocalSocketAddress(address);
-        assertFalse(clientSocket.isConnected());
-        clientSocket.connect(locSockAddr);
-        assertTrue(clientSocket.isConnected());
-
-        LocalSocket serverSocket = localServerSocket.accept();
-        assertTrue(serverSocket.isConnected());
-        assertTrue(serverSocket.isBound());
-        try {
-            serverSocket.bind(localServerSocket.getLocalSocketAddress());
-            fail("Cannot bind a LocalSocket from accept()");
-        } catch (IOException expected) {
-        }
-        try {
-            serverSocket.connect(locSockAddr);
-            fail("Cannot connect a LocalSocket from accept()");
-        } catch (IOException expected) {
-        }
-
-        Credentials credent = clientSocket.getPeerCredentials();
-        assertTrue(0 != credent.getPid());
-
-        // send data from client to server
-        OutputStream clientOutStream = clientSocket.getOutputStream();
-        clientOutStream.write(12);
-        InputStream serverInStream = serverSocket.getInputStream();
-        assertEquals(12, serverInStream.read());
-
-        //send data from server to client
-        OutputStream serverOutStream = serverSocket.getOutputStream();
-        serverOutStream.write(3);
-        InputStream clientInStream = clientSocket.getInputStream();
-        assertEquals(3, clientInStream.read());
-
-        // Test sending and receiving file descriptors
-        clientSocket.setFileDescriptorsForSend(new FileDescriptor[]{FileDescriptor.in});
-        clientOutStream.write(32);
-        assertEquals(32, serverInStream.read());
-
-        FileDescriptor[] out = serverSocket.getAncillaryFileDescriptors();
-        assertEquals(1, out.length);
-        FileDescriptor fd = clientSocket.getFileDescriptor();
-        assertTrue(fd.valid());
-
-        //shutdown input stream of client
-        clientSocket.shutdownInput();
-        assertEquals(-1, clientInStream.read());
-
-        //shutdown output stream of client
-        clientSocket.shutdownOutput();
-        try {
-            clientOutStream.write(10);
-            fail("testLocalSocket shouldn't come to here");
-        } catch (IOException e) {
-            // expected
-        }
-
-        //shutdown input stream of server
-        serverSocket.shutdownInput();
-        assertEquals(-1, serverInStream.read());
-
-        //shutdown output stream of server
-        serverSocket.shutdownOutput();
-        try {
-            serverOutStream.write(10);
-            fail("testLocalSocket shouldn't come to here");
-        } catch (IOException e) {
-            // expected
-        }
-
-        //close client socket
-        clientSocket.close();
-        try {
-            clientInStream.read();
-            fail("testLocalSocket shouldn't come to here");
-        } catch (IOException e) {
-            // expected
-        }
-
-        //close server socket
-        serverSocket.close();
-        try {
-            serverInStream.read();
-            fail("testLocalSocket shouldn't come to here");
-        } catch (IOException e) {
-            // expected
-        }
-    }
-
-    public void testAccessors() throws IOException {
-        String address = ADDRESS_PREFIX + "_testAccessors";
-        LocalSocket socket = new LocalSocket();
-        LocalSocketAddress addr = new LocalSocketAddress(address);
-
-        assertFalse(socket.isBound());
-        socket.bind(addr);
-        assertTrue(socket.isBound());
-        assertEquals(addr, socket.getLocalSocketAddress());
-
-        String str = socket.toString();
-        assertTrue(str.contains("impl:android.net.LocalSocketImpl"));
-
-        socket.setReceiveBufferSize(1999);
-        assertEquals(1999 << 1, socket.getReceiveBufferSize());
-
-        socket.setSendBufferSize(3998);
-        assertEquals(3998 << 1, socket.getSendBufferSize());
-
-        assertEquals(0, socket.getSoTimeout());
-        socket.setSoTimeout(1996);
-        assertTrue(socket.getSoTimeout() > 0);
-
-        try {
-            socket.getRemoteSocketAddress();
-            fail("testLocalSocketSecondary shouldn't come to here");
-        } catch (UnsupportedOperationException e) {
-            // expected
-        }
-
-        try {
-            socket.isClosed();
-            fail("testLocalSocketSecondary shouldn't come to here");
-        } catch (UnsupportedOperationException e) {
-            // expected
-        }
-
-        try {
-            socket.isInputShutdown();
-            fail("testLocalSocketSecondary shouldn't come to here");
-        } catch (UnsupportedOperationException e) {
-            // expected
-        }
-
-        try {
-            socket.isOutputShutdown();
-            fail("testLocalSocketSecondary shouldn't come to here");
-        } catch (UnsupportedOperationException e) {
-            // expected
-        }
-
-        try {
-            socket.connect(addr, 2005);
-            fail("testLocalSocketSecondary shouldn't come to here");
-        } catch (UnsupportedOperationException e) {
-            // expected
-        }
-
-        socket.close();
-    }
-
-    // http://b/31205169
-    public void testSetSoTimeout_readTimeout() throws Exception {
-        String address = ADDRESS_PREFIX + "_testSetSoTimeout_readTimeout";
-
-        try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
-            final LocalSocket clientSocket = socketPair.clientSocket;
-
-            // Set the timeout in millis.
-            int timeoutMillis = 1000;
-            clientSocket.setSoTimeout(timeoutMillis);
-
-            // Avoid blocking the test run if timeout doesn't happen by using a separate thread.
-            Callable<Result> reader = () -> {
-                try {
-                    clientSocket.getInputStream().read();
-                    return Result.noException("Did not block");
-                } catch (IOException e) {
-                    return Result.exception(e);
-                }
-            };
-            // Allow the configured timeout, plus some slop.
-            int allowedTime = timeoutMillis + 2000;
-            Result result = runInSeparateThread(allowedTime, reader);
-
-            // Check the message was a timeout, it's all we have to go on.
-            String expectedMessage = Os.strerror(OsConstants.EAGAIN);
-            result.assertThrewIOException(expectedMessage);
-        }
-    }
-
-    // http://b/31205169
-    public void testSetSoTimeout_writeTimeout() throws Exception {
-        String address = ADDRESS_PREFIX + "_testSetSoTimeout_writeTimeout";
-
-        try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
-            final LocalSocket clientSocket = socketPair.clientSocket;
-
-            // Set the timeout in millis.
-            int timeoutMillis = 1000;
-            clientSocket.setSoTimeout(timeoutMillis);
-
-            // Set a small buffer size so we know we can flood it.
-            clientSocket.setSendBufferSize(100);
-            final int bufferSize = clientSocket.getSendBufferSize();
-
-            // Avoid blocking the test run if timeout doesn't happen by using a separate thread.
-            Callable<Result> writer = () -> {
-                try {
-                    byte[] toWrite = new byte[bufferSize * 2];
-                    clientSocket.getOutputStream().write(toWrite);
-                    return Result.noException("Did not block");
-                } catch (IOException e) {
-                    return Result.exception(e);
-                }
-            };
-            // Allow the configured timeout, plus some slop.
-            int allowedTime = timeoutMillis + 2000;
-
-            Result result = runInSeparateThread(allowedTime, writer);
-
-            // Check the message was a timeout, it's all we have to go on.
-            String expectedMessage = Os.strerror(OsConstants.EAGAIN);
-            result.assertThrewIOException(expectedMessage);
-        }
-    }
-
-    public void testAvailable() throws Exception {
-        String address = ADDRESS_PREFIX + "_testAvailable";
-
-        try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
-            LocalSocket clientSocket = socketPair.clientSocket;
-            LocalSocket serverSocket = socketPair.serverSocket.accept();
-
-            OutputStream clientOutputStream = clientSocket.getOutputStream();
-            InputStream serverInputStream = serverSocket.getInputStream();
-            assertEquals(0, serverInputStream.available());
-
-            byte[] buffer = new byte[50];
-            clientOutputStream.write(buffer);
-            assertEquals(50, serverInputStream.available());
-
-            InputStream clientInputStream = clientSocket.getInputStream();
-            OutputStream serverOutputStream = serverSocket.getOutputStream();
-            assertEquals(0, clientInputStream.available());
-            serverOutputStream.write(buffer);
-            assertEquals(50, serverInputStream.available());
-
-            serverSocket.close();
-        }
-    }
-
-    // http://b/34095140
-    public void testLocalSocketCreatedFromFileDescriptor() throws Exception {
-        String address = ADDRESS_PREFIX + "_testLocalSocketCreatedFromFileDescriptor";
-
-        // Establish connection between a local client and server to get a valid client socket file
-        // descriptor.
-        try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
-            // Extract the client FileDescriptor we can use.
-            FileDescriptor fileDescriptor = socketPair.clientSocket.getFileDescriptor();
-            assertTrue(fileDescriptor.valid());
-
-            // Create the LocalSocket we want to test.
-            LocalSocket clientSocketCreatedFromFileDescriptor =
-                    LocalSocket.createConnectedLocalSocket(fileDescriptor);
-            assertTrue(clientSocketCreatedFromFileDescriptor.isConnected());
-            assertTrue(clientSocketCreatedFromFileDescriptor.isBound());
-
-            // Test the LocalSocket can be used for communication.
-            LocalSocket serverSocket = socketPair.serverSocket.accept();
-            OutputStream clientOutputStream =
-                    clientSocketCreatedFromFileDescriptor.getOutputStream();
-            InputStream serverInputStream = serverSocket.getInputStream();
-
-            clientOutputStream.write(12);
-            assertEquals(12, serverInputStream.read());
-
-            // Closing clientSocketCreatedFromFileDescriptor does not close the file descriptor.
-            clientSocketCreatedFromFileDescriptor.close();
-            assertTrue(fileDescriptor.valid());
-
-            // .. while closing the LocalSocket that owned the file descriptor does.
-            socketPair.clientSocket.close();
-            assertFalse(fileDescriptor.valid());
-        }
-    }
-
-    public void testFlush() throws Exception {
-        String address = ADDRESS_PREFIX + "_testFlush";
-
-        try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
-            LocalSocket clientSocket = socketPair.clientSocket;
-            LocalSocket serverSocket = socketPair.serverSocket.accept();
-
-            OutputStream clientOutputStream = clientSocket.getOutputStream();
-            InputStream serverInputStream = serverSocket.getInputStream();
-            testFlushWorks(clientOutputStream, serverInputStream);
-
-            OutputStream serverOutputStream = serverSocket.getOutputStream();
-            InputStream clientInputStream = clientSocket.getInputStream();
-            testFlushWorks(serverOutputStream, clientInputStream);
-
-            serverSocket.close();
-        }
-    }
-
-    private void testFlushWorks(OutputStream outputStream, InputStream inputStream)
-            throws Exception {
-        final int bytesToTransfer = 50;
-        StreamReader inputStreamReader = new StreamReader(inputStream, bytesToTransfer);
-
-        byte[] buffer = new byte[bytesToTransfer];
-        outputStream.write(buffer);
-        assertEquals(bytesToTransfer, inputStream.available());
-
-        // Start consuming the data.
-        inputStreamReader.start();
-
-        // This doesn't actually flush any buffers, it just polls until the reader has read all the
-        // bytes.
-        outputStream.flush();
-
-        inputStreamReader.waitForCompletion(5000);
-        inputStreamReader.assertBytesRead(bytesToTransfer);
-        assertEquals(0, inputStream.available());
-    }
-
-    private static class StreamReader extends Thread {
-        private final InputStream is;
-        private final int expectedByteCount;
-        private final CountDownLatch completeLatch = new CountDownLatch(1);
-
-        private volatile Exception exception;
-        private int bytesRead;
-
-        private StreamReader(InputStream is, int expectedByteCount) {
-            this.is = is;
-            this.expectedByteCount = expectedByteCount;
-        }
-
-        @Override
-        public void run() {
-            try {
-                byte[] buffer = new byte[10];
-                int readCount;
-                while ((readCount = is.read(buffer)) >= 0) {
-                    bytesRead += readCount;
-                    if (bytesRead >= expectedByteCount) {
-                        break;
-                    }
-                }
-            } catch (IOException e) {
-                exception = e;
-            } finally {
-                completeLatch.countDown();
-            }
-        }
-
-        public void waitForCompletion(long waitMillis) throws Exception {
-            if (!completeLatch.await(waitMillis, TimeUnit.MILLISECONDS)) {
-                fail("Timeout waiting for completion");
-            }
-            if (exception != null) {
-                throw new Exception("Read failed", exception);
-            }
-        }
-
-        public void assertBytesRead(int expected) {
-            assertEquals(expected, bytesRead);
-        }
-    }
-
-    private static class Result {
-        private final String type;
-        private final Exception e;
-
-        private Result(String type, Exception e) {
-            this.type = type;
-            this.e = e;
-        }
-
-        static Result noException(String description) {
-            return new Result(description, null);
-        }
-
-        static Result exception(Exception e) {
-            return new Result(e.getClass().getName(), e);
-        }
-
-        void assertThrewIOException(String expectedMessage) {
-            assertEquals("Unexpected result type", IOException.class.getName(), type);
-            assertEquals("Unexpected exception message", expectedMessage, e.getMessage());
-        }
-    }
-
-    private static Result runInSeparateThread(int allowedTime, final Callable<Result> callable)
-            throws Exception {
-        ExecutorService service = Executors.newSingleThreadScheduledExecutor();
-        Future<Result> future = service.submit(callable);
-        Result result = future.get(allowedTime, TimeUnit.MILLISECONDS);
-        if (!future.isDone()) {
-            fail("Worker thread appears blocked");
-        }
-        return result;
-    }
-
-    private static class LocalSocketPair implements AutoCloseable {
-        static LocalSocketPair createConnectedSocketPair(String address) throws Exception {
-            LocalServerSocket localServerSocket = new LocalServerSocket(address);
-            final LocalSocket clientSocket = new LocalSocket();
-
-            // Establish connection between client and server
-            LocalSocketAddress locSockAddr = new LocalSocketAddress(address);
-            clientSocket.connect(locSockAddr);
-            assertTrue(clientSocket.isConnected());
-            return new LocalSocketPair(localServerSocket, clientSocket);
-        }
-
-        final LocalServerSocket serverSocket;
-        final LocalSocket clientSocket;
-
-        LocalSocketPair(LocalServerSocket serverSocket, LocalSocket clientSocket) {
-            this.serverSocket = serverSocket;
-            this.clientSocket = clientSocket;
-        }
-
-        public void close() throws Exception {
-            serverSocket.close();
-            clientSocket.close();
-        }
-    }
-}
diff --git a/tests/cts/net/src/android/net/cts/MacAddressTest.java b/tests/cts/net/src/android/net/cts/MacAddressTest.java
index 3fd3bba..e47155b 100644
--- a/tests/cts/net/src/android/net/cts/MacAddressTest.java
+++ b/tests/cts/net/src/android/net/cts/MacAddressTest.java
@@ -20,7 +20,7 @@
 import static android.net.MacAddress.TYPE_MULTICAST;
 import static android.net.MacAddress.TYPE_UNICAST;
 
-import static com.android.testutils.ParcelUtils.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertFalse;
@@ -218,6 +218,6 @@
     public void testParcelMacAddress() {
         final MacAddress mac = MacAddress.fromString("52:74:f2:b1:a8:7f");
 
-        assertParcelSane(mac, 1);
+        assertParcelingIsLossless(mac);
     }
 }
diff --git a/tests/mts/Android.bp b/tests/mts/Android.bp
index a56f76e..2c44010 100644
--- a/tests/mts/Android.bp
+++ b/tests/mts/Android.bp
@@ -23,6 +23,9 @@
         "general-tests",
         "mts-tethering",
     ],
+    defaults: [
+        "connectivity-mainline-presubmit-cc-defaults",
+    ],
     require_root: true,
     static_libs: [
         "libbase",
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index ac8096c..142e013 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -92,7 +92,7 @@
     a->insert(b.begin(), b.end());
 }
 
-void removeAll(set<string>* a, const set<string> b) {
+void removeAll(set<string>* a, const set<string>& b) {
     for (const auto& toRemove : b) {
         a->erase(toRemove);
     }
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 3735ca4..48751f4 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -130,6 +130,7 @@
         "platform-compat-test-rules",
         "platform-test-annotations",
         "service-connectivity-pre-jarjar",
+        "service-connectivity-tiramisu-pre-jarjar",
         "services.core-vpn",
     ],
     libs: [
diff --git a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
index 6e51069..561e621 100644
--- a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
+++ b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
@@ -240,6 +240,27 @@
         assertFalse(stats.hasNextBucket());
     }
 
+
+    @Test
+    public void testQueryDetailsForDevice() throws Exception {
+        final long startTime = 1;
+        final long endTime = 100;
+
+        reset(mStatsSession);
+        when(mService.openSessionForUsageStats(anyInt(), anyString())).thenReturn(mStatsSession);
+        when(mStatsSession.getHistoryIntervalForNetwork(any(NetworkTemplate.class),
+                anyInt(), anyLong(), anyLong()))
+                .thenReturn(new NetworkStatsHistory(10, 0));
+        final NetworkTemplate template = new NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE)
+                .setMeteredness(NetworkStats.Bucket.METERED_YES).build();
+        NetworkStats stats = mManager.queryDetailsForDevice(template, startTime, endTime);
+
+        verify(mStatsSession, times(1)).getHistoryIntervalForNetwork(
+                eq(template), eq(NetworkStatsHistory.FIELD_ALL), eq(startTime), eq(endTime));
+
+        assertFalse(stats.hasNextBucket());
+    }
+
     private void assertBucketMatches(Entry expected, NetworkStats.Bucket actual) {
         assertEquals(expected.uid, actual.getUid());
         assertEquals(expected.rxBytes, actual.getRxBytes());
diff --git a/tests/unit/java/android/net/Ikev2VpnProfileTest.java b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
index 83de40e..a151f03 100644
--- a/tests/unit/java/android/net/Ikev2VpnProfileTest.java
+++ b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
@@ -35,6 +35,7 @@
 import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -64,6 +65,9 @@
     private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
     private static final int TEST_MTU = 1300;
 
+    @Rule
+    public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
     private final MockContext mMockContext =
             new MockContext() {
                 @Override
diff --git a/tests/unit/java/android/net/NetworkStatsAccessTest.java b/tests/unit/java/android/net/NetworkStatsAccessTest.java
index e4fc118..97a93ca 100644
--- a/tests/unit/java/android/net/NetworkStatsAccessTest.java
+++ b/tests/unit/java/android/net/NetworkStatsAccessTest.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.when;
 
@@ -25,7 +27,6 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.os.Build;
 import android.telephony.TelephonyManager;
 
 import androidx.test.filters.SmallTest;
@@ -42,7 +43,7 @@
 
 @RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S)
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
 public class NetworkStatsAccessTest {
     private static final String TEST_PKG = "com.example.test";
     private static final int TEST_PID = 1234;
diff --git a/tests/unit/java/android/net/NetworkStatsCollectionTest.java b/tests/unit/java/android/net/NetworkStatsCollectionTest.java
index 1c557d6..2e82986 100644
--- a/tests/unit/java/android/net/NetworkStatsCollectionTest.java
+++ b/tests/unit/java/android/net/NetworkStatsCollectionTest.java
@@ -29,6 +29,7 @@
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
 import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 import static com.android.testutils.MiscAsserts.assertThrows;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -37,7 +38,6 @@
 import static org.junit.Assert.fail;
 
 import android.content.res.Resources;
-import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
 import android.telephony.SubscriptionPlan;
@@ -59,6 +59,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -78,7 +79,7 @@
  */
 @RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S)
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
 public class NetworkStatsCollectionTest {
 
     private static final String TEST_FILE = "test.bin";
@@ -195,8 +196,8 @@
         // record empty data straddling between buckets
         final NetworkStats.Entry entry = new NetworkStats.Entry();
         entry.rxBytes = 32;
-        collection.recordData(null, UID_ALL, SET_DEFAULT, TAG_NONE, 30 * MINUTE_IN_MILLIS,
-                90 * MINUTE_IN_MILLIS, entry);
+        collection.recordData(Mockito.mock(NetworkIdentitySet.class), UID_ALL, SET_DEFAULT,
+                TAG_NONE, 30 * MINUTE_IN_MILLIS, 90 * MINUTE_IN_MILLIS, entry);
 
         // assert that we report boundary in atomic buckets
         assertEquals(0, collection.getStartMillis());
diff --git a/tests/unit/java/android/net/NetworkStatsTest.java b/tests/unit/java/android/net/NetworkStatsTest.java
index be2857e..b0cc16c 100644
--- a/tests/unit/java/android/net/NetworkStatsTest.java
+++ b/tests/unit/java/android/net/NetworkStatsTest.java
@@ -37,6 +37,7 @@
 import static android.net.NetworkStats.UID_ALL;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.os.Build;
@@ -53,8 +54,10 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.Iterator;
 
 @RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
@@ -1037,6 +1040,29 @@
         assertEquals(secondEntry, stats.getValues(1, null));
     }
 
+    @Test
+    public void testIterator() {
+        final NetworkStats emptyStats = new NetworkStats(0, 0);
+        final Iterator emptyIterator = emptyStats.iterator();
+        assertFalse(emptyIterator.hasNext());
+
+        final int numEntries = 10;
+        final ArrayList<NetworkStats.Entry> entries = new ArrayList<>();
+        final NetworkStats stats = new NetworkStats(TEST_START, 1);
+        for (int i = 0; i < numEntries; ++i) {
+            NetworkStats.Entry entry = new NetworkStats.Entry("test1", 10100, SET_DEFAULT,
+                    TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
+                    i * 10L /* rxBytes */, i * 3L /* rxPackets */,
+                    i * 15L /* txBytes */, i * 2L /* txPackets */, 0L /* operations */);
+            stats.insertEntry(entry);
+            entries.add(entry);
+        }
+
+        for (NetworkStats.Entry e : stats) {
+            assertEquals(e, entries.remove(0));
+        }
+    }
+
     private static void assertContains(NetworkStats stats,  String iface, int uid, int set,
             int tag, int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets,
             long txBytes, long txPackets, long operations) {
diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt
index 15db45c..0c3bee3 100644
--- a/tests/unit/java/android/net/NetworkTemplateTest.kt
+++ b/tests/unit/java/android/net/NetworkTemplateTest.kt
@@ -22,7 +22,6 @@
 import android.net.NetworkIdentity.OEM_NONE
 import android.net.NetworkIdentity.OEM_PAID
 import android.net.NetworkIdentity.OEM_PRIVATE
-import android.net.NetworkIdentity.SUBTYPE_COMBINED
 import android.net.NetworkIdentity.buildNetworkIdentity
 import android.net.NetworkStats.DEFAULT_NETWORK_ALL
 import android.net.NetworkStats.METERED_ALL
@@ -57,6 +56,7 @@
 import com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.SC_V2
 import com.android.testutils.assertParcelSane
 import org.junit.Before
 import org.junit.Test
@@ -312,7 +312,7 @@
         val identLteMetered = buildNetworkIdentity(
                 mockContext, stateMobileImsi1Metered, false, TelephonyManager.NETWORK_TYPE_LTE)
         val identCombinedMetered = buildNetworkIdentity(
-                mockContext, stateMobileImsi1Metered, false, SUBTYPE_COMBINED)
+                mockContext, stateMobileImsi1Metered, false, NetworkTemplate.NETWORK_TYPE_ALL)
         val identImsi2UmtsMetered = buildNetworkIdentity(mockContext,
                 buildMobileNetworkState(TEST_IMSI2), false, TelephonyManager.NETWORK_TYPE_UMTS)
         val identWifi = buildNetworkIdentity(
@@ -326,7 +326,7 @@
         val identLteNonMetered = buildNetworkIdentity(
                 mockContext, stateMobileImsi1NonMetered, false, TelephonyManager.NETWORK_TYPE_LTE)
         val identCombinedNonMetered = buildNetworkIdentity(
-                mockContext, stateMobileImsi1NonMetered, false, SUBTYPE_COMBINED)
+                mockContext, stateMobileImsi1NonMetered, false, NetworkTemplate.NETWORK_TYPE_ALL)
         val identImsi2UmtsNonMetered = buildNetworkIdentity(mockContext,
                 stateMobileImsi2NonMetered, false, TelephonyManager.NETWORK_TYPE_UMTS)
 
@@ -556,7 +556,7 @@
         }
     }
 
-    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S)
+    @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
     @Test
     fun testBuilderMatchRules() {
         // Verify unknown match rules cannot construct templates.
@@ -657,7 +657,7 @@
         }
     }
 
-    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S)
+    @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
     @Test
     fun testBuilderWifiNetworkKeys() {
         // Verify template builder which generates same template with the given different
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
index 416549c..d993d1f 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -29,6 +29,8 @@
 import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
@@ -41,7 +43,6 @@
 import android.net.NetworkStats;
 import android.net.NetworkStatsAccess;
 import android.net.NetworkTemplate;
-import android.os.Build;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -74,7 +75,7 @@
  */
 @RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S)
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
 public class NetworkStatsObserversTest {
     private static final String TEST_IFACE = "test0";
     private static final String TEST_IFACE2 = "test1";
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 9009aaa..5e1699a 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -66,8 +66,10 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -116,6 +118,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.net.module.util.LocationPermissionChecker;
 import com.android.server.net.NetworkStatsService.AlertObserver;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
@@ -190,6 +193,8 @@
     @Mock
     private NetworkStatsSubscriptionsMonitor mNetworkStatsSubscriptionsMonitor;
     private HandlerThread mHandlerThread;
+    @Mock
+    private LocationPermissionChecker mLocationPermissionChecker;
 
     private NetworkStatsService mService;
     private INetworkStatsSession mSession;
@@ -259,6 +264,8 @@
         MockitoAnnotations.initMocks(this);
         final Context context = InstrumentationRegistry.getContext();
         mServiceContext = new MockContext(context);
+        when(mLocationPermissionChecker.checkCallersLocationPermission(
+                any(), any(), anyInt(), anyBoolean(), any())).thenReturn(true);
         when(sWifiInfo.getCurrentNetworkKey()).thenReturn(TEST_WIFI_NETWORK_KEY);
         mStatsDir = TestIoUtils.createTemporaryDirectory(getClass().getSimpleName());
 
@@ -331,6 +338,10 @@
                 return mContentObserver = super.makeContentObserver(handler, settings, monitor);
             }
 
+            @Override
+            public LocationPermissionChecker makeLocationPermissionChecker(final Context context) {
+                return mLocationPermissionChecker;
+            }
         };
     }
 
@@ -1623,6 +1634,28 @@
         provider.expectOnRequestStatsUpdate(0 /* unused */);
     }
 
+    /**
+     * Verify the service will throw exceptions if the template is location sensitive but
+     * the permission is not granted.
+     */
+    @Test
+    public void testEnforceTemplateLocationPermission() throws Exception {
+        when(mLocationPermissionChecker.checkCallersLocationPermission(
+                any(), any(), anyInt(), anyBoolean(), any())).thenReturn(false);
+        initWifiStats(buildWifiState(true, TEST_IFACE, IMSI_1));
+        assertThrows(SecurityException.class, () ->
+                assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0));
+        // Templates w/o wifi network keys can query stats as usual.
+        assertNetworkTotal(sTemplateCarrierWifi1, 0L, 0L, 0L, 0L, 0);
+        assertNetworkTotal(sTemplateImsi1, 0L, 0L, 0L, 0L, 0);
+
+        when(mLocationPermissionChecker.checkCallersLocationPermission(
+                any(), any(), anyInt(), anyBoolean(), any())).thenReturn(true);
+        assertNetworkTotal(sTemplateCarrierWifi1, 0L, 0L, 0L, 0L, 0);
+        assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
+        assertNetworkTotal(sTemplateImsi1, 0L, 0L, 0L, 0L, 0);
+    }
+
     private static File getBaseDir(File statsDir) {
         File baseDir = new File(statsDir, "netstats");
         baseDir.mkdirs();
@@ -1638,7 +1671,8 @@
     private void assertNetworkTotal(NetworkTemplate template, long start, long end, long rxBytes,
             long rxPackets, long txBytes, long txPackets, int operations) throws Exception {
         // verify history API
-        final NetworkStatsHistory history = mSession.getHistoryForNetwork(template, FIELD_ALL);
+        final NetworkStatsHistory history =
+                mSession.getHistoryIntervalForNetwork(template, FIELD_ALL, start, end);
         assertValues(history, start, end, rxBytes, rxPackets, txBytes, txPackets, operations);
 
         // verify summary API