Merge "Include both eth%d and usb%d ethernet interfaces on U+" into main
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 9757daa..9132857 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -96,6 +96,7 @@
     },
     binaries: [
         "clatd",
+        "netbpfload",
         "ot-daemon",
     ],
     canned_fs_config: "canned_fs_config",
diff --git a/Tethering/apex/canned_fs_config b/Tethering/apex/canned_fs_config
index 5a03347..1f5fcfa 100644
--- a/Tethering/apex/canned_fs_config
+++ b/Tethering/apex/canned_fs_config
@@ -1,2 +1,3 @@
 /bin/for-system 0 1000 0750
 /bin/for-system/clatd 1029 1029 06755
+/bin/netbpfload 0 0 0750
diff --git a/Tethering/common/TetheringLib/api/current.txt b/Tethering/common/TetheringLib/api/current.txt
index d802177..14191eb 100644
--- a/Tethering/common/TetheringLib/api/current.txt
+++ b/Tethering/common/TetheringLib/api/current.txt
@@ -1 +1,3 @@
 // Signature format: 2.0
+// - add-additional-overrides=no
+// - migrating=Migration in progress see b/299366704
diff --git a/Tethering/common/TetheringLib/api/module-lib-current.txt b/Tethering/common/TetheringLib/api/module-lib-current.txt
index 460c216..f09b26d 100644
--- a/Tethering/common/TetheringLib/api/module-lib-current.txt
+++ b/Tethering/common/TetheringLib/api/module-lib-current.txt
@@ -1,4 +1,6 @@
 // Signature format: 2.0
+// - add-additional-overrides=no
+// - migrating=Migration in progress see b/299366704
 package android.net {
 
   public final class TetheringConstants {
diff --git a/Tethering/common/TetheringLib/api/module-lib-removed.txt b/Tethering/common/TetheringLib/api/module-lib-removed.txt
index d802177..14191eb 100644
--- a/Tethering/common/TetheringLib/api/module-lib-removed.txt
+++ b/Tethering/common/TetheringLib/api/module-lib-removed.txt
@@ -1 +1,3 @@
 // Signature format: 2.0
+// - add-additional-overrides=no
+// - migrating=Migration in progress see b/299366704
diff --git a/Tethering/common/TetheringLib/api/removed.txt b/Tethering/common/TetheringLib/api/removed.txt
index d802177..14191eb 100644
--- a/Tethering/common/TetheringLib/api/removed.txt
+++ b/Tethering/common/TetheringLib/api/removed.txt
@@ -1 +1,3 @@
 // Signature format: 2.0
+// - add-additional-overrides=no
+// - migrating=Migration in progress see b/299366704
diff --git a/Tethering/common/TetheringLib/api/system-current.txt b/Tethering/common/TetheringLib/api/system-current.txt
index 844ff64..83cee25 100644
--- a/Tethering/common/TetheringLib/api/system-current.txt
+++ b/Tethering/common/TetheringLib/api/system-current.txt
@@ -1,4 +1,6 @@
 // Signature format: 2.0
+// - add-additional-overrides=no
+// - migrating=Migration in progress see b/299366704
 package android.net {
 
   public final class TetheredClient implements android.os.Parcelable {
diff --git a/Tethering/common/TetheringLib/api/system-removed.txt b/Tethering/common/TetheringLib/api/system-removed.txt
index d802177..14191eb 100644
--- a/Tethering/common/TetheringLib/api/system-removed.txt
+++ b/Tethering/common/TetheringLib/api/system-removed.txt
@@ -1 +1,3 @@
 // Signature format: 2.0
+// - add-additional-overrides=no
+// - migrating=Migration in progress see b/299366704
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
index 81d4fbe..60f2d17 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
@@ -44,6 +44,7 @@
 import com.android.net.module.util.netlink.NetlinkUtils;
 import com.android.net.module.util.netlink.StructNlMsgHdr;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -84,6 +85,14 @@
         mOffloadHw = new OffloadHardwareInterface(mHandler, mLog, mDeps);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        if (mHandlerThread != null) {
+            mHandlerThread.quitSafely();
+            mHandlerThread.join();
+        }
+    }
+
     void findConnectionOrThrow(FileDescriptor fd, InetSocketAddress local, InetSocketAddress remote)
             throws Exception {
         Log.d(TAG, "Looking for socket " + local + " -> " + remote);
diff --git a/bpf_progs/block.c b/bpf_progs/block.c
index d734b74..0a2b0b8 100644
--- a/bpf_progs/block.c
+++ b/bpf_progs/block.c
@@ -24,8 +24,8 @@
 
 #include "bpf_helpers.h"
 
-#define ALLOW 1
-#define DISALLOW 0
+static const int ALLOW = 1;
+static const int DISALLOW = 0;
 
 DEFINE_BPF_MAP_GRW(blocked_ports_map, ARRAY, int, uint64_t,
         1024 /* 64K ports -> 1024 u64s */, AID_SYSTEM)
@@ -57,14 +57,18 @@
     return ALLOW;
 }
 
-DEFINE_BPF_PROG_KVER("bind4/block_port", AID_ROOT, AID_SYSTEM,
-                     bind4_block_port, KVER(5, 4, 0))
+// the program need to be accessible/loadable by netd (from netd updatable plugin)
+#define DEFINE_NETD_RO_BPF_PROG(SECTION_NAME, the_prog, min_kver) \
+    DEFINE_BPF_PROG_EXT(SECTION_NAME, AID_ROOT, AID_ROOT, the_prog, min_kver, KVER_INF,  \
+                        BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, MANDATORY, \
+                        "", "netd_readonly/", LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG)
+
+DEFINE_NETD_RO_BPF_PROG("bind4/block_port", bind4_block_port, KVER_4_19)
 (struct bpf_sock_addr *ctx) {
     return block_port(ctx);
 }
 
-DEFINE_BPF_PROG_KVER("bind6/block_port", AID_ROOT, AID_SYSTEM,
-                     bind6_block_port, KVER(5, 4, 0))
+DEFINE_NETD_RO_BPF_PROG("bind6/block_port", bind6_block_port, KVER_4_19)
 (struct bpf_sock_addr *ctx) {
     return block_port(ctx);
 }
diff --git a/bpf_progs/bpf_net_helpers.h b/bpf_progs/bpf_net_helpers.h
index bb5e330..f3c7de5 100644
--- a/bpf_progs/bpf_net_helpers.h
+++ b/bpf_progs/bpf_net_helpers.h
@@ -87,36 +87,18 @@
     if (skb->data_end - skb->data < len) bpf_skb_pull_data(skb, len);
 }
 
-// constants for passing in to 'bool shared' (for maps)
-static const bool PRIVATE = false;
-static const bool SHARED = true;
+struct egress_bool { bool egress; };
+#define INGRESS ((struct egress_bool){ .egress = false })
+#define EGRESS ((struct egress_bool){ .egress = true })
 
-// constants for passing in to 'bool optional' (for programs)
-static const bool MANDATORY = false;
-static const bool OPTIONAL = true;
+struct stream_bool { bool down; };
+#define UPSTREAM ((struct stream_bool){ .down = false })
+#define DOWNSTREAM ((struct stream_bool){ .down = true })
 
-// constants for passing in to 'bool egress'
-static const bool INGRESS = false;
-static const bool EGRESS = true;
+struct rawip_bool { bool rawip; };
+#define ETHER ((struct rawip_bool){ .rawip = false })
+#define RAWIP ((struct rawip_bool){ .rawip = true })
 
-// constants for passing in to 'bool downstream'
-static const bool UPSTREAM = false;
-static const bool DOWNSTREAM = true;
-
-// constants for passing in to 'bool is_ethernet'
-static const bool RAWIP = false;
-static const bool ETHER = true;
-
-// constants for passing in to 'bool updatetime'
-static const bool NO_UPDATETIME = false;
-static const bool UPDATETIME = true;
-
-// constants for passing in to ignore_on_eng / ignore_on_user / ignore_on_userdebug
-static const bool LOAD_ON_ENG = false;
-static const bool LOAD_ON_USER = false;
-static const bool LOAD_ON_USERDEBUG = false;
-static const bool IGNORE_ON_ENG = true;
-static const bool IGNORE_ON_USER = true;
-static const bool IGNORE_ON_USERDEBUG = true;
-
-#define KVER_4_14 KVER(4, 14, 0)
+struct updatetime_bool { bool updatetime; };
+#define NO_UPDATETIME ((struct updatetime_bool){ .updatetime = false })
+#define UPDATETIME ((struct updatetime_bool){ .updatetime = true })
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index 8f0ff84..addb02f 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -55,8 +55,10 @@
 DEFINE_BPF_MAP_GRW(clat_ingress6_map, HASH, ClatIngress6Key, ClatIngress6Value, 16, AID_SYSTEM)
 
 static inline __always_inline int nat64(struct __sk_buff* skb,
-                                        const bool is_ethernet,
-                                        const unsigned kver) {
+                                        const struct rawip_bool rawip,
+                                        const struct kver_uint kver) {
+    const bool is_ethernet = !rawip.rawip;
+
     // Require ethernet dst mac address to be our unicast address.
     if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_PIPE;
 
@@ -115,7 +117,7 @@
 
     if (proto == IPPROTO_FRAGMENT) {
         // Fragment handling requires bpf_skb_adjust_room which is 4.14+
-        if (kver < KVER_4_14) return TC_ACT_PIPE;
+        if (!KVER_IS_AT_LEAST(kver, 4, 14, 0)) return TC_ACT_PIPE;
 
         // Must have (ethernet and) ipv6 header and ipv6 fragment extension header
         if (data + l2_header_size + sizeof(*ip6) + sizeof(struct frag_hdr) > data_end)
@@ -233,7 +235,7 @@
     //
     // Note: we currently have no TreeHugger coverage for 4.9-T devices (there are no such
     // Pixel or cuttlefish devices), so likely you won't notice for months if this breaks...
-    if (kver >= KVER_4_14 && frag_off != htons(IP_DF)) {
+    if (KVER_IS_AT_LEAST(kver, 4, 14, 0) && frag_off != htons(IP_DF)) {
         // If we're converting an IPv6 Fragment, we need to trim off 8 more bytes
         // We're beyond recovery on error here... but hard to imagine how this could fail.
         if (bpf_skb_adjust_room(skb, -(__s32)sizeof(struct frag_hdr), BPF_ADJ_ROOM_NET, /*flags*/0))
diff --git a/bpf_progs/dscpPolicy.c b/bpf_progs/dscpPolicy.c
index 88b50b5..e845a69 100644
--- a/bpf_progs/dscpPolicy.c
+++ b/bpf_progs/dscpPolicy.c
@@ -222,7 +222,7 @@
 }
 
 DEFINE_BPF_PROG_KVER("schedcls/set_dscp_ether", AID_ROOT, AID_SYSTEM, schedcls_set_dscp_ether,
-                     KVER(5, 15, 0))
+                     KVER_5_15)
 (struct __sk_buff* skb) {
     if (skb->pkt_type != PACKET_HOST) return TC_ACT_PIPE;
 
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 7a48e8c..9017976 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -178,8 +178,8 @@
 #define DEFINE_UPDATE_STATS(the_stats_map, TypeOfKey)                                            \
     static __always_inline inline void update_##the_stats_map(const struct __sk_buff* const skb, \
                                                               const TypeOfKey* const key,        \
-                                                              const bool egress,                 \
-                                                              const unsigned kver) {             \
+                                                              const struct egress_bool egress,   \
+                                                              const struct kver_uint kver) {     \
         StatsValue* value = bpf_##the_stats_map##_lookup_elem(key);                              \
         if (!value) {                                                                            \
             StatsValue newValue = {};                                                            \
@@ -199,7 +199,7 @@
                 packets = (payload + mss - 1) / mss;                                             \
                 bytes = tcp_overhead * packets + payload;                                        \
             }                                                                                    \
-            if (egress) {                                                                        \
+            if (egress.egress) {                                                                 \
                 __sync_fetch_and_add(&value->txPackets, packets);                                \
                 __sync_fetch_and_add(&value->txBytes, bytes);                                    \
             } else {                                                                             \
@@ -219,7 +219,7 @@
                                                          const int L3_off,
                                                          void* const to,
                                                          const int len,
-                                                         const unsigned kver) {
+                                                         const struct kver_uint kver) {
     // 'kver' (here and throughout) is the compile time guaranteed minimum kernel version,
     // ie. we're building (a version of) the bpf program for kver (or newer!) kernels.
     //
@@ -236,16 +236,16 @@
     //
     // For similar reasons this will fail with non-offloaded VLAN tags on < 4.19 kernels,
     // since those extend the ethernet header from 14 to 18 bytes.
-    return kver >= KVER(4, 19, 0)
+    return KVER_IS_AT_LEAST(kver, 4, 19, 0)
         ? bpf_skb_load_bytes_relative(skb, L3_off, to, len, BPF_HDR_START_NET)
         : bpf_skb_load_bytes(skb, L3_off, to, len);
 }
 
 static __always_inline inline void do_packet_tracing(
-        const struct __sk_buff* const skb, const bool egress, const uint32_t uid,
-        const uint32_t tag, const bool enable_tracing, const unsigned kver) {
+        const struct __sk_buff* const skb, const struct egress_bool egress, const uint32_t uid,
+        const uint32_t tag, const bool enable_tracing, const struct kver_uint kver) {
     if (!enable_tracing) return;
-    if (kver < KVER(5, 8, 0)) return;
+    if (!KVER_IS_AT_LEAST(kver, 5, 8, 0)) return;
 
     uint32_t mapKey = 0;
     bool* traceConfig = bpf_packet_trace_enabled_map_lookup_elem(&mapKey);
@@ -317,8 +317,8 @@
     pkt->sport = sport;
     pkt->dport = dport;
 
-    pkt->egress = egress;
-    pkt->wakeup = !egress && (skb->mark & 0x80000000);  // Fwmark.ingress_cpu_wakeup
+    pkt->egress = egress.egress;
+    pkt->wakeup = !egress.egress && (skb->mark & 0x80000000);  // Fwmark.ingress_cpu_wakeup
     pkt->ipProto = proto;
     pkt->tcpFlags = flags;
     pkt->ipVersion = ipVersion;
@@ -326,8 +326,9 @@
     bpf_packet_trace_ringbuf_submit(pkt);
 }
 
-static __always_inline inline bool skip_owner_match(struct __sk_buff* skb, bool egress,
-                                                    const unsigned kver) {
+static __always_inline inline bool skip_owner_match(struct __sk_buff* skb,
+                                                    const struct egress_bool egress,
+                                                    const struct kver_uint kver) {
     uint32_t flag = 0;
     if (skb->protocol == htons(ETH_P_IP)) {
         uint8_t proto;
@@ -358,7 +359,7 @@
         return false;
     }
     // Always allow RST's, and additionally allow ingress FINs
-    return flag & (TCP_FLAG_RST | (egress ? 0 : TCP_FLAG_FIN));  // false on read failure
+    return flag & (TCP_FLAG_RST | (egress.egress ? 0 : TCP_FLAG_FIN));  // false on read failure
 }
 
 static __always_inline inline BpfConfig getConfig(uint32_t configKey) {
@@ -372,11 +373,11 @@
 }
 
 static __always_inline inline bool ingress_should_discard(struct __sk_buff* skb,
-                                                          const unsigned kver) {
+                                                          const struct kver_uint kver) {
     // Require 4.19, since earlier kernels don't have bpf_skb_load_bytes_relative() which
     // provides relative to L3 header reads.  Without that we could fetch the wrong bytes.
     // Additionally earlier bpf verifiers are much harder to please.
-    if (kver < KVER(4, 19, 0)) return false;
+    if (!KVER_IS_AT_LEAST(kver, 4, 19, 0)) return false;
 
     IngressDiscardKey k = {};
     if (skb->protocol == htons(ETH_P_IP)) {
@@ -401,7 +402,8 @@
 }
 
 static __always_inline inline int bpf_owner_match(struct __sk_buff* skb, uint32_t uid,
-                                                  bool egress, const unsigned kver) {
+                                                  const struct egress_bool egress,
+                                                  const struct kver_uint kver) {
     if (is_system_uid(uid)) return PASS;
 
     if (skip_owner_match(skb, egress, kver)) return PASS;
@@ -414,7 +416,7 @@
 
     if (isBlockedByUidRules(enabledRules, uidRules)) return DROP;
 
-    if (!egress && skb->ifindex != 1) {
+    if (!egress.egress && skb->ifindex != 1) {
         if (ingress_should_discard(skb, kver)) return DROP;
         if (uidRules & IIF_MATCH) {
             if (allowed_iif && skb->ifindex != allowed_iif) {
@@ -434,8 +436,8 @@
 static __always_inline inline void update_stats_with_config(const uint32_t selectedMap,
                                                             const struct __sk_buff* const skb,
                                                             const StatsKey* const key,
-                                                            const bool egress,
-                                                            const unsigned kver) {
+                                                            const struct egress_bool egress,
+                                                            const struct kver_uint kver) {
     if (selectedMap == SELECT_MAP_A) {
         update_stats_map_A(skb, key, egress, kver);
     } else {
@@ -443,9 +445,10 @@
     }
 }
 
-static __always_inline inline int bpf_traffic_account(struct __sk_buff* skb, bool egress,
+static __always_inline inline int bpf_traffic_account(struct __sk_buff* skb,
+                                                      const struct egress_bool egress,
                                                       const bool enable_tracing,
-                                                      const unsigned kver) {
+                                                      const struct kver_uint kver) {
     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);
@@ -462,7 +465,7 @@
     // interface is accounted for and subject to usage restrictions.
     // CLAT IPv6 TX sockets are *always* tagged with CLAT uid, see tagSocketAsClat()
     // CLAT daemon receives via an untagged AF_PACKET socket.
-    if (egress && uid == AID_CLAT) return PASS;
+    if (egress.egress && uid == AID_CLAT) return PASS;
 
     int match = bpf_owner_match(skb, sock_uid, egress, kver);
 
@@ -478,7 +481,7 @@
     }
 
     // If an outbound packet is going to be dropped, we do not count that traffic.
-    if (egress && (match == DROP)) return DROP;
+    if (egress.egress && (match == DROP)) return DROP;
 
     StatsKey key = {.uid = uid, .tag = tag, .counterSet = 0, .ifaceIndex = skb->ifindex};
 
@@ -505,64 +508,64 @@
 
 // This program is optional, and enables tracing on Android U+, 5.8+ on user builds.
 DEFINE_BPF_PROG_EXT("cgroupskb/ingress/stats$trace_user", AID_ROOT, AID_SYSTEM,
-                    bpf_cgroup_ingress_trace_user, KVER(5, 8, 0), KVER_INF,
+                    bpf_cgroup_ingress_trace_user, KVER_5_8, KVER_INF,
                     BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
                     "fs_bpf_netd_readonly", "",
                     IGNORE_ON_ENG, LOAD_ON_USER, IGNORE_ON_USERDEBUG)
 (struct __sk_buff* skb) {
-    return bpf_traffic_account(skb, INGRESS, TRACE_ON, KVER(5, 8, 0));
+    return bpf_traffic_account(skb, INGRESS, TRACE_ON, KVER_5_8);
 }
 
 // This program is required, and enables tracing on Android U+, 5.8+, userdebug/eng.
 DEFINE_BPF_PROG_EXT("cgroupskb/ingress/stats$trace", AID_ROOT, AID_SYSTEM,
-                    bpf_cgroup_ingress_trace, KVER(5, 8, 0), KVER_INF,
+                    bpf_cgroup_ingress_trace, KVER_5_8, KVER_INF,
                     BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, MANDATORY,
                     "fs_bpf_netd_readonly", "",
                     LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG)
 (struct __sk_buff* skb) {
-    return bpf_traffic_account(skb, INGRESS, TRACE_ON, KVER(5, 8, 0));
+    return bpf_traffic_account(skb, INGRESS, TRACE_ON, KVER_5_8);
 }
 
 DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/ingress/stats$4_19", AID_ROOT, AID_SYSTEM,
-                                bpf_cgroup_ingress_4_19, KVER(4, 19, 0), KVER_INF)
+                                bpf_cgroup_ingress_4_19, KVER_4_19, KVER_INF)
 (struct __sk_buff* skb) {
-    return bpf_traffic_account(skb, INGRESS, TRACE_OFF, KVER(4, 19, 0));
+    return bpf_traffic_account(skb, INGRESS, TRACE_OFF, KVER_4_19);
 }
 
 DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/ingress/stats$4_14", AID_ROOT, AID_SYSTEM,
-                                bpf_cgroup_ingress_4_14, KVER_NONE, KVER(4, 19, 0))
+                                bpf_cgroup_ingress_4_14, KVER_NONE, KVER_4_19)
 (struct __sk_buff* skb) {
     return bpf_traffic_account(skb, INGRESS, TRACE_OFF, KVER_NONE);
 }
 
 // This program is optional, and enables tracing on Android U+, 5.8+ on user builds.
 DEFINE_BPF_PROG_EXT("cgroupskb/egress/stats$trace_user", AID_ROOT, AID_SYSTEM,
-                    bpf_cgroup_egress_trace_user, KVER(5, 8, 0), KVER_INF,
+                    bpf_cgroup_egress_trace_user, KVER_5_8, KVER_INF,
                     BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
                     "fs_bpf_netd_readonly", "",
                     LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG)
 (struct __sk_buff* skb) {
-    return bpf_traffic_account(skb, EGRESS, TRACE_ON, KVER(5, 8, 0));
+    return bpf_traffic_account(skb, EGRESS, TRACE_ON, KVER_5_8);
 }
 
 // This program is required, and enables tracing on Android U+, 5.8+, userdebug/eng.
 DEFINE_BPF_PROG_EXT("cgroupskb/egress/stats$trace", AID_ROOT, AID_SYSTEM,
-                    bpf_cgroup_egress_trace, KVER(5, 8, 0), KVER_INF,
+                    bpf_cgroup_egress_trace, KVER_5_8, KVER_INF,
                     BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, MANDATORY,
                     "fs_bpf_netd_readonly", "",
                     LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG)
 (struct __sk_buff* skb) {
-    return bpf_traffic_account(skb, EGRESS, TRACE_ON, KVER(5, 8, 0));
+    return bpf_traffic_account(skb, EGRESS, TRACE_ON, KVER_5_8);
 }
 
 DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/egress/stats$4_19", AID_ROOT, AID_SYSTEM,
-                                bpf_cgroup_egress_4_19, KVER(4, 19, 0), KVER_INF)
+                                bpf_cgroup_egress_4_19, KVER_4_19, KVER_INF)
 (struct __sk_buff* skb) {
-    return bpf_traffic_account(skb, EGRESS, TRACE_OFF, KVER(4, 19, 0));
+    return bpf_traffic_account(skb, EGRESS, TRACE_OFF, KVER_4_19);
 }
 
 DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/egress/stats$4_14", AID_ROOT, AID_SYSTEM,
-                                bpf_cgroup_egress_4_14, KVER_NONE, KVER(4, 19, 0))
+                                bpf_cgroup_egress_4_14, KVER_NONE, KVER_4_19)
 (struct __sk_buff* skb) {
     return bpf_traffic_account(skb, EGRESS, TRACE_OFF, KVER_NONE);
 }
@@ -637,9 +640,7 @@
     return BPF_NOMATCH;
 }
 
-DEFINE_NETD_BPF_PROG_KVER("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create,
-                          KVER(4, 14, 0))
-(struct bpf_sock* sk) {
+static __always_inline inline uint8_t get_app_permissions() {
     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
@@ -649,13 +650,15 @@
      */
     uint32_t appId = (gid_uid & 0xffffffff) % AID_USER_OFFSET;  // == PER_USER_RANGE == 100000
     uint8_t* permissions = bpf_uid_permission_map_lookup_elem(&appId);
-    if (!permissions) {
-        // UID not in map. Default to just INTERNET permission.
-        return 1;
-    }
+    // if UID not in map, then default to just INTERNET permission.
+    return permissions ? *permissions : BPF_PERMISSION_INTERNET;
+}
 
+DEFINE_NETD_BPF_PROG_KVER("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create,
+                          KVER_4_14)
+(struct bpf_sock* sk) {
     // A return value of 1 means allow, everything else means deny.
-    return (*permissions & BPF_PERMISSION_INTERNET) == BPF_PERMISSION_INTERNET;
+    return (get_app_permissions() & BPF_PERMISSION_INTERNET) ? 1 : 0;
 }
 
 LICENSE("Apache 2.0");
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index c752779..35b8eea 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -124,8 +124,12 @@
 DEFINE_BPF_MAP_GRW(tether_upstream6_map, HASH, TetherUpstream6Key, Tether6Value, 64,
                    TETHERING_GID)
 
-static inline __always_inline int do_forward6(struct __sk_buff* skb, const bool is_ethernet,
-        const bool downstream, const unsigned kver) {
+static inline __always_inline int do_forward6(struct __sk_buff* skb,
+                                              const struct rawip_bool rawip,
+                                              const struct stream_bool stream,
+                                              const struct kver_uint kver) {
+    const bool is_ethernet = !rawip.rawip;
+
     // Must be meta-ethernet IPv6 frame
     if (skb->protocol != htons(ETH_P_IPV6)) return TC_ACT_PIPE;
 
@@ -184,7 +188,7 @@
         TC_PUNT(NON_GLOBAL_DST);
 
     // In the upstream direction do not forward traffic within the same /64 subnet.
-    if (!downstream && (src32 == dst32) && (ip6->saddr.s6_addr32[1] == ip6->daddr.s6_addr32[1]))
+    if (!stream.down && (src32 == dst32) && (ip6->saddr.s6_addr32[1] == ip6->daddr.s6_addr32[1]))
         TC_PUNT(LOCAL_SRC_DST);
 
     TetherDownstream6Key kd = {
@@ -196,15 +200,15 @@
             .iif = skb->ifindex,
             .src64 = 0,
     };
-    if (is_ethernet) __builtin_memcpy(downstream ? kd.dstMac : ku.dstMac, eth->h_dest, ETH_ALEN);
+    if (is_ethernet) __builtin_memcpy(stream.down ? kd.dstMac : ku.dstMac, eth->h_dest, ETH_ALEN);
 
-    Tether6Value* v = downstream ? bpf_tether_downstream6_map_lookup_elem(&kd)
-                                 : bpf_tether_upstream6_map_lookup_elem(&ku);
+    Tether6Value* v = stream.down ? bpf_tether_downstream6_map_lookup_elem(&kd)
+                                  : bpf_tether_upstream6_map_lookup_elem(&ku);
 
     // If we don't find any offload information then simply let the core stack handle it...
     if (!v) return TC_ACT_PIPE;
 
-    uint32_t stat_and_limit_k = downstream ? skb->ifindex : v->oif;
+    uint32_t stat_and_limit_k = stream.down ? skb->ifindex : v->oif;
 
     TetherStatsValue* stat_v = bpf_tether_stats_map_lookup_elem(&stat_and_limit_k);
 
@@ -249,7 +253,7 @@
         // We do this even if TX interface is RAWIP and thus does not need an ethernet header,
         // because this is easier and the kernel will strip extraneous ethernet header.
         if (bpf_skb_change_head(skb, sizeof(struct ethhdr), /*flags*/ 0)) {
-            __sync_fetch_and_add(downstream ? &stat_v->rxErrors : &stat_v->txErrors, 1);
+            __sync_fetch_and_add(stream.down ? &stat_v->rxErrors : &stat_v->txErrors, 1);
             TC_PUNT(CHANGE_HEAD_FAILED);
         }
 
@@ -261,7 +265,7 @@
 
         // I do not believe this can ever happen, but keep the verifier happy...
         if (data + sizeof(struct ethhdr) + sizeof(*ip6) > data_end) {
-            __sync_fetch_and_add(downstream ? &stat_v->rxErrors : &stat_v->txErrors, 1);
+            __sync_fetch_and_add(stream.down ? &stat_v->rxErrors : &stat_v->txErrors, 1);
             TC_DROP(TOO_SHORT);
         }
     };
@@ -281,8 +285,8 @@
     // (-ENOTSUPP) if it isn't.
     bpf_csum_update(skb, 0xFFFF - ntohs(old_hl) + ntohs(new_hl));
 
-    __sync_fetch_and_add(downstream ? &stat_v->rxPackets : &stat_v->txPackets, packets);
-    __sync_fetch_and_add(downstream ? &stat_v->rxBytes : &stat_v->txBytes, L3_bytes);
+    __sync_fetch_and_add(stream.down ? &stat_v->rxPackets : &stat_v->txPackets, packets);
+    __sync_fetch_and_add(stream.down ? &stat_v->rxBytes : &stat_v->txBytes, L3_bytes);
 
     // Overwrite any mac header with the new one
     // For a rawip tx interface it will simply be a bunch of zeroes and later stripped.
@@ -324,26 +328,26 @@
 //
 // Hence, these mandatory (must load successfully) implementations for 4.14+ kernels:
 DEFINE_BPF_PROG_KVER("schedcls/tether_downstream6_rawip$4_14", TETHERING_UID, TETHERING_GID,
-                     sched_cls_tether_downstream6_rawip_4_14, KVER(4, 14, 0))
+                     sched_cls_tether_downstream6_rawip_4_14, KVER_4_14)
 (struct __sk_buff* skb) {
-    return do_forward6(skb, RAWIP, DOWNSTREAM, KVER(4, 14, 0));
+    return do_forward6(skb, RAWIP, DOWNSTREAM, KVER_4_14);
 }
 
 DEFINE_BPF_PROG_KVER("schedcls/tether_upstream6_rawip$4_14", TETHERING_UID, TETHERING_GID,
-                     sched_cls_tether_upstream6_rawip_4_14, KVER(4, 14, 0))
+                     sched_cls_tether_upstream6_rawip_4_14, KVER_4_14)
 (struct __sk_buff* skb) {
-    return do_forward6(skb, RAWIP, UPSTREAM, KVER(4, 14, 0));
+    return do_forward6(skb, RAWIP, UPSTREAM, KVER_4_14);
 }
 
 // and define no-op stubs for pre-4.14 kernels.
 DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream6_rawip$stub", TETHERING_UID, TETHERING_GID,
-                           sched_cls_tether_downstream6_rawip_stub, KVER_NONE, KVER(4, 14, 0))
+                           sched_cls_tether_downstream6_rawip_stub, KVER_NONE, KVER_4_14)
 (struct __sk_buff* skb) {
     return TC_ACT_PIPE;
 }
 
 DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream6_rawip$stub", TETHERING_UID, TETHERING_GID,
-                           sched_cls_tether_upstream6_rawip_stub, KVER_NONE, KVER(4, 14, 0))
+                           sched_cls_tether_upstream6_rawip_stub, KVER_NONE, KVER_4_14)
 (struct __sk_buff* skb) {
     return TC_ACT_PIPE;
 }
@@ -356,9 +360,10 @@
 
 static inline __always_inline int do_forward4_bottom(struct __sk_buff* skb,
         const int l2_header_size, void* data, const void* data_end,
-        struct ethhdr* eth, struct iphdr* ip, const bool is_ethernet,
-        const bool downstream, const bool updatetime, const bool is_tcp,
-        const unsigned kver) {
+        struct ethhdr* eth, struct iphdr* ip, const struct rawip_bool rawip,
+        const struct stream_bool stream, const struct updatetime_bool updatetime,
+        const bool is_tcp, const struct kver_uint kver) {
+    const bool is_ethernet = !rawip.rawip;
     struct tcphdr* tcph = is_tcp ? (void*)(ip + 1) : NULL;
     struct udphdr* udph = is_tcp ? NULL : (void*)(ip + 1);
 
@@ -416,13 +421,13 @@
     };
     if (is_ethernet) __builtin_memcpy(k.dstMac, eth->h_dest, ETH_ALEN);
 
-    Tether4Value* v = downstream ? bpf_tether_downstream4_map_lookup_elem(&k)
-                                 : bpf_tether_upstream4_map_lookup_elem(&k);
+    Tether4Value* v = stream.down ? bpf_tether_downstream4_map_lookup_elem(&k)
+                                  : bpf_tether_upstream4_map_lookup_elem(&k);
 
     // If we don't find any offload information then simply let the core stack handle it...
     if (!v) return TC_ACT_PIPE;
 
-    uint32_t stat_and_limit_k = downstream ? skb->ifindex : v->oif;
+    uint32_t stat_and_limit_k = stream.down ? skb->ifindex : v->oif;
 
     TetherStatsValue* stat_v = bpf_tether_stats_map_lookup_elem(&stat_and_limit_k);
 
@@ -467,7 +472,7 @@
         // We do this even if TX interface is RAWIP and thus does not need an ethernet header,
         // because this is easier and the kernel will strip extraneous ethernet header.
         if (bpf_skb_change_head(skb, sizeof(struct ethhdr), /*flags*/ 0)) {
-            __sync_fetch_and_add(downstream ? &stat_v->rxErrors : &stat_v->txErrors, 1);
+            __sync_fetch_and_add(stream.down ? &stat_v->rxErrors : &stat_v->txErrors, 1);
             TC_PUNT(CHANGE_HEAD_FAILED);
         }
 
@@ -481,7 +486,7 @@
 
         // I do not believe this can ever happen, but keep the verifier happy...
         if (data + sizeof(struct ethhdr) + sizeof(*ip) + (is_tcp ? sizeof(*tcph) : sizeof(*udph)) > data_end) {
-            __sync_fetch_and_add(downstream ? &stat_v->rxErrors : &stat_v->txErrors, 1);
+            __sync_fetch_and_add(stream.down ? &stat_v->rxErrors : &stat_v->txErrors, 1);
             TC_DROP(TOO_SHORT);
         }
     };
@@ -533,10 +538,10 @@
 
     // This requires the bpf_ktime_get_boot_ns() helper which was added in 5.8,
     // and backported to all Android Common Kernel 4.14+ trees.
-    if (updatetime) v->last_used = bpf_ktime_get_boot_ns();
+    if (updatetime.updatetime) v->last_used = bpf_ktime_get_boot_ns();
 
-    __sync_fetch_and_add(downstream ? &stat_v->rxPackets : &stat_v->txPackets, packets);
-    __sync_fetch_and_add(downstream ? &stat_v->rxBytes : &stat_v->txBytes, L3_bytes);
+    __sync_fetch_and_add(stream.down ? &stat_v->rxPackets : &stat_v->txPackets, packets);
+    __sync_fetch_and_add(stream.down ? &stat_v->rxBytes : &stat_v->txBytes, L3_bytes);
 
     // Redirect to forwarded interface.
     //
@@ -547,8 +552,13 @@
     return bpf_redirect(v->oif, 0 /* this is effectively BPF_F_EGRESS */);
 }
 
-static inline __always_inline int do_forward4(struct __sk_buff* skb, const bool is_ethernet,
-        const bool downstream, const bool updatetime, const unsigned kver) {
+static inline __always_inline int do_forward4(struct __sk_buff* skb,
+                                              const struct rawip_bool rawip,
+                                              const struct stream_bool stream,
+                                              const struct updatetime_bool updatetime,
+                                              const struct kver_uint kver) {
+    const bool is_ethernet = !rawip.rawip;
+
     // Require ethernet dst mac address to be our unicast address.
     if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_PIPE;
 
@@ -606,16 +616,16 @@
     // in such a situation we can only support TCP.  This also has the added nice benefit of
     // using a separate error counter, and thus making it obvious which version of the program
     // is loaded.
-    if (!updatetime && ip->protocol != IPPROTO_TCP) TC_PUNT(NON_TCP);
+    if (!updatetime.updatetime && ip->protocol != IPPROTO_TCP) TC_PUNT(NON_TCP);
 
     // We do not support offloading anything besides IPv4 TCP and UDP, due to need for NAT,
     // but no need to check this if !updatetime due to check immediately above.
-    if (updatetime && (ip->protocol != IPPROTO_TCP) && (ip->protocol != IPPROTO_UDP))
+    if (updatetime.updatetime && (ip->protocol != IPPROTO_TCP) && (ip->protocol != IPPROTO_UDP))
         TC_PUNT(NON_TCP_UDP);
 
     // We want to make sure that the compiler will, in the !updatetime case, entirely optimize
     // out all the non-tcp logic.  Also note that at this point is_udp === !is_tcp.
-    const bool is_tcp = !updatetime || (ip->protocol == IPPROTO_TCP);
+    const bool is_tcp = !updatetime.updatetime || (ip->protocol == IPPROTO_TCP);
 
     // This is a bit of a hack to make things easier on the bpf verifier.
     // (In particular I believe the Linux 4.14 kernel's verifier can get confused later on about
@@ -636,37 +646,37 @@
     // if the underlying requisite kernel support (bpf_ktime_get_boot_ns) was backported.
     if (is_tcp) {
       return do_forward4_bottom(skb, l2_header_size, data, data_end, eth, ip,
-                                is_ethernet, downstream, updatetime, /* is_tcp */ true, kver);
+                                rawip, stream, updatetime, /* is_tcp */ true, kver);
     } else {
       return do_forward4_bottom(skb, l2_header_size, data, data_end, eth, ip,
-                                is_ethernet, downstream, updatetime, /* is_tcp */ false, kver);
+                                rawip, stream, updatetime, /* is_tcp */ false, kver);
     }
 }
 
 // Full featured (required) implementations for 5.8+ kernels (these are S+ by definition)
 
 DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_rawip$5_8", TETHERING_UID, TETHERING_GID,
-                     sched_cls_tether_downstream4_rawip_5_8, KVER(5, 8, 0))
+                     sched_cls_tether_downstream4_rawip_5_8, KVER_5_8)
 (struct __sk_buff* skb) {
-    return do_forward4(skb, RAWIP, DOWNSTREAM, UPDATETIME, KVER(5, 8, 0));
+    return do_forward4(skb, RAWIP, DOWNSTREAM, UPDATETIME, KVER_5_8);
 }
 
 DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_rawip$5_8", TETHERING_UID, TETHERING_GID,
-                     sched_cls_tether_upstream4_rawip_5_8, KVER(5, 8, 0))
+                     sched_cls_tether_upstream4_rawip_5_8, KVER_5_8)
 (struct __sk_buff* skb) {
-    return do_forward4(skb, RAWIP, UPSTREAM, UPDATETIME, KVER(5, 8, 0));
+    return do_forward4(skb, RAWIP, UPSTREAM, UPDATETIME, KVER_5_8);
 }
 
 DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_ether$5_8", TETHERING_UID, TETHERING_GID,
-                     sched_cls_tether_downstream4_ether_5_8, KVER(5, 8, 0))
+                     sched_cls_tether_downstream4_ether_5_8, KVER_5_8)
 (struct __sk_buff* skb) {
-    return do_forward4(skb, ETHER, DOWNSTREAM, UPDATETIME, KVER(5, 8, 0));
+    return do_forward4(skb, ETHER, DOWNSTREAM, UPDATETIME, KVER_5_8);
 }
 
 DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_ether$5_8", TETHERING_UID, TETHERING_GID,
-                     sched_cls_tether_upstream4_ether_5_8, KVER(5, 8, 0))
+                     sched_cls_tether_upstream4_ether_5_8, KVER_5_8)
 (struct __sk_buff* skb) {
-    return do_forward4(skb, ETHER, UPSTREAM, UPDATETIME, KVER(5, 8, 0));
+    return do_forward4(skb, ETHER, UPSTREAM, UPDATETIME, KVER_5_8);
 }
 
 // Full featured (optional) implementations for 4.14-S, 4.19-S & 5.4-S kernels
@@ -675,33 +685,33 @@
 DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$opt",
                                     TETHERING_UID, TETHERING_GID,
                                     sched_cls_tether_downstream4_rawip_opt,
-                                    KVER(4, 14, 0), KVER(5, 8, 0))
+                                    KVER_4_14, KVER_5_8)
 (struct __sk_buff* skb) {
-    return do_forward4(skb, RAWIP, DOWNSTREAM, UPDATETIME, KVER(4, 14, 0));
+    return do_forward4(skb, RAWIP, DOWNSTREAM, UPDATETIME, KVER_4_14);
 }
 
 DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$opt",
                                     TETHERING_UID, TETHERING_GID,
                                     sched_cls_tether_upstream4_rawip_opt,
-                                    KVER(4, 14, 0), KVER(5, 8, 0))
+                                    KVER_4_14, KVER_5_8)
 (struct __sk_buff* skb) {
-    return do_forward4(skb, RAWIP, UPSTREAM, UPDATETIME, KVER(4, 14, 0));
+    return do_forward4(skb, RAWIP, UPSTREAM, UPDATETIME, KVER_4_14);
 }
 
 DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$opt",
                                     TETHERING_UID, TETHERING_GID,
                                     sched_cls_tether_downstream4_ether_opt,
-                                    KVER(4, 14, 0), KVER(5, 8, 0))
+                                    KVER_4_14, KVER_5_8)
 (struct __sk_buff* skb) {
-    return do_forward4(skb, ETHER, DOWNSTREAM, UPDATETIME, KVER(4, 14, 0));
+    return do_forward4(skb, ETHER, DOWNSTREAM, UPDATETIME, KVER_4_14);
 }
 
 DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$opt",
                                     TETHERING_UID, TETHERING_GID,
                                     sched_cls_tether_upstream4_ether_opt,
-                                    KVER(4, 14, 0), KVER(5, 8, 0))
+                                    KVER_4_14, KVER_5_8)
 (struct __sk_buff* skb) {
-    return do_forward4(skb, ETHER, UPSTREAM, UPDATETIME, KVER(4, 14, 0));
+    return do_forward4(skb, ETHER, UPSTREAM, UPDATETIME, KVER_4_14);
 }
 
 // Partial (TCP-only: will not update 'last_used' field) implementations for 4.14+ kernels.
@@ -719,15 +729,15 @@
 // RAWIP: Required for 5.4-R kernels -- which always support bpf_skb_change_head().
 
 DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$5_4", TETHERING_UID, TETHERING_GID,
-                           sched_cls_tether_downstream4_rawip_5_4, KVER(5, 4, 0), KVER(5, 8, 0))
+                           sched_cls_tether_downstream4_rawip_5_4, KVER_5_4, KVER_5_8)
 (struct __sk_buff* skb) {
-    return do_forward4(skb, RAWIP, DOWNSTREAM, NO_UPDATETIME, KVER(5, 4, 0));
+    return do_forward4(skb, RAWIP, DOWNSTREAM, NO_UPDATETIME, KVER_5_4);
 }
 
 DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$5_4", TETHERING_UID, TETHERING_GID,
-                           sched_cls_tether_upstream4_rawip_5_4, KVER(5, 4, 0), KVER(5, 8, 0))
+                           sched_cls_tether_upstream4_rawip_5_4, KVER_5_4, KVER_5_8)
 (struct __sk_buff* skb) {
-    return do_forward4(skb, RAWIP, UPSTREAM, NO_UPDATETIME, KVER(5, 4, 0));
+    return do_forward4(skb, RAWIP, UPSTREAM, NO_UPDATETIME, KVER_5_4);
 }
 
 // RAWIP: Optional for 4.14/4.19 (R) kernels -- which support bpf_skb_change_head().
@@ -736,31 +746,31 @@
 DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$4_14",
                                     TETHERING_UID, TETHERING_GID,
                                     sched_cls_tether_downstream4_rawip_4_14,
-                                    KVER(4, 14, 0), KVER(5, 4, 0))
+                                    KVER_4_14, KVER_5_4)
 (struct __sk_buff* skb) {
-    return do_forward4(skb, RAWIP, DOWNSTREAM, NO_UPDATETIME, KVER(4, 14, 0));
+    return do_forward4(skb, RAWIP, DOWNSTREAM, NO_UPDATETIME, KVER_4_14);
 }
 
 DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$4_14",
                                     TETHERING_UID, TETHERING_GID,
                                     sched_cls_tether_upstream4_rawip_4_14,
-                                    KVER(4, 14, 0), KVER(5, 4, 0))
+                                    KVER_4_14, KVER_5_4)
 (struct __sk_buff* skb) {
-    return do_forward4(skb, RAWIP, UPSTREAM, NO_UPDATETIME, KVER(4, 14, 0));
+    return do_forward4(skb, RAWIP, UPSTREAM, NO_UPDATETIME, KVER_4_14);
 }
 
 // ETHER: Required for 4.14-Q/R, 4.19-Q/R & 5.4-R kernels.
 
 DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$4_14", TETHERING_UID, TETHERING_GID,
-                           sched_cls_tether_downstream4_ether_4_14, KVER(4, 14, 0), KVER(5, 8, 0))
+                           sched_cls_tether_downstream4_ether_4_14, KVER_4_14, KVER_5_8)
 (struct __sk_buff* skb) {
-    return do_forward4(skb, ETHER, DOWNSTREAM, NO_UPDATETIME, KVER(4, 14, 0));
+    return do_forward4(skb, ETHER, DOWNSTREAM, NO_UPDATETIME, KVER_4_14);
 }
 
 DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$4_14", TETHERING_UID, TETHERING_GID,
-                           sched_cls_tether_upstream4_ether_4_14, KVER(4, 14, 0), KVER(5, 8, 0))
+                           sched_cls_tether_upstream4_ether_4_14, KVER_4_14, KVER_5_8)
 (struct __sk_buff* skb) {
-    return do_forward4(skb, ETHER, UPSTREAM, NO_UPDATETIME, KVER(4, 14, 0));
+    return do_forward4(skb, ETHER, UPSTREAM, NO_UPDATETIME, KVER_4_14);
 }
 
 // Placeholder (no-op) implementations for older Q kernels
@@ -768,13 +778,13 @@
 // RAWIP: 4.9-P/Q, 4.14-P/Q & 4.19-Q kernels -- without bpf_skb_change_head() for tc programs
 
 DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$stub", TETHERING_UID, TETHERING_GID,
-                           sched_cls_tether_downstream4_rawip_stub, KVER_NONE, KVER(5, 4, 0))
+                           sched_cls_tether_downstream4_rawip_stub, KVER_NONE, KVER_5_4)
 (struct __sk_buff* skb) {
     return TC_ACT_PIPE;
 }
 
 DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$stub", TETHERING_UID, TETHERING_GID,
-                           sched_cls_tether_upstream4_rawip_stub, KVER_NONE, KVER(5, 4, 0))
+                           sched_cls_tether_upstream4_rawip_stub, KVER_NONE, KVER_5_4)
 (struct __sk_buff* skb) {
     return TC_ACT_PIPE;
 }
@@ -782,13 +792,13 @@
 // ETHER: 4.9-P/Q kernel
 
 DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$stub", TETHERING_UID, TETHERING_GID,
-                           sched_cls_tether_downstream4_ether_stub, KVER_NONE, KVER(4, 14, 0))
+                           sched_cls_tether_downstream4_ether_stub, KVER_NONE, KVER_4_14)
 (struct __sk_buff* skb) {
     return TC_ACT_PIPE;
 }
 
 DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$stub", TETHERING_UID, TETHERING_GID,
-                           sched_cls_tether_upstream4_ether_stub, KVER_NONE, KVER(4, 14, 0))
+                           sched_cls_tether_upstream4_ether_stub, KVER_NONE, KVER_4_14)
 (struct __sk_buff* skb) {
     return TC_ACT_PIPE;
 }
@@ -797,17 +807,18 @@
 
 DEFINE_BPF_MAP_GRW(tether_dev_map, DEVMAP_HASH, uint32_t, uint32_t, 64, TETHERING_GID)
 
-static inline __always_inline int do_xdp_forward6(struct xdp_md *ctx, const bool is_ethernet,
-        const bool downstream) {
+static inline __always_inline int do_xdp_forward6(struct xdp_md *ctx, const struct rawip_bool rawip,
+        const struct stream_bool stream) {
     return XDP_PASS;
 }
 
-static inline __always_inline int do_xdp_forward4(struct xdp_md *ctx, const bool is_ethernet,
-        const bool downstream) {
+static inline __always_inline int do_xdp_forward4(struct xdp_md *ctx, const struct rawip_bool rawip,
+        const struct stream_bool stream) {
     return XDP_PASS;
 }
 
-static inline __always_inline int do_xdp_forward_ether(struct xdp_md *ctx, const bool downstream) {
+static inline __always_inline int do_xdp_forward_ether(struct xdp_md *ctx,
+                                                       const struct stream_bool stream) {
     const void* data = (void*)(long)ctx->data;
     const void* data_end = (void*)(long)ctx->data_end;
     const struct ethhdr* eth = data;
@@ -816,15 +827,16 @@
     if ((void*)(eth + 1) > data_end) return XDP_PASS;
 
     if (eth->h_proto == htons(ETH_P_IPV6))
-        return do_xdp_forward6(ctx, ETHER, downstream);
+        return do_xdp_forward6(ctx, ETHER, stream);
     if (eth->h_proto == htons(ETH_P_IP))
-        return do_xdp_forward4(ctx, ETHER, downstream);
+        return do_xdp_forward4(ctx, ETHER, stream);
 
     // Anything else we don't know how to handle...
     return XDP_PASS;
 }
 
-static inline __always_inline int do_xdp_forward_rawip(struct xdp_md *ctx, const bool downstream) {
+static inline __always_inline int do_xdp_forward_rawip(struct xdp_md *ctx,
+                                                       const struct stream_bool stream) {
     const void* data = (void*)(long)ctx->data;
     const void* data_end = (void*)(long)ctx->data_end;
 
@@ -832,15 +844,15 @@
     if (data_end - data < 1) return XDP_PASS;
     const uint8_t v = (*(uint8_t*)data) >> 4;
 
-    if (v == 6) return do_xdp_forward6(ctx, RAWIP, downstream);
-    if (v == 4) return do_xdp_forward4(ctx, RAWIP, downstream);
+    if (v == 6) return do_xdp_forward6(ctx, RAWIP, stream);
+    if (v == 4) return do_xdp_forward4(ctx, RAWIP, stream);
 
     // Anything else we don't know how to handle...
     return XDP_PASS;
 }
 
 #define DEFINE_XDP_PROG(str, func) \
-    DEFINE_BPF_PROG_KVER(str, TETHERING_UID, TETHERING_GID, func, KVER(5, 9, 0))(struct xdp_md *ctx)
+    DEFINE_BPF_PROG_KVER(str, TETHERING_UID, TETHERING_GID, func, KVER_5_9)(struct xdp_md *ctx)
 
 DEFINE_XDP_PROG("xdp/tether_downstream_ether",
                  xdp_tether_downstream_ether) {
diff --git a/bpf_progs/test.c b/bpf_progs/test.c
index 68469c8..70b08b7 100644
--- a/bpf_progs/test.c
+++ b/bpf_progs/test.c
@@ -49,7 +49,7 @@
 DEFINE_BPF_MAP_GRW(bitmap, ARRAY, int, uint64_t, 2, TETHERING_GID)
 
 DEFINE_BPF_PROG_KVER("xdp/drop_ipv4_udp_ether", TETHERING_UID, TETHERING_GID,
-                      xdp_test, KVER(5, 9, 0))
+                      xdp_test, KVER_5_9)
 (struct xdp_md *ctx) {
     void *data = (void *)(long)ctx->data;
     void *data_end = (void *)(long)ctx->data_end;
diff --git a/common/TrunkStable.bp b/common/TrunkStable.bp
index 772f79e..56938fc 100644
--- a/common/TrunkStable.bp
+++ b/common/TrunkStable.bp
@@ -19,3 +19,8 @@
     package: "com.android.net.flags",
     srcs: ["flags.aconfig"],
 }
+
+java_aconfig_library {
+    name: "connectivity_flags_aconfig_lib",
+    aconfig_declarations: "com.android.net.flags-aconfig",
+}
diff --git a/common/flags.aconfig b/common/flags.aconfig
index 4926503..cadc44f 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -6,3 +6,10 @@
   description: "NetworkActivityTracker tracks multiple networks including non default networks"
   bug: "267870186"
 }
+
+flag {
+  name: "forbidden_capability"
+  namespace: "android_core_networking"
+  description: "This flag controls the forbidden capability API"
+  bug: "302997505"
+}
diff --git a/framework-t/api/current.txt b/framework-t/api/current.txt
index 86745d4..e4b211f 100644
--- a/framework-t/api/current.txt
+++ b/framework-t/api/current.txt
@@ -1,4 +1,6 @@
 // Signature format: 2.0
+// - add-additional-overrides=no
+// - migrating=Migration in progress see b/299366704
 package android.app.usage {
 
   public final class NetworkStats implements java.lang.AutoCloseable {
diff --git a/framework-t/api/module-lib-current.txt b/framework-t/api/module-lib-current.txt
index 5a8d47b..fd42a37 100644
--- a/framework-t/api/module-lib-current.txt
+++ b/framework-t/api/module-lib-current.txt
@@ -1,4 +1,6 @@
 // Signature format: 2.0
+// - add-additional-overrides=no
+// - migrating=Migration in progress see b/299366704
 package android.app.usage {
 
   public class NetworkStatsManager {
diff --git a/framework-t/api/module-lib-removed.txt b/framework-t/api/module-lib-removed.txt
index d802177..14191eb 100644
--- a/framework-t/api/module-lib-removed.txt
+++ b/framework-t/api/module-lib-removed.txt
@@ -1 +1,3 @@
 // Signature format: 2.0
+// - add-additional-overrides=no
+// - migrating=Migration in progress see b/299366704
diff --git a/framework-t/api/removed.txt b/framework-t/api/removed.txt
index 1ba87d8..d9d243d 100644
--- a/framework-t/api/removed.txt
+++ b/framework-t/api/removed.txt
@@ -1,4 +1,6 @@
 // Signature format: 2.0
+// - add-additional-overrides=no
+// - migrating=Migration in progress see b/299366704
 package android.net {
 
   public class TrafficStats {
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index 53ad834..9bdb595 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -1,4 +1,6 @@
 // Signature format: 2.0
+// - add-additional-overrides=no
+// - migrating=Migration in progress see b/299366704
 package android.app.usage {
 
   public class NetworkStatsManager {
diff --git a/framework-t/api/system-removed.txt b/framework-t/api/system-removed.txt
index d802177..14191eb 100644
--- a/framework-t/api/system-removed.txt
+++ b/framework-t/api/system-removed.txt
@@ -1 +1,3 @@
 // Signature format: 2.0
+// - add-additional-overrides=no
+// - migrating=Migration in progress see b/299366704
diff --git a/framework/api/current.txt b/framework/api/current.txt
index 6860c3c..4f00977 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -1,4 +1,6 @@
 // Signature format: 2.0
+// - add-additional-overrides=no
+// - migrating=Migration in progress see b/299366704
 package android.net {
 
   public class CaptivePortal implements android.os.Parcelable {
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index 193bd92..ac57c10 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -1,4 +1,6 @@
 // Signature format: 2.0
+// - add-additional-overrides=no
+// - migrating=Migration in progress see b/299366704
 package android.net {
 
   public final class ConnectivityFrameworkInitializer {
diff --git a/framework/api/module-lib-removed.txt b/framework/api/module-lib-removed.txt
index d802177..14191eb 100644
--- a/framework/api/module-lib-removed.txt
+++ b/framework/api/module-lib-removed.txt
@@ -1 +1,3 @@
 // Signature format: 2.0
+// - add-additional-overrides=no
+// - migrating=Migration in progress see b/299366704
diff --git a/framework/api/removed.txt b/framework/api/removed.txt
index 303a1e6..f5da46a 100644
--- a/framework/api/removed.txt
+++ b/framework/api/removed.txt
@@ -1,4 +1,6 @@
 // Signature format: 2.0
+// - add-additional-overrides=no
+// - migrating=Migration in progress see b/299366704
 package android.net {
 
   public class ConnectivityManager {
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index 4a2ed8a..cd120e9 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -1,4 +1,6 @@
 // Signature format: 2.0
+// - add-additional-overrides=no
+// - migrating=Migration in progress see b/299366704
 package android.net {
 
   public class CaptivePortal implements android.os.Parcelable {
diff --git a/framework/api/system-removed.txt b/framework/api/system-removed.txt
index d802177..14191eb 100644
--- a/framework/api/system-removed.txt
+++ b/framework/api/system-removed.txt
@@ -1 +1,3 @@
 // Signature format: 2.0
+// - add-additional-overrides=no
+// - migrating=Migration in progress see b/299366704
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index abda1fa..f959114 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -29,6 +29,9 @@
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.net.ConnectivityManager.NetworkCallback;
+// Can't be imported because aconfig tooling doesn't exist on udc-mainline-prod yet
+// See inner class Flags which mimics this for the time being
+// import android.net.flags.Flags;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -121,6 +124,14 @@
 public final class NetworkCapabilities implements Parcelable {
     private static final String TAG = "NetworkCapabilities";
 
+    // TODO : remove this class when udc-mainline-prod is abandoned and android.net.flags.Flags is
+    // available here
+    /** @hide */
+    public static class Flags {
+        static final String FLAG_FORBIDDEN_CAPABILITY =
+                "com.android.net.flags.forbidden_capability";
+    }
+
     /**
      * Mechanism to support redaction of fields in NetworkCapabilities that are guarded by specific
      * app permissions.
@@ -442,6 +453,7 @@
             NET_CAPABILITY_MMTEL,
             NET_CAPABILITY_PRIORITIZE_LATENCY,
             NET_CAPABILITY_PRIORITIZE_BANDWIDTH,
+            NET_CAPABILITY_LOCAL_NETWORK,
     })
     public @interface NetCapability { }
 
@@ -703,7 +715,21 @@
      */
     public static final int NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 35;
 
-    private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
+    /**
+     * This is a local network, e.g. a tethering downstream or a P2P direct network.
+     *
+     * <p>
+     * Note that local networks are not sent to callbacks by default. To receive callbacks about
+     * them, the {@link NetworkRequest} instance must be prepared to see them, either by
+     * adding the capability with {@link NetworkRequest.Builder#addCapability}, by removing
+     * this forbidden capability with {@link NetworkRequest.Builder#removeForbiddenCapability},
+     * or by clearing all capabilites with {@link NetworkRequest.Builder#clearCapabilities()}.
+     * </p>
+     * @hide
+     */
+    public static final int NET_CAPABILITY_LOCAL_NETWORK = 36;
+
+    private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_LOCAL_NETWORK;
 
     // Set all bits up to the MAX_NET_CAPABILITY-th bit
     private static final long ALL_VALID_CAPABILITIES = (2L << MAX_NET_CAPABILITY) - 1;
@@ -793,6 +819,10 @@
      * Adds the given capability to this {@code NetworkCapability} instance.
      * Note that when searching for a network to satisfy a request, all capabilities
      * requested must be satisfied.
+     * <p>
+     * If the capability was previously added to the list of forbidden capabilities (either
+     * by default or added using {@link #addForbiddenCapability(int)}), then it will be removed
+     * from the list of forbidden capabilities as well.
      *
      * @param capability the capability to be added.
      * @return This NetworkCapabilities instance, to facilitate chaining.
@@ -801,8 +831,7 @@
     public @NonNull NetworkCapabilities addCapability(@NetCapability int capability) {
         // If the given capability was previously added to the list of forbidden capabilities
         // then the capability will also be removed from the list of forbidden capabilities.
-        // TODO: Consider adding forbidden capabilities to the public API and mention this
-        // in the documentation.
+        // TODO: Add forbidden capabilities to the public API
         checkValidCapability(capability);
         mNetworkCapabilities |= 1L << capability;
         // remove from forbidden capability list
@@ -845,7 +874,7 @@
     }
 
     /**
-     * Removes (if found) the given forbidden capability from this {@code NetworkCapability}
+     * Removes (if found) the given forbidden capability from this {@link NetworkCapabilities}
      * instance that were added via addForbiddenCapability(int) or setCapabilities(int[], int[]).
      *
      * @param capability the capability to be removed.
@@ -859,6 +888,16 @@
     }
 
     /**
+     * Removes all forbidden capabilities from this {@link NetworkCapabilities} instance.
+     * @return This NetworkCapabilities instance, to facilitate chaining.
+     * @hide
+     */
+    public @NonNull NetworkCapabilities removeAllForbiddenCapabilities() {
+        mForbiddenNetworkCapabilities = 0;
+        return this;
+    }
+
+    /**
      * Sets (or clears) the given capability on this {@link NetworkCapabilities}
      * instance.
      * @hide
@@ -901,6 +940,8 @@
      * @return an array of forbidden capability values for this instance.
      * @hide
      */
+    @NonNull
+    // TODO : @FlaggedApi(Flags.FLAG_FORBIDDEN_CAPABILITY) and public
     public @NetCapability int[] getForbiddenCapabilities() {
         return BitUtils.unpackBits(mForbiddenNetworkCapabilities);
     }
@@ -1000,7 +1041,7 @@
     /**
      * Tests for the presence of a capability on this instance.
      *
-     * @param capability the capabilities to be tested for.
+     * @param capability the capability to be tested for.
      * @return {@code true} if set on this instance.
      */
     public boolean hasCapability(@NetCapability int capability) {
@@ -1008,19 +1049,27 @@
                 && ((mNetworkCapabilities & (1L << capability)) != 0);
     }
 
-    /** @hide */
+    /**
+     * Tests for the presence of a forbidden capability on this instance.
+     *
+     * @param capability the capability to be tested for.
+     * @return {@code true} if this capability is set forbidden on this instance.
+     * @hide
+     */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    // TODO : @FlaggedApi(Flags.FLAG_FORBIDDEN_CAPABILITY) and public
     public boolean hasForbiddenCapability(@NetCapability int capability) {
         return isValidCapability(capability)
                 && ((mForbiddenNetworkCapabilities & (1L << capability)) != 0);
     }
 
     /**
-     * Check if this NetworkCapabilities has system managed capabilities or not.
+     * Check if this NetworkCapabilities has connectivity-managed capabilities or not.
      * @hide
      */
     public boolean hasConnectivityManagedCapability() {
-        return ((mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0);
+        return (mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0
+                || mForbiddenNetworkCapabilities != 0;
     }
 
     /**
@@ -2500,6 +2549,7 @@
             case NET_CAPABILITY_MMTEL:                return "MMTEL";
             case NET_CAPABILITY_PRIORITIZE_LATENCY:          return "PRIORITIZE_LATENCY";
             case NET_CAPABILITY_PRIORITIZE_BANDWIDTH:        return "PRIORITIZE_BANDWIDTH";
+            case NET_CAPABILITY_LOCAL_NETWORK:        return "LOCAL_NETWORK";
             default:                                  return Integer.toString(capability);
         }
     }
@@ -2889,6 +2939,44 @@
         }
 
         /**
+         * Adds the given capability to the list of forbidden capabilities.
+         *
+         * A network with a capability will not match a {@link NetworkCapabilities} or
+         * {@link NetworkRequest} which has said capability set as forbidden. For example, if
+         * a request has NET_CAPABILITY_INTERNET in the list of forbidden capabilities, networks
+         * with NET_CAPABILITY_INTERNET will not match the request.
+         *
+         * If the capability was previously added to the list of required capabilities (for
+         * example, it was there by default or added using {@link #addCapability(int)} method), then
+         * it will be removed from the list of required capabilities as well.
+         *
+         * @param capability the capability
+         * @return this builder
+         * @hide
+         */
+        @NonNull
+        // TODO : @FlaggedApi(Flags.FLAG_FORBIDDEN_CAPABILITY) and public
+        public Builder addForbiddenCapability(@NetCapability final int capability) {
+            mCaps.addForbiddenCapability(capability);
+            return this;
+        }
+
+        /**
+         * Removes the given capability from the list of forbidden capabilities.
+         *
+         * @see #addForbiddenCapability(int)
+         * @param capability the capability
+         * @return this builder
+         * @hide
+         */
+        @NonNull
+        // TODO : @FlaggedApi(Flags.FLAG_FORBIDDEN_CAPABILITY) and public
+        public Builder removeForbiddenCapability(@NetCapability final int capability) {
+            mCaps.removeForbiddenCapability(capability);
+            return this;
+        }
+
+        /**
          * Adds the given enterprise capability identifier.
          * Note that when searching for a network to satisfy a request, all capabilities identifier
          * requested must be satisfied. Enterprise capability identifier is applicable only
@@ -3235,4 +3323,4 @@
             return new NetworkCapabilities(mCaps);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index 6c351d0..9824faa 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -20,6 +20,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
@@ -39,6 +40,8 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
+// TODO : replace with android.net.flags.Flags when aconfig is supported on udc-mainline-prod
+// import android.net.NetworkCapabilities.Flags;
 import android.net.NetworkCapabilities.NetCapability;
 import android.net.NetworkCapabilities.Transport;
 import android.os.Build;
@@ -281,6 +284,15 @@
                 NET_CAPABILITY_TRUSTED,
                 NET_CAPABILITY_VALIDATED);
 
+        /**
+         * Capabilities that are forbidden by default.
+         * Forbidden capabilities only make sense in NetworkRequest, not for network agents.
+         * Therefore these capabilities are only in NetworkRequest.
+         */
+        private static final int[] DEFAULT_FORBIDDEN_CAPABILITIES = new int[] {
+            NET_CAPABILITY_LOCAL_NETWORK
+        };
+
         private final NetworkCapabilities mNetworkCapabilities;
 
         // A boolean that represents whether the NOT_VCN_MANAGED capability should be deduced when
@@ -296,6 +308,16 @@
             // it for apps that do not have the NETWORK_SETTINGS permission.
             mNetworkCapabilities = new NetworkCapabilities();
             mNetworkCapabilities.setSingleUid(Process.myUid());
+            // Default forbidden capabilities are foremost meant to help with backward
+            // compatibility. When adding new types of network identified by a capability that
+            // might confuse older apps, a default forbidden capability will have apps not see
+            // these networks unless they explicitly ask for it.
+            // If the app called clearCapabilities() it will see everything, but then it
+            // can be argued that it's fair to send them too, since it asked for everything
+            // explicitly.
+            for (final int forbiddenCap : DEFAULT_FORBIDDEN_CAPABILITIES) {
+                mNetworkCapabilities.addForbiddenCapability(forbiddenCap);
+            }
         }
 
         /**
@@ -408,6 +430,7 @@
         @NonNull
         @SuppressLint("MissingGetterMatchingBuilder")
         @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        // TODO : @FlaggedApi(Flags.FLAG_FORBIDDEN_CAPABILITY) and public
         public Builder addForbiddenCapability(@NetworkCapabilities.NetCapability int capability) {
             mNetworkCapabilities.addForbiddenCapability(capability);
             return this;
@@ -424,6 +447,7 @@
         @NonNull
         @SuppressLint("BuilderSetStyle")
         @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        // TODO : @FlaggedApi(Flags.FLAG_FORBIDDEN_CAPABILITY) and public
         public Builder removeForbiddenCapability(
                 @NetworkCapabilities.NetCapability int capability) {
             mNetworkCapabilities.removeForbiddenCapability(capability);
@@ -433,6 +457,7 @@
         /**
          * Completely clears all the {@code NetworkCapabilities} from this builder instance,
          * removing even the capabilities that are set by default when the object is constructed.
+         * Also removes any set forbidden capabilities.
          *
          * @return The builder to facilitate chaining.
          */
@@ -721,6 +746,7 @@
      * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    // TODO : @FlaggedApi(Flags.FLAG_FORBIDDEN_CAPABILITY) and public instead of @SystemApi
     public boolean hasForbiddenCapability(@NetCapability int capability) {
         return networkCapabilities.hasForbiddenCapability(capability);
     }
@@ -843,6 +869,7 @@
      */
     @NonNull
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    // TODO : @FlaggedApi(Flags.FLAG_FORBIDDEN_CAPABILITY) and public instead of @SystemApi
     public @NetCapability int[] getForbiddenCapabilities() {
         // No need to make a defensive copy here as NC#getForbiddenCapabilities() already returns
         // a new array.
diff --git a/framework/src/android/net/NetworkScore.java b/framework/src/android/net/NetworkScore.java
index 815e2b0..00382f6 100644
--- a/framework/src/android/net/NetworkScore.java
+++ b/framework/src/android/net/NetworkScore.java
@@ -44,7 +44,9 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {
             KEEP_CONNECTED_NONE,
-            KEEP_CONNECTED_FOR_HANDOVER
+            KEEP_CONNECTED_FOR_HANDOVER,
+            KEEP_CONNECTED_FOR_TEST,
+            KEEP_CONNECTED_DOWNSTREAM_NETWORK
     })
     public @interface KeepConnectedReason { }
 
@@ -57,6 +59,18 @@
      * is being considered for handover.
      */
     public static final int KEEP_CONNECTED_FOR_HANDOVER = 1;
+    /**
+     * Keep this network connected even if there is no outstanding request for it, because it
+     * is used in a test and it's not necessarily easy to file the right request for it.
+     * @hide
+     */
+    public static final int KEEP_CONNECTED_FOR_TEST = 2;
+    /**
+     * Keep this network connected even if there is no outstanding request for it, because
+     * it is a downstream network.
+     * @hide
+     */
+    public static final int KEEP_CONNECTED_DOWNSTREAM_NETWORK = 3;
 
     // Agent-managed policies
     // This network should lose to a wifi that has ever been validated
diff --git a/nearby/tests/cts/fastpair/Android.bp b/nearby/tests/cts/fastpair/Android.bp
index a61d180..66a1ffe 100644
--- a/nearby/tests/cts/fastpair/Android.bp
+++ b/nearby/tests/cts/fastpair/Android.bp
@@ -26,7 +26,7 @@
         "bluetooth-test-util-lib",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
-        "truth-prebuilt",
+        "truth",
     ],
     libs: [
         "android.test.base",
diff --git a/nearby/tests/integration/privileged/Android.bp b/nearby/tests/integration/privileged/Android.bp
index e3250f6..9b6e488 100644
--- a/nearby/tests/integration/privileged/Android.bp
+++ b/nearby/tests/integration/privileged/Android.bp
@@ -27,7 +27,7 @@
         "androidx.test.ext.junit",
         "androidx.test.rules",
         "junit",
-        "truth-prebuilt",
+        "truth",
     ],
     test_suites: ["device-tests"],
 }
diff --git a/nearby/tests/integration/untrusted/Android.bp b/nearby/tests/integration/untrusted/Android.bp
index 57499e4..75f765b 100644
--- a/nearby/tests/integration/untrusted/Android.bp
+++ b/nearby/tests/integration/untrusted/Android.bp
@@ -31,7 +31,7 @@
         "androidx.test.uiautomator_uiautomator",
         "junit",
         "kotlin-test",
-        "truth-prebuilt",
+        "truth",
     ],
     test_suites: ["device-tests"],
 }
diff --git a/nearby/tests/unit/Android.bp b/nearby/tests/unit/Android.bp
index 9b35452..112c751 100644
--- a/nearby/tests/unit/Android.bp
+++ b/nearby/tests/unit/Android.bp
@@ -42,7 +42,7 @@
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
         "service-nearby-pre-jarjar",
-        "truth-prebuilt",
+        "truth",
         // "Robolectric_all-target",
     ],
     // these are needed for Extended Mockito
diff --git a/netbpfload/Android.bp b/netbpfload/Android.bp
new file mode 100644
index 0000000..5480ef7
--- /dev/null
+++ b/netbpfload/Android.bp
@@ -0,0 +1,48 @@
+//
+// Copyright (C) 2023 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.
+//
+
+cc_binary {
+    name: "netbpfload",
+
+    defaults: ["bpf_defaults"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wthread-safety",
+    ],
+    sanitize: {
+        integer_overflow: true,
+    },
+
+    header_libs: ["bpf_headers"],
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+    srcs: [
+        "loader.cpp",
+        "NetBpfLoad.cpp",
+    ],
+    apex_available: [
+        "com.android.tethering",
+        "//apex_available:platform",
+    ],
+    // really should be Android 14/U (34), but we cannot include binaries built
+    // against newer sdk in the apex, which still targets 30(R):
+    // module "netbpfload" variant "android_x86_apex30": should support
+    // min_sdk_version(30) for "com.android.tethering": newer SDK(34).
+    min_sdk_version: "30",
+}
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index 8e47ea8..b44a0bc 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -44,12 +44,11 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
-#include <libbpf_android.h>
 #include <log/log.h>
-#include <netdutils/Misc.h>
-#include <netdutils/Slice.h>
+
 #include "BpfSyscallWrappers.h"
 #include "bpf/BpfUtils.h"
+#include "loader.h"
 
 using android::base::EndsWith;
 using android::bpf::domain;
@@ -66,76 +65,34 @@
     abort();  // can only hit this if permissions (likely selinux) are screwed up
 }
 
-constexpr unsigned long long kTetheringApexDomainBitmask =
-        domainToBitmask(domain::tethering) |
-        domainToBitmask(domain::net_private) |
-        domainToBitmask(domain::net_shared) |
-        domainToBitmask(domain::netd_readonly) |
-        domainToBitmask(domain::netd_shared);
-
-// Programs shipped inside the tethering apex should be limited to networking stuff,
-// as KPROBE, PERF_EVENT, TRACEPOINT are dangerous to use from mainline updatable code,
-// since they are less stable abi/api and may conflict with platform uses of bpf.
-constexpr bpf_prog_type kTetheringApexAllowedProgTypes[] = {
-        BPF_PROG_TYPE_CGROUP_SKB,
-        BPF_PROG_TYPE_CGROUP_SOCK,
-        BPF_PROG_TYPE_CGROUP_SOCKOPT,
-        BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
-        BPF_PROG_TYPE_CGROUP_SYSCTL,
-        BPF_PROG_TYPE_LWT_IN,
-        BPF_PROG_TYPE_LWT_OUT,
-        BPF_PROG_TYPE_LWT_SEG6LOCAL,
-        BPF_PROG_TYPE_LWT_XMIT,
-        BPF_PROG_TYPE_SCHED_ACT,
-        BPF_PROG_TYPE_SCHED_CLS,
-        BPF_PROG_TYPE_SOCKET_FILTER,
-        BPF_PROG_TYPE_SOCK_OPS,
-        BPF_PROG_TYPE_XDP,
-};
-
 
 const android::bpf::Location locations[] = {
         // S+ Tethering mainline module (network_stack): tether offload
         {
                 .dir = "/apex/com.android.tethering/etc/bpf/",
                 .prefix = "tethering/",
-                .allowedDomainBitmask = kTetheringApexDomainBitmask,
-                .allowedProgTypes = kTetheringApexAllowedProgTypes,
-                .allowedProgTypesLength = arraysize(kTetheringApexAllowedProgTypes),
         },
         // T+ Tethering mainline module (shared with netd & system server)
         // netutils_wrapper (for iptables xt_bpf) has access to programs
         {
                 .dir = "/apex/com.android.tethering/etc/bpf/netd_shared/",
                 .prefix = "netd_shared/",
-                .allowedDomainBitmask = kTetheringApexDomainBitmask,
-                .allowedProgTypes = kTetheringApexAllowedProgTypes,
-                .allowedProgTypesLength = arraysize(kTetheringApexAllowedProgTypes),
         },
         // T+ Tethering mainline module (shared with netd & system server)
         // netutils_wrapper has no access, netd has read only access
         {
                 .dir = "/apex/com.android.tethering/etc/bpf/netd_readonly/",
                 .prefix = "netd_readonly/",
-                .allowedDomainBitmask = kTetheringApexDomainBitmask,
-                .allowedProgTypes = kTetheringApexAllowedProgTypes,
-                .allowedProgTypesLength = arraysize(kTetheringApexAllowedProgTypes),
         },
         // T+ Tethering mainline module (shared with system server)
         {
                 .dir = "/apex/com.android.tethering/etc/bpf/net_shared/",
                 .prefix = "net_shared/",
-                .allowedDomainBitmask = kTetheringApexDomainBitmask,
-                .allowedProgTypes = kTetheringApexAllowedProgTypes,
-                .allowedProgTypesLength = arraysize(kTetheringApexAllowedProgTypes),
         },
         // T+ Tethering mainline module (not shared, just network_stack)
         {
                 .dir = "/apex/com.android.tethering/etc/bpf/net_private/",
                 .prefix = "net_private/",
-                .allowedDomainBitmask = kTetheringApexDomainBitmask,
-                .allowedProgTypes = kTetheringApexAllowedProgTypes,
-                .allowedProgTypesLength = arraysize(kTetheringApexAllowedProgTypes),
         },
 };
 
@@ -278,13 +235,6 @@
         if (createSysFsBpfSubDir(location.prefix)) return 1;
     }
 
-    // Note: there's no actual src dir for fs_bpf_loader .o's,
-    // so it is not listed in 'locations[].prefix'.
-    // This is because this is primarily meant for triggering genfscon rules,
-    // and as such this will likely always be the case.
-    // Thus we need to manually create the /sys/fs/bpf/loader subdirectory.
-    if (createSysFsBpfSubDir("loader")) return 1;
-
     // Load all ELF objects, create programs and maps, and pin them
     for (const auto& location : locations) {
         if (loadAllElfObjects(location) != 0) {
diff --git a/netbpfload/loader.cpp b/netbpfload/loader.cpp
index 3bb758b..c534b2c 100644
--- a/netbpfload/loader.cpp
+++ b/netbpfload/loader.cpp
@@ -43,7 +43,7 @@
 #include "BpfSyscallWrappers.h"
 #include "bpf/BpfUtils.h"
 #include "bpf/bpf_map_def.h"
-#include "include/libbpf_android.h"
+#include "loader.h"
 
 #if BPFLOADER_VERSION < COMPILE_FOR_BPFLOADER_VERSION
 #error "BPFLOADER_VERSION is less than COMPILE_FOR_BPFLOADER_VERSION"
@@ -59,9 +59,9 @@
 
 #include <android-base/cmsg.h>
 #include <android-base/file.h>
+#include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
-#include <cutils/properties.h>
 
 #define BPF_FS_PATH "/sys/fs/bpf/"
 
@@ -79,17 +79,11 @@
 using std::string;
 using std::vector;
 
-static std::string getBuildTypeInternal() {
-    char value[PROPERTY_VALUE_MAX] = {};
-    (void)property_get("ro.build.type", value, "unknown");  // ignore length
-    return value;
-}
-
 namespace android {
 namespace bpf {
 
 const std::string& getBuildType() {
-    static std::string t = getBuildTypeInternal();
+    static std::string t = android::base::GetProperty("ro.build.type", "unknown");
     return t;
 }
 
@@ -178,6 +172,10 @@
  *
  * However, be aware that you should not be directly using the SECTION() macro.
  * Instead use the DEFINE_(BPF|XDP)_(PROG|MAP)... & LICENSE/CRITICAL macros.
+ *
+ * Programs shipped inside the tethering apex should be limited to networking stuff,
+ * as KPROBE, PERF_EVENT, TRACEPOINT are dangerous to use from mainline updatable code,
+ * since they are less stable abi/api and may conflict with platform uses of bpf.
  */
 sectionType sectionNameTypes[] = {
         {"bind4/",         BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_BIND},
@@ -189,13 +187,10 @@
         {"egress/",        BPF_PROG_TYPE_CGROUP_SKB,       BPF_CGROUP_INET_EGRESS},
         {"getsockopt/",    BPF_PROG_TYPE_CGROUP_SOCKOPT,   BPF_CGROUP_GETSOCKOPT},
         {"ingress/",       BPF_PROG_TYPE_CGROUP_SKB,       BPF_CGROUP_INET_INGRESS},
-        {"kprobe/",        BPF_PROG_TYPE_KPROBE,           BPF_ATTACH_TYPE_UNSPEC},
-        {"kretprobe/",     BPF_PROG_TYPE_KPROBE,           BPF_ATTACH_TYPE_UNSPEC},
         {"lwt_in/",        BPF_PROG_TYPE_LWT_IN,           BPF_ATTACH_TYPE_UNSPEC},
         {"lwt_out/",       BPF_PROG_TYPE_LWT_OUT,          BPF_ATTACH_TYPE_UNSPEC},
         {"lwt_seg6local/", BPF_PROG_TYPE_LWT_SEG6LOCAL,    BPF_ATTACH_TYPE_UNSPEC},
         {"lwt_xmit/",      BPF_PROG_TYPE_LWT_XMIT,         BPF_ATTACH_TYPE_UNSPEC},
-        {"perf_event/",    BPF_PROG_TYPE_PERF_EVENT,       BPF_ATTACH_TYPE_UNSPEC},
         {"postbind4/",     BPF_PROG_TYPE_CGROUP_SOCK,      BPF_CGROUP_INET4_POST_BIND},
         {"postbind6/",     BPF_PROG_TYPE_CGROUP_SOCK,      BPF_CGROUP_INET6_POST_BIND},
         {"recvmsg4/",      BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_RECVMSG},
@@ -208,9 +203,6 @@
         {"skfilter/",      BPF_PROG_TYPE_SOCKET_FILTER,    BPF_ATTACH_TYPE_UNSPEC},
         {"sockops/",       BPF_PROG_TYPE_SOCK_OPS,         BPF_CGROUP_SOCK_OPS},
         {"sysctl",         BPF_PROG_TYPE_CGROUP_SYSCTL,    BPF_CGROUP_SYSCTL},
-        {"tracepoint/",    BPF_PROG_TYPE_TRACEPOINT,       BPF_ATTACH_TYPE_UNSPEC},
-        {"uprobe/",        BPF_PROG_TYPE_KPROBE,           BPF_ATTACH_TYPE_UNSPEC},
-        {"uretprobe/",     BPF_PROG_TYPE_KPROBE,           BPF_ATTACH_TYPE_UNSPEC},
         {"xdp/",           BPF_PROG_TYPE_XDP,              BPF_ATTACH_TYPE_UNSPEC},
 };
 
@@ -393,19 +385,10 @@
     return 0;
 }
 
-static enum bpf_prog_type getFuseProgType() {
-    int result = BPF_PROG_TYPE_UNSPEC;
-    ifstream("/sys/fs/fuse/bpf_prog_type_fuse") >> result;
-    return static_cast<bpf_prog_type>(result);
-}
-
 static enum bpf_prog_type getSectionType(string& name) {
     for (auto& snt : sectionNameTypes)
         if (StartsWith(name, snt.name)) return snt.type;
 
-    // TODO Remove this code when fuse-bpf is upstream and this BPF_PROG_TYPE_FUSE is fixed
-    if (StartsWith(name, "fuse/")) return getFuseProgType();
-
     return BPF_PROG_TYPE_UNSPEC;
 }
 
@@ -415,6 +398,7 @@
     return BPF_ATTACH_TYPE_UNSPEC;
 }
 
+/*
 static string getSectionName(enum bpf_prog_type type)
 {
     for (auto& snt : sectionNameTypes)
@@ -423,6 +407,7 @@
 
     return "UNKNOWN SECTION NAME " + std::to_string(type);
 }
+*/
 
 static int readProgDefs(ifstream& elfFile, vector<struct bpf_prog_def>& pd,
                         size_t sizeOfBpfProgDef) {
@@ -502,22 +487,8 @@
     return 0;
 }
 
-static bool IsAllowed(bpf_prog_type type, const bpf_prog_type* allowed, size_t numAllowed) {
-    if (allowed == nullptr) return true;
-
-    for (size_t i = 0; i < numAllowed; i++) {
-        if (allowed[i] == BPF_PROG_TYPE_UNSPEC) {
-            if (type == getFuseProgType()) return true;
-        } else if (type == allowed[i])
-            return true;
-    }
-
-    return false;
-}
-
 /* Read a section by its index - for ex to get sec hdr strtab blob */
-static int readCodeSections(ifstream& elfFile, vector<codeSection>& cs, size_t sizeOfBpfProgDef,
-                            const bpf_prog_type* allowed, size_t numAllowed) {
+static int readCodeSections(ifstream& elfFile, vector<codeSection>& cs, size_t sizeOfBpfProgDef) {
     vector<Elf64_Shdr> shTable;
     int entries, ret = 0;
 
@@ -544,11 +515,6 @@
 
         if (ptype == BPF_PROG_TYPE_UNSPEC) continue;
 
-        if (!IsAllowed(ptype, allowed, numAllowed)) {
-            ALOGE("Program type %s not permitted here", getSectionName(ptype).c_str());
-            return -1;
-        }
-
         // This must be done before '/' is replaced with '_'.
         cs_temp.expected_attach_type = getExpectedAttachType(name);
 
@@ -655,8 +621,7 @@
 }
 
 static int createMaps(const char* elfPath, ifstream& elfFile, vector<unique_fd>& mapFds,
-                      const char* prefix, const unsigned long long allowedDomainBitmask,
-                      const size_t sizeOfBpfMapDef) {
+                      const char* prefix, const size_t sizeOfBpfMapDef) {
     int ret;
     vector<char> mdData;
     vector<struct bpf_map_def> md;
@@ -767,11 +732,6 @@
 
         domain selinux_context = getDomainFromSelinuxContext(md[i].selinux_context);
         if (specified(selinux_context)) {
-            if (!inDomainBitmask(selinux_context, allowedDomainBitmask)) {
-                ALOGE("map %s has invalid selinux_context of %d (allowed bitmask 0x%llx)",
-                      mapNames[i].c_str(), selinux_context, allowedDomainBitmask);
-                return -EINVAL;
-            }
             ALOGI("map %s selinux_context [%-32s] -> %d -> '%s' (%s)", mapNames[i].c_str(),
                   md[i].selinux_context, selinux_context, lookupSelinuxContext(selinux_context),
                   lookupPinSubdir(selinux_context));
@@ -780,11 +740,6 @@
         domain pin_subdir = getDomainFromPinSubdir(md[i].pin_subdir);
         if (unrecognized(pin_subdir)) return -ENOTDIR;
         if (specified(pin_subdir)) {
-            if (!inDomainBitmask(pin_subdir, allowedDomainBitmask)) {
-                ALOGE("map %s has invalid pin_subdir of %d (allowed bitmask 0x%llx)",
-                      mapNames[i].c_str(), pin_subdir, allowedDomainBitmask);
-                return -EINVAL;
-            }
             ALOGI("map %s pin_subdir [%-32s] -> %d -> '%s'", mapNames[i].c_str(), md[i].pin_subdir,
                   pin_subdir, lookupPinSubdir(pin_subdir));
         }
@@ -955,7 +910,7 @@
 }
 
 static int loadCodeSections(const char* elfPath, vector<codeSection>& cs, const string& license,
-                            const char* prefix, const unsigned long long allowedDomainBitmask) {
+                            const char* prefix) {
     unsigned kvers = kernelVersion();
 
     if (!kvers) {
@@ -1014,22 +969,12 @@
         if (unrecognized(pin_subdir)) return -ENOTDIR;
 
         if (specified(selinux_context)) {
-            if (!inDomainBitmask(selinux_context, allowedDomainBitmask)) {
-                ALOGE("prog %s has invalid selinux_context of %d (allowed bitmask 0x%llx)",
-                      name.c_str(), selinux_context, allowedDomainBitmask);
-                return -EINVAL;
-            }
             ALOGI("prog %s selinux_context [%-32s] -> %d -> '%s' (%s)", name.c_str(),
                   cs[i].prog_def->selinux_context, selinux_context,
                   lookupSelinuxContext(selinux_context), lookupPinSubdir(selinux_context));
         }
 
         if (specified(pin_subdir)) {
-            if (!inDomainBitmask(pin_subdir, allowedDomainBitmask)) {
-                ALOGE("prog %s has invalid pin_subdir of %d (allowed bitmask 0x%llx)", name.c_str(),
-                      pin_subdir, allowedDomainBitmask);
-                return -EINVAL;
-            }
             ALOGI("prog %s pin_subdir [%-32s] -> %d -> '%s'", name.c_str(),
                   cs[i].prog_def->pin_subdir, pin_subdir, lookupPinSubdir(pin_subdir));
         }
@@ -1210,8 +1155,7 @@
         return -1;
     }
 
-    ret = readCodeSections(elfFile, cs, sizeOfBpfProgDef, location.allowedProgTypes,
-                           location.allowedProgTypesLength);
+    ret = readCodeSections(elfFile, cs, sizeOfBpfProgDef);
     if (ret) {
         ALOGE("Couldn't read all code sections in %s", elfPath);
         return ret;
@@ -1220,8 +1164,7 @@
     /* Just for future debugging */
     if (0) dumpAllCs(cs);
 
-    ret = createMaps(elfPath, elfFile, mapFds, location.prefix, location.allowedDomainBitmask,
-                     sizeOfBpfMapDef);
+    ret = createMaps(elfPath, elfFile, mapFds, location.prefix, sizeOfBpfMapDef);
     if (ret) {
         ALOGE("Failed to create maps: (ret=%d) in %s", ret, elfPath);
         return ret;
@@ -1232,8 +1175,7 @@
 
     applyMapRelo(elfFile, mapFds, cs);
 
-    ret = loadCodeSections(elfPath, cs, string(license.data()), location.prefix,
-                           location.allowedDomainBitmask);
+    ret = loadCodeSections(elfPath, cs, string(license.data()), location.prefix);
     if (ret) ALOGE("Failed to load programs, loadCodeSections ret=%d", ret);
 
     return ret;
diff --git a/netbpfload/loader.h b/netbpfload/loader.h
index a47e4da..b884637 100644
--- a/netbpfload/loader.h
+++ b/netbpfload/loader.h
@@ -64,20 +64,9 @@
     return d != domain::unspecified;
 }
 
-static constexpr unsigned long long domainToBitmask(domain d) {
-    return specified(d) ? 1uLL << (static_cast<int>(d) - 1) : 0;
-}
-
-static constexpr bool inDomainBitmask(domain d, unsigned long long v) {
-    return domainToBitmask(d) & v;
-}
-
 struct Location {
     const char* const dir = "";
     const char* const prefix = "";
-    unsigned long long allowedDomainBitmask = 0;
-    const bpf_prog_type* allowedProgTypes = nullptr;
-    size_t allowedProgTypesLength = 0;
 };
 
 // BPF loader implementation. Loads an eBPF ELF object
diff --git a/netbpfload/netbpfload.rc b/netbpfload/netbpfload.rc
deleted file mode 100644
index 20fbb9f..0000000
--- a/netbpfload/netbpfload.rc
+++ /dev/null
@@ -1,85 +0,0 @@
-# zygote-start is what officially starts netd (see //system/core/rootdir/init.rc)
-# However, on some hardware it's started from post-fs-data as well, which is just
-# a tad earlier.  There's no benefit to that though, since on 4.9+ P+ devices netd
-# will just block until bpfloader finishes and sets the bpf.progs_loaded property.
-#
-# It is important that we start netbpfload after:
-#   - /sys/fs/bpf is already mounted,
-#   - apex (incl. rollback) is initialized (so that in the future we can load bpf
-#     programs shipped as part of apex mainline modules)
-#   - logd is ready for us to log stuff
-#
-# At the same time we want to be as early as possible to reduce races and thus
-# failures (before memory is fragmented, and cpu is busy running tons of other
-# stuff) and we absolutely want to be before netd and the system boot slot is
-# considered to have booted successfully.
-#
-on load_bpf_programs
-    exec_start netbpfload
-
-service netbpfload /system/bin/netbpfload
-    capabilities CHOWN SYS_ADMIN NET_ADMIN
-    # The following group memberships are a workaround for lack of DAC_OVERRIDE
-    # and allow us to open (among other things) files that we created and are
-    # no longer root owned (due to CHOWN) but still have group read access to
-    # one of the following groups.  This is not perfect, but a more correct
-    # solution requires significantly more effort to implement.
-    group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
-    user root
-    #
-    # Set RLIMIT_MEMLOCK to 1GiB for netbpfload
-    #
-    # Actually only 8MiB would be needed if netbpfload ran as its own uid.
-    #
-    # However, while the rlimit is per-thread, the accounting is system wide.
-    # So, for example, if the graphics stack has already allocated 10MiB of
-    # memlock data before netbpfload even gets a chance to run, it would fail
-    # if its memlock rlimit is only 8MiB - since there would be none left for it.
-    #
-    # netbpfload succeeding is critical to system health, since a failure will
-    # cause netd crashloop and thus system server crashloop... and the only
-    # recovery is a full kernel reboot.
-    #
-    # We've had issues where devices would sometimes (rarely) boot into
-    # a crashloop because netbpfload would occasionally lose a boot time
-    # race against the graphics stack's boot time locked memory allocation.
-    #
-    # Thus netbpfload's memlock has to be 8MB higher then the locked memory
-    # consumption of the root uid anywhere else in the system...
-    # But we don't know what that is for all possible devices...
-    #
-    # Ideally, we'd simply grant netbpfload the IPC_LOCK capability and it
-    # would simply ignore it's memlock rlimit... but it turns that this
-    # capability is not even checked by the kernel's bpf system call.
-    #
-    # As such we simply use 1GiB as a reasonable approximation of infinity.
-    #
-    rlimit memlock 1073741824 1073741824
-    oneshot
-    #
-    # How to debug bootloops caused by 'netbpfload-failed'.
-    #
-    # 1. On some lower RAM devices (like wembley) you may need to first enable developer mode
-    #    (from the Settings app UI), and change the developer option "Logger buffer sizes"
-    #    from the default (wembley: 64kB) to the maximum (1M) per log buffer.
-    #    Otherwise buffer will overflow before you manage to dump it and you'll get useless logs.
-    #
-    # 2. comment out 'reboot_on_failure reboot,netbpfload-failed' below
-    # 3. rebuild/reflash/reboot
-    # 4. as the device is booting up capture netbpfload logs via:
-    #    adb logcat -s 'NetBpfLoad:*' 'NetBpfLoader:*'
-    #
-    # something like:
-    #   $ adb reboot; sleep 1; adb wait-for-device; adb root; sleep 1; adb wait-for-device; adb logcat -s 'NetBpfLoad:*' 'NetBpfLoader:*'
-    # will take care of capturing logs as early as possible
-    #
-    # 5. look through the logs from the kernel's bpf verifier that netbpfload dumps out,
-    #    it usually makes sense to search back from the end and find the particular
-    #    bpf verifier failure that caused netbpfload to terminate early with an error code.
-    #    This will probably be something along the lines of 'too many jumps' or
-    #    'cannot prove return value is 0 or 1' or 'unsupported / unknown operation / helper',
-    #    'invalid bpf_context access', etc.
-    #
-    reboot_on_failure reboot,netbpfload-failed
-    # we're not really updatable, but want to be able to load bpf programs shipped in apexes
-    updatable
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index fa92f10..a7a4059 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -130,12 +130,21 @@
                 attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
     }
 
-    // This should trivially pass, since we just attached up above,
-    // but BPF_PROG_QUERY is only implemented on 4.19+ kernels.
     if (bpf::isAtLeastKernelVersion(4, 19, 0)) {
+        RETURN_IF_NOT_OK(attachProgramToCgroup(
+                "/sys/fs/bpf/netd_readonly/prog_block_bind4_block_port",
+                cg_fd, BPF_CGROUP_INET4_BIND));
+        RETURN_IF_NOT_OK(attachProgramToCgroup(
+                "/sys/fs/bpf/netd_readonly/prog_block_bind6_block_port",
+                cg_fd, BPF_CGROUP_INET6_BIND));
+
+        // This should trivially pass, since we just attached up above,
+        // but BPF_PROG_QUERY is only implemented on 4.19+ kernels.
         if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET_EGRESS) <= 0) abort();
         if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET_INGRESS) <= 0) abort();
         if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET_SOCK_CREATE) <= 0) abort();
+        if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET4_BIND) <= 0) abort();
+        if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET6_BIND) <= 0) abort();
     }
 
     return netdutils::status::ok;
diff --git a/remoteauth/tests/unit/Android.bp b/remoteauth/tests/unit/Android.bp
index 16a8242..a21c033 100644
--- a/remoteauth/tests/unit/Android.bp
+++ b/remoteauth/tests/unit/Android.bp
@@ -20,7 +20,7 @@
     name: "RemoteAuthUnitTests",
     defaults: [
         "enable-remoteauth-targets",
-        "mts-target-sdk-version-current"
+        "mts-target-sdk-version-current",
     ],
     sdk_version: "test_current",
     min_sdk_version: "31",
@@ -45,7 +45,7 @@
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
         "service-remoteauth-pre-jarjar",
-        "truth-prebuilt",
+        "truth",
     ],
     // these are needed for Extended Mockito
     jni_libs: [
diff --git a/service-t/Android.bp b/service-t/Android.bp
index 7e2d2f4..bc49f0e 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -102,7 +102,12 @@
     ],
     exclude_srcs: [
         "src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java",
-        "src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java"
+        "src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java",
+        "src/com/android/server/connectivity/mdns/MdnsAdvertiser.java",
+        "src/com/android/server/connectivity/mdns/MdnsAnnouncer.java",
+        "src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java",
+        "src/com/android/server/connectivity/mdns/MdnsProber.java",
+        "src/com/android/server/connectivity/mdns/MdnsRecordRepository.java",
     ],
     static_libs: [
         "net-utils-device-common-mdns-standalone-build-test",
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAnnouncer.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAnnouncer.java
index d9bc643..5812797 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAnnouncer.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAnnouncer.java
@@ -110,7 +110,7 @@
             @NonNull MdnsReplySender replySender,
             @Nullable PacketRepeaterCallback<BaseAnnouncementInfo> cb,
             @NonNull SharedLog sharedLog) {
-        super(looper, replySender, cb, sharedLog);
+        super(looper, replySender, cb, sharedLog, MdnsAdvertiser.DBG);
     }
 
     // TODO: Notify MdnsRecordRepository that the records were announced for that service ID,
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsConstants.java b/service-t/src/com/android/server/connectivity/mdns/MdnsConstants.java
index 1251170..b83a6a0 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsConstants.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsConstants.java
@@ -19,6 +19,7 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
 import java.nio.charset.Charset;
 
@@ -42,6 +43,10 @@
     public static final String SUBTYPE_PREFIX = "_";
     private static final String MDNS_IPV4_HOST_ADDRESS = "224.0.0.251";
     private static final String MDNS_IPV6_HOST_ADDRESS = "FF02::FB";
+    public static final InetSocketAddress IPV6_SOCKET_ADDR = new InetSocketAddress(
+            getMdnsIPv6Address(), MDNS_PORT);
+    public static final InetSocketAddress IPV4_SOCKET_ADDR = new InetSocketAddress(
+            getMdnsIPv4Address(), MDNS_PORT);
     private static InetAddress mdnsAddress;
     private MdnsConstants() {
     }
@@ -75,4 +80,4 @@
     public static Charset getUtf8Charset() {
         return UTF_8;
     }
-}
\ No newline at end of file
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
index 40dfd57..37e9743 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -161,7 +161,7 @@
                 @NonNull SharedLog sharedLog) {
             return new MdnsReplySender(looper, socket, packetCreationBuffer,
                     sharedLog.forSubComponent(
-                            MdnsReplySender.class.getSimpleName() + "/" + interfaceTag));
+                            MdnsReplySender.class.getSimpleName() + "/" + interfaceTag), DBG);
         }
 
         /** @see MdnsAnnouncer */
@@ -370,7 +370,7 @@
         // happen when the incoming packet has answer records (not a question), so there will be no
         // answer. One exception is simultaneous probe tiebreaking (rfc6762 8.2), in which case the
         // conflicting service is still probing and won't reply either.
-        final MdnsRecordRepository.ReplyInfo answers = mRecordRepository.getReply(packet, src);
+        final MdnsReplyInfo answers = mRecordRepository.getReply(packet, src);
 
         if (answers == null) return;
         mReplySender.queueReply(answers);
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
index fd0f5c9..e84cead 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
@@ -16,8 +16,8 @@
 
 package com.android.server.connectivity.mdns;
 
-import static com.android.server.connectivity.mdns.MdnsRecordRepository.IPV4_ADDR;
-import static com.android.server.connectivity.mdns.MdnsRecordRepository.IPV6_ADDR;
+import static com.android.server.connectivity.mdns.MdnsConstants.IPV4_SOCKET_ADDR;
+import static com.android.server.connectivity.mdns.MdnsConstants.IPV6_SOCKET_ADDR;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -38,9 +38,8 @@
  */
 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
 public abstract class MdnsPacketRepeater<T extends MdnsPacketRepeater.Request> {
-    private static final boolean DBG = MdnsAdvertiser.DBG;
     private static final InetSocketAddress[] ALL_ADDRS = new InetSocketAddress[] {
-            IPV4_ADDR, IPV6_ADDR
+            IPV4_SOCKET_ADDR, IPV6_SOCKET_ADDR
     };
 
     @NonNull
@@ -51,6 +50,7 @@
     private final PacketRepeaterCallback<T> mCb;
     @NonNull
     private final SharedLog mSharedLog;
+    private final boolean mEnableDebugLog;
 
     /**
      * Status callback from {@link MdnsPacketRepeater}.
@@ -111,7 +111,7 @@
             }
 
             final MdnsPacket packet = request.getPacket(index);
-            if (DBG) {
+            if (mEnableDebugLog) {
                 mSharedLog.v("Sending packets for iteration " + index + " out of "
                         + request.getNumSends() + " for ID " + msg.what);
             }
@@ -134,7 +134,7 @@
                 // likely not to be available since the device is in deep sleep anyway.
                 final long delay = request.getDelayMs(nextIndex);
                 sendMessageDelayed(obtainMessage(msg.what, nextIndex, 0, request), delay);
-                if (DBG) mSharedLog.v("Scheduled next packet in " + delay + "ms");
+                if (mEnableDebugLog) mSharedLog.v("Scheduled next packet in " + delay + "ms");
             }
 
             // Call onSent after scheduling the next run, to allow the callback to cancel it
@@ -145,15 +145,17 @@
     }
 
     protected MdnsPacketRepeater(@NonNull Looper looper, @NonNull MdnsReplySender replySender,
-            @Nullable PacketRepeaterCallback<T> cb, @NonNull SharedLog sharedLog) {
+            @Nullable PacketRepeaterCallback<T> cb, @NonNull SharedLog sharedLog,
+            boolean enableDebugLog) {
         mHandler = new ProbeHandler(looper);
         mReplySender = replySender;
         mCb = cb;
         mSharedLog = sharedLog;
+        mEnableDebugLog = enableDebugLog;
     }
 
     protected void startSending(int id, @NonNull T request, long initialDelayMs) {
-        if (DBG) {
+        if (mEnableDebugLog) {
             mSharedLog.v("Starting send with id " + id + ", request "
                     + request.getClass().getSimpleName() + ", delay " + initialDelayMs);
         }
@@ -172,7 +174,7 @@
         // all in the handler queue; unless this method is called from a message, but the current
         // message cannot be cancelled.
         if (mHandler.hasMessages(id)) {
-            if (DBG) {
+            if (mEnableDebugLog) {
                 mSharedLog.v("Stopping send on id " + id);
             }
             mHandler.removeMessages(id);
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java b/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java
index f2b562a..e88947a 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java
@@ -40,9 +40,8 @@
     private static final long CONFLICT_RETRY_DELAY_MS = 5_000L;
 
     public MdnsProber(@NonNull Looper looper, @NonNull MdnsReplySender replySender,
-            @NonNull PacketRepeaterCallback<ProbingInfo> cb,
-            @NonNull SharedLog sharedLog) {
-        super(looper, replySender, cb, sharedLog);
+            @NonNull PacketRepeaterCallback<ProbingInfo> cb, @NonNull SharedLog sharedLog) {
+        super(looper, replySender, cb, sharedLog, MdnsAdvertiser.DBG);
     }
 
     /** Probing request to send with {@link MdnsProber}. */
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
index f532372..130ff48 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -16,6 +16,8 @@
 
 package com.android.server.connectivity.mdns;
 
+import static com.android.server.connectivity.mdns.MdnsConstants.IPV4_SOCKET_ADDR;
+import static com.android.server.connectivity.mdns.MdnsConstants.IPV6_SOCKET_ADDR;
 import static com.android.server.connectivity.mdns.MdnsConstants.NO_PACKET;
 
 import android.annotation.NonNull;
@@ -79,11 +81,6 @@
     private static final String[] DNS_SD_SERVICE_TYPE =
             new String[] { "_services", "_dns-sd", "_udp", LOCAL_TLD };
 
-    public static final InetSocketAddress IPV6_ADDR = new InetSocketAddress(
-            MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT);
-    public static final InetSocketAddress IPV4_ADDR = new InetSocketAddress(
-            MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT);
-
     @NonNull
     private final Random mDelayGenerator = new Random();
     // Map of service unique ID -> records for service
@@ -455,44 +452,13 @@
     }
 
     /**
-     * Info about a reply to be sent.
-     */
-    public static class ReplyInfo {
-        @NonNull
-        public final List<MdnsRecord> answers;
-        @NonNull
-        public final List<MdnsRecord> additionalAnswers;
-        public final long sendDelayMs;
-        @NonNull
-        public final InetSocketAddress destination;
-
-        public ReplyInfo(
-                @NonNull List<MdnsRecord> answers,
-                @NonNull List<MdnsRecord> additionalAnswers,
-                long sendDelayMs,
-                @NonNull InetSocketAddress destination) {
-            this.answers = answers;
-            this.additionalAnswers = additionalAnswers;
-            this.sendDelayMs = sendDelayMs;
-            this.destination = destination;
-        }
-
-        @Override
-        public String toString() {
-            return "{ReplyInfo to " + destination + ", answers: " + answers.size()
-                    + ", additionalAnswers: " + additionalAnswers.size()
-                    + ", sendDelayMs " + sendDelayMs + "}";
-        }
-    }
-
-    /**
      * Get the reply to send to an incoming packet.
      *
      * @param packet The incoming packet.
      * @param src The source address of the incoming packet.
      */
     @Nullable
-    public ReplyInfo getReply(MdnsPacket packet, InetSocketAddress src) {
+    public MdnsReplyInfo getReply(MdnsPacket packet, InetSocketAddress src) {
         final long now = SystemClock.elapsedRealtime();
         final boolean replyUnicast = (packet.flags & MdnsConstants.QCLASS_UNICAST) != 0;
         final ArrayList<MdnsRecord> additionalAnswerRecords = new ArrayList<>();
@@ -543,9 +509,9 @@
         if (replyUnicast) {
             dest = src;
         } else if (src.getAddress() instanceof Inet4Address) {
-            dest = IPV4_ADDR;
+            dest = IPV4_SOCKET_ADDR;
         } else {
-            dest = IPV6_ADDR;
+            dest = IPV6_SOCKET_ADDR;
         }
 
         // Build the list of answer records from their RecordInfo
@@ -559,7 +525,7 @@
             answerRecords.add(info.record);
         }
 
-        return new ReplyInfo(answerRecords, additionalAnswerRecords, delayMs, dest);
+        return new MdnsReplyInfo(answerRecords, additionalAnswerRecords, delayMs, dest);
     }
 
     /**
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsReplyInfo.java b/service-t/src/com/android/server/connectivity/mdns/MdnsReplyInfo.java
new file mode 100644
index 0000000..ce61b54
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsReplyInfo.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 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.mdns;
+
+import android.annotation.NonNull;
+
+import java.net.InetSocketAddress;
+import java.util.List;
+
+/**
+ * Info about a mDNS reply to be sent.
+ */
+public final class MdnsReplyInfo {
+    @NonNull
+    public final List<MdnsRecord> answers;
+    @NonNull
+    public final List<MdnsRecord> additionalAnswers;
+    public final long sendDelayMs;
+    @NonNull
+    public final InetSocketAddress destination;
+
+    public MdnsReplyInfo(
+            @NonNull List<MdnsRecord> answers,
+            @NonNull List<MdnsRecord> additionalAnswers,
+            long sendDelayMs,
+            @NonNull InetSocketAddress destination) {
+        this.answers = answers;
+        this.additionalAnswers = additionalAnswers;
+        this.sendDelayMs = sendDelayMs;
+        this.destination = destination;
+    }
+
+    @Override
+    public String toString() {
+        return "{MdnsReplyInfo to " + destination + ", answers: " + answers.size()
+                + ", additionalAnswers: " + additionalAnswers.size()
+                + ", sendDelayMs " + sendDelayMs + "}";
+    }
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
index 3d64b5a..abf5d99 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
@@ -26,7 +26,6 @@
 import android.os.Message;
 
 import com.android.net.module.util.SharedLog;
-import com.android.server.connectivity.mdns.MdnsRecordRepository.ReplyInfo;
 import com.android.server.connectivity.mdns.util.MdnsUtils;
 
 import java.io.IOException;
@@ -45,7 +44,6 @@
  */
 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
 public class MdnsReplySender {
-    private static final boolean DBG = MdnsAdvertiser.DBG;
     private static final int MSG_SEND = 1;
     private static final int PACKET_NOT_SENT = 0;
     private static final int PACKET_SENT = 1;
@@ -58,24 +56,27 @@
     private final byte[] mPacketCreationBuffer;
     @NonNull
     private final SharedLog mSharedLog;
+    private final boolean mEnableDebugLog;
 
     public MdnsReplySender(@NonNull Looper looper, @NonNull MdnsInterfaceSocket socket,
-            @NonNull byte[] packetCreationBuffer, @NonNull SharedLog sharedLog) {
+            @NonNull byte[] packetCreationBuffer, @NonNull SharedLog sharedLog,
+            boolean enableDebugLog) {
         mHandler = new SendHandler(looper);
         mSocket = socket;
         mPacketCreationBuffer = packetCreationBuffer;
         mSharedLog = sharedLog;
+        mEnableDebugLog = enableDebugLog;
     }
 
     /**
      * Queue a reply to be sent when its send delay expires.
      */
-    public void queueReply(@NonNull ReplyInfo reply) {
+    public void queueReply(@NonNull MdnsReplyInfo reply) {
         ensureRunningOnHandlerThread(mHandler);
         // TODO: implement response aggregation (RFC 6762 6.4)
         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SEND, reply), reply.sendDelayMs);
 
-        if (DBG) {
+        if (mEnableDebugLog) {
             mSharedLog.v("Scheduling " + reply);
         }
     }
@@ -118,8 +119,8 @@
 
         @Override
         public void handleMessage(@NonNull Message msg) {
-            final ReplyInfo replyInfo = (ReplyInfo) msg.obj;
-            if (DBG) mSharedLog.v("Sending " + replyInfo);
+            final MdnsReplyInfo replyInfo = (MdnsReplyInfo) msg.obj;
+            if (mEnableDebugLog) mSharedLog.v("Sending " + replyInfo);
 
             final int flags = 0x8400; // Response, authoritative (rfc6762 18.4)
             final MdnsPacket packet = new MdnsPacket(flags,
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 01ffb03..986d58b 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -97,12 +97,14 @@
 import static android.system.OsConstants.ETH_P_ALL;
 import static android.system.OsConstants.IPPROTO_TCP;
 import static android.system.OsConstants.IPPROTO_UDP;
+
 import static com.android.net.module.util.NetworkMonitorUtils.isPrivateDnsValidationRequired;
 import static com.android.net.module.util.PermissionUtils.checkAnyPermissionOf;
 import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf;
 import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission;
 import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr;
 import static com.android.server.ConnectivityStatsLog.CONNECTIVITY_STATE_SAMPLE;
+
 import static java.util.Map.Entry;
 
 import android.Manifest;
@@ -1315,6 +1317,10 @@
             return SdkLevel.isAtLeastU();
         }
 
+        public boolean isAtLeastV() {
+            return SdkLevel.isAtLeastV();
+        }
+
         /**
          * Get system properties to use in ConnectivityService.
          */
@@ -11462,7 +11468,8 @@
         // If there is no default network, default network is considered active to keep the existing
         // behavior. Initial value is used until first connect to the default network.
         private volatile boolean mIsDefaultNetworkActive = true;
-        private final ArrayMap<String, IdleTimerParams> mActiveIdleTimers = new ArrayMap<>();
+        // Key is netId. Value is configured idle timer information.
+        private final SparseArray<IdleTimerParams> mActiveIdleTimers = new SparseArray<>();
 
         private static class IdleTimerParams {
             public final int timeout;
@@ -11490,7 +11497,7 @@
 
         public void handleReportNetworkActivity(NetworkActivityParams activityParams) {
             ensureRunningOnConnectivityServiceThread();
-            if (mActiveIdleTimers.isEmpty()) {
+            if (mActiveIdleTimers.size() == 0) {
                 // This activity change is not for the current default network.
                 // This can happen if netd callback post activity change event message but
                 // the default network is lost before processing this message.
@@ -11566,6 +11573,7 @@
          */
         private boolean setupDataActivityTracking(NetworkAgentInfo networkAgent) {
             final String iface = networkAgent.linkProperties.getInterfaceName();
+            final int netId = networkAgent.network().netId;
 
             final int timeout;
             final int type;
@@ -11590,7 +11598,7 @@
 
             if (timeout > 0 && iface != null) {
                 try {
-                    mActiveIdleTimers.put(iface, new IdleTimerParams(timeout, type));
+                    mActiveIdleTimers.put(netId, new IdleTimerParams(timeout, type));
                     mNetd.idletimerAddInterface(iface, timeout, Integer.toString(type));
                     return true;
                 } catch (Exception e) {
@@ -11606,6 +11614,7 @@
          */
         private void removeDataActivityTracking(NetworkAgentInfo networkAgent) {
             final String iface = networkAgent.linkProperties.getInterfaceName();
+            final int netId = networkAgent.network().netId;
             final NetworkCapabilities caps = networkAgent.networkCapabilities;
 
             if (iface == null) return;
@@ -11621,11 +11630,12 @@
 
             try {
                 updateRadioPowerState(false /* isActive */, type);
-                final IdleTimerParams params = mActiveIdleTimers.remove(iface);
+                final IdleTimerParams params = mActiveIdleTimers.get(netId);
                 if (params == null) {
                     // IdleTimer is not added if the configured timeout is 0 or negative value
                     return;
                 }
+                mActiveIdleTimers.remove(netId);
                 // The call fails silently if no idle timer setup for this interface
                 mNetd.idletimerRemoveInterface(iface, params.timeout,
                         Integer.toString(params.transportType));
@@ -11696,9 +11706,9 @@
             pw.print("mIsDefaultNetworkActive="); pw.println(mIsDefaultNetworkActive);
             pw.println("Idle timers:");
             try {
-                for (Map.Entry<String, IdleTimerParams> ent : mActiveIdleTimers.entrySet()) {
-                    pw.print("  "); pw.print(ent.getKey()); pw.println(":");
-                    final IdleTimerParams params = ent.getValue();
+                for (int i = 0; i < mActiveIdleTimers.size(); i++) {
+                    pw.print("  "); pw.print(mActiveIdleTimers.keyAt(i)); pw.println(":");
+                    final IdleTimerParams params = mActiveIdleTimers.valueAt(i);
                     pw.print("    timeout="); pw.print(params.timeout);
                     pw.print(" type="); pw.println(params.transportType);
                 }
diff --git a/service/src/com/android/server/connectivity/ConnectivityNativeService.java b/service/src/com/android/server/connectivity/ConnectivityNativeService.java
index c1ba40e..e16117b 100644
--- a/service/src/com/android/server/connectivity/ConnectivityNativeService.java
+++ b/service/src/com/android/server/connectivity/ConnectivityNativeService.java
@@ -46,10 +46,6 @@
 
     private static final String TAG = ConnectivityNativeService.class.getSimpleName();
     private static final String CGROUP_PATH = "/sys/fs/cgroup";
-    private static final String V4_PROG_PATH =
-            "/sys/fs/bpf/net_shared/prog_block_bind4_block_port";
-    private static final String V6_PROG_PATH =
-            "/sys/fs/bpf/net_shared/prog_block_bind6_block_port";
     private static final String BLOCKED_PORTS_MAP_PATH =
             "/sys/fs/bpf/net_shared/map_block_blocked_ports_map";
 
@@ -95,7 +91,6 @@
     protected ConnectivityNativeService(final Context context, @NonNull Dependencies deps) {
         mContext = context;
         mBpfBlockedPortsMap = deps.getBlockPortsMap();
-        attachProgram();
     }
 
     @Override
@@ -155,23 +150,4 @@
     public String getInterfaceHash() {
         return this.HASH;
     }
-
-    /**
-     * Attach BPF program
-     */
-    private void attachProgram() {
-        try {
-            BpfUtils.attachProgram(BPF_CGROUP_INET4_BIND, V4_PROG_PATH, CGROUP_PATH, 0);
-        } catch (IOException e) {
-            throw new UnsupportedOperationException("Unable to attach to BPF_CGROUP_INET4_BIND: "
-                    + e);
-        }
-        try {
-            BpfUtils.attachProgram(BPF_CGROUP_INET6_BIND, V6_PROG_PATH, CGROUP_PATH, 0);
-        } catch (IOException e) {
-            throw new UnsupportedOperationException("Unable to attach to BPF_CGROUP_INET6_BIND: "
-                    + e);
-        }
-        Log.d(TAG, "Attached BPF_CGROUP_INET4_BIND and BPF_CGROUP_INET6_BIND programs");
-    }
 }
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index bdd841f..0f72cd4 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -453,6 +453,8 @@
      * apply to the allowedUids field.
      * They also should not mutate immutable capabilities, although for backward-compatibility
      * this is not enforced and limited to just a log.
+     * Forbidden capabilities also make no sense for networks, so they are disallowed and
+     * will be ignored with a warning.
      *
      * @param carrierPrivilegeAuthenticator the authenticator, to check access UIDs.
      */
@@ -461,6 +463,7 @@
         final NetworkCapabilities nc = new NetworkCapabilities(mDeclaredCapabilitiesUnsanitized);
         if (nc.hasConnectivityManagedCapability()) {
             Log.wtf(TAG, "BUG: " + this + " has CS-managed capability.");
+            nc.removeAllForbiddenCapabilities();
         }
         if (networkCapabilities.getOwnerUid() != nc.getOwnerUid()) {
             Log.e(TAG, toShortString() + ": ignoring attempt to change owner from "
diff --git a/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java b/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java
index 59d655c..176b7bc 100644
--- a/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java
+++ b/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java
@@ -18,6 +18,8 @@
 
 import static com.android.net.module.util.NetworkStackConstants.DHCP6_OPTION_IAPREFIX;
 
+import android.util.Log;
+
 import com.android.net.module.util.Struct;
 import com.android.net.module.util.Struct.Field;
 import com.android.net.module.util.Struct.Type;
@@ -52,6 +54,7 @@
  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  */
 public class IaPrefixOption extends Struct {
+    private static final String TAG = IaPrefixOption.class.getSimpleName();
     public static final int LENGTH = 25; // option length excluding IAprefix-options
 
     @Field(order = 0, type = Type.S16)
@@ -78,6 +81,33 @@
     }
 
     /**
+     * Check whether or not IA Prefix option in IA_PD option is valid per RFC8415#section-21.22.
+     */
+    public boolean isValid(int t2) {
+        if (preferred < 0 || valid < 0) {
+            Log.w(TAG, "IA_PD option with invalid lifetime, preferred lifetime " + preferred
+                    + ", valid lifetime " + valid);
+            return false;
+        }
+        if (preferred > valid) {
+            Log.w(TAG, "IA_PD option with preferred lifetime " + preferred
+                    + " greater than valid lifetime " + valid);
+            return false;
+        }
+        if (prefixLen > 64) {
+            Log.w(TAG, "IA_PD option with prefix length " + prefixLen
+                    + " longer than 64");
+            return false;
+        }
+        // Either preferred lifetime or t2 might be 0 which is valid, then ignore it.
+        if (preferred != 0 && t2 != 0 && preferred < t2) {
+            Log.w(TAG, "preferred lifetime " + preferred + " is smaller than T2 " + t2);
+            return false;
+        }
+        return true;
+    }
+
+    /**
      * Build an IA_PD prefix option with given specific parameters.
      */
     public static ByteBuffer build(final short length, final long preferred, final long valid,
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h b/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h
index 1ae671e..81be37d 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h
@@ -170,13 +170,13 @@
 // HOPOPTS/DSTOPS follow up with 'u8 len', counting 8 byte units, (0->8, 1->16)
 // *THREE* instructions
 #define BPF3_LOAD_NETX_RELATIVE_V6EXTHDR_LEN \
-    BPF_LOAD_NETX_RELATIVE_L4_U8(1) \
-    BPF_STMT(BPF_ALU | BPF_ADD | BPF_K, 1) \
+    BPF_LOAD_NETX_RELATIVE_L4_U8(1), \
+    BPF_STMT(BPF_ALU | BPF_ADD | BPF_K, 1), \
     BPF_STMT(BPF_ALU | BPF_LSH | BPF_K, 3)
 
 // *TWO* instructions: A += X; X := A
 #define BPF2_ADD_A_TO_X \
-    BPF_STMT(BPF_ALU | BPF_ADD | BPF_X, 0) \
+    BPF_STMT(BPF_ALU | BPF_ADD | BPF_X, 0), \
     BPF_STMT(BPF_MISC | BPF_TAX, 0)
 
 // UDP/UDPLITE/TCP/SCTP/DCCP all start with be16 srcport, dstport
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
index 67ac0e4..baff09b 100644
--- a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
@@ -105,9 +105,19 @@
  * implemented in the kernel sources.
  */
 
-#define KVER_NONE 0
-#define KVER(a, b, c) (((a) << 24) + ((b) << 16) + (c))
-#define KVER_INF 0xFFFFFFFFu
+struct kver_uint { unsigned int kver; };
+#define KVER_(v) ((struct kver_uint){ .kver = (v) })
+#define KVER(a, b, c) KVER_(((a) << 24) + ((b) << 16) + (c))
+#define KVER_NONE KVER_(0)
+#define KVER_4_14 KVER(4, 14, 0)
+#define KVER_4_19 KVER(4, 19, 0)
+#define KVER_5_4 KVER(5, 4, 0)
+#define KVER_5_8 KVER(5, 8, 0)
+#define KVER_5_9 KVER(5, 9, 0)
+#define KVER_5_15 KVER(5, 15, 0)
+#define KVER_INF KVER_(0xFFFFFFFFu)
+
+#define KVER_IS_AT_LEAST(kver, a, b, c) ((kver).kver >= KVER(a, b, c).kver)
 
 /*
  * BPFFS (ie. /sys/fs/bpf) labelling is as follows:
@@ -188,10 +198,12 @@
         __attribute__ ((section(".maps." #name), used)) \
                 ____btf_map_##name = { }
 
-#define BPF_ASSERT_LOADER_VERSION(min_loader, ignore_eng, ignore_user, ignore_userdebug)  \
-    _Static_assert(                                                                       \
-        (min_loader) >= BPFLOADER_IGNORED_ON_VERSION ||                                   \
-            !((ignore_eng) || (ignore_user) || (ignore_userdebug)),                       \
+#define BPF_ASSERT_LOADER_VERSION(min_loader, ignore_eng, ignore_user, ignore_userdebug) \
+    _Static_assert(                                                                      \
+        (min_loader) >= BPFLOADER_IGNORED_ON_VERSION ||                                  \
+            !((ignore_eng).ignore_on_eng ||                                              \
+              (ignore_user).ignore_on_user ||                                            \
+              (ignore_userdebug).ignore_on_userdebug),                                   \
         "bpfloader min version must be >= 0.33 in order to use ignored_on");
 
 #define DEFINE_BPF_MAP_BASE(the_map, TYPE, keysize, valuesize, num_entries, \
@@ -209,14 +221,14 @@
         .mode = (md),                                                       \
         .bpfloader_min_ver = (minloader),                                   \
         .bpfloader_max_ver = (maxloader),                                   \
-        .min_kver = (minkver),                                              \
-        .max_kver = (maxkver),                                              \
+        .min_kver = (minkver).kver,                                         \
+        .max_kver = (maxkver).kver,                                         \
         .selinux_context = (selinux),                                       \
         .pin_subdir = (pindir),                                             \
-        .shared = (share),                                                  \
-        .ignore_on_eng = (ignore_eng),                                      \
-        .ignore_on_user = (ignore_user),                                    \
-        .ignore_on_userdebug = (ignore_userdebug),                          \
+        .shared = (share).shared,                                           \
+        .ignore_on_eng = (ignore_eng).ignore_on_eng,                        \
+        .ignore_on_user = (ignore_user).ignore_on_user,                     \
+        .ignore_on_userdebug = (ignore_userdebug).ignore_on_userdebug,      \
     };                                                                      \
     BPF_ASSERT_LOADER_VERSION(minloader, ignore_eng, ignore_user, ignore_userdebug);
 
@@ -230,7 +242,7 @@
                                selinux, pindir, share, min_loader, max_loader, \
                                ignore_eng, ignore_user, ignore_userdebug)      \
     DEFINE_BPF_MAP_BASE(the_map, RINGBUF, 0, 0, size_bytes, usr, grp, md,      \
-                        selinux, pindir, share, KVER(5, 8, 0), KVER_INF,       \
+                        selinux, pindir, share, KVER_5_8, KVER_INF,            \
                         min_loader, max_loader, ignore_eng, ignore_user,       \
                         ignore_userdebug);                                     \
                                                                                \
@@ -312,11 +324,11 @@
 #error "Bpf Map UID must be left at default of AID_ROOT for BpfLoader prior to v0.28"
 #endif
 
-#define DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, usr, grp, md)   \
-    DEFINE_BPF_MAP_EXT(the_map, TYPE, KeyType, ValueType, num_entries, usr, grp, md,       \
-                       DEFAULT_BPF_MAP_SELINUX_CONTEXT, DEFAULT_BPF_MAP_PIN_SUBDIR, false, \
-                       BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, /*ignore_on_eng*/false,       \
-                       /*ignore_on_user*/false, /*ignore_on_userdebug*/false)
+#define DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, usr, grp, md)     \
+    DEFINE_BPF_MAP_EXT(the_map, TYPE, KeyType, ValueType, num_entries, usr, grp, md,         \
+                       DEFAULT_BPF_MAP_SELINUX_CONTEXT, DEFAULT_BPF_MAP_PIN_SUBDIR, PRIVATE, \
+                       BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, LOAD_ON_ENG,                    \
+                       LOAD_ON_USER, LOAD_ON_USERDEBUG)
 
 #define DEFINE_BPF_MAP(the_map, TYPE, KeyType, ValueType, num_entries) \
     DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, \
@@ -362,16 +374,16 @@
     const struct bpf_prog_def SECTION("progs") the_prog##_def = {                        \
         .uid = (prog_uid),                                                               \
         .gid = (prog_gid),                                                               \
-        .min_kver = (min_kv),                                                            \
-        .max_kver = (max_kv),                                                            \
-        .optional = (opt),                                                               \
+        .min_kver = (min_kv).kver,                                                       \
+        .max_kver = (max_kv).kver,                                                       \
+        .optional = (opt).optional,                                                      \
         .bpfloader_min_ver = (min_loader),                                               \
         .bpfloader_max_ver = (max_loader),                                               \
         .selinux_context = (selinux),                                                    \
         .pin_subdir = (pindir),                                                          \
-        .ignore_on_eng = (ignore_eng),                                                   \
-        .ignore_on_user = (ignore_user),                                                 \
-        .ignore_on_userdebug = (ignore_userdebug),                                       \
+        .ignore_on_eng = (ignore_eng).ignore_on_eng,                                     \
+        .ignore_on_user = (ignore_user).ignore_on_user,                                  \
+        .ignore_on_userdebug = (ignore_userdebug).ignore_on_userdebug,                   \
     };                                                                                   \
     SECTION(SECTION_NAME)                                                                \
     int the_prog
@@ -389,7 +401,7 @@
     DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv,                \
                         BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, opt,                                 \
                         DEFAULT_BPF_PROG_SELINUX_CONTEXT, DEFAULT_BPF_PROG_PIN_SUBDIR,             \
-                        false, false, false)
+                        LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG)
 
 // Programs (here used in the sense of functions/sections) marked optional are allowed to fail
 // to load (for example due to missing kernel patches).
@@ -405,21 +417,24 @@
 // programs requiring a kernel version >= min_kv && < max_kv
 #define DEFINE_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv) \
     DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, \
-                                   false)
+                                   MANDATORY)
 #define DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, \
                                             max_kv)                                             \
-    DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, true)
+    DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, \
+                                   OPTIONAL)
 
 // programs requiring a kernel version >= min_kv
 #define DEFINE_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv)                 \
     DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, KVER_INF, \
-                                   false)
+                                   MANDATORY)
 #define DEFINE_OPTIONAL_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv)        \
     DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, KVER_INF, \
-                                   true)
+                                   OPTIONAL)
 
 // programs with no kernel version requirements
 #define DEFINE_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \
-    DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, 0, KVER_INF, false)
+    DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, KVER_NONE, KVER_INF, \
+                                   MANDATORY)
 #define DEFINE_OPTIONAL_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \
-    DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, 0, KVER_INF, true)
+    DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, KVER_NONE, KVER_INF, \
+                                   OPTIONAL)
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h b/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
index e7428b6..ef03c4d 100644
--- a/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
@@ -114,6 +114,31 @@
 // BPF wants 8, but 32-bit x86 wants 4
 //_Static_assert(_Alignof(unsigned long long) == 8, "_Alignof unsigned long long != 8");
 
+
+// for maps:
+struct shared_bool { bool shared; };
+#define PRIVATE ((struct shared_bool){ .shared = false })
+#define SHARED ((struct shared_bool){ .shared = true })
+
+// for programs:
+struct optional_bool { bool optional; };
+#define MANDATORY ((struct optional_bool){ .optional = false })
+#define OPTIONAL ((struct optional_bool){ .optional = true })
+
+// for both maps and programs:
+struct ignore_on_eng_bool { bool ignore_on_eng; };
+#define LOAD_ON_ENG ((struct ignore_on_eng_bool){ .ignore_on_eng = false })
+#define IGNORE_ON_ENG ((struct ignore_on_eng_bool){ .ignore_on_eng = true })
+
+struct ignore_on_user_bool { bool ignore_on_user; };
+#define LOAD_ON_USER ((struct ignore_on_user_bool){ .ignore_on_user = false })
+#define IGNORE_ON_USER ((struct ignore_on_user_bool){ .ignore_on_user = true })
+
+struct ignore_on_userdebug_bool { bool ignore_on_userdebug; };
+#define LOAD_ON_USERDEBUG ((struct ignore_on_userdebug_bool){ .ignore_on_userdebug = false })
+#define IGNORE_ON_USERDEBUG ((struct ignore_on_userdebug_bool){ .ignore_on_userdebug = true })
+
+
 // Length of strings (incl. selinux_context and pin_subdir)
 // in the bpf_map_def and bpf_prog_def structs.
 //
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
index 35f22b9..46229b0 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
@@ -27,6 +27,9 @@
 
 @Deprecated("Use Build.VERSION_CODES", ReplaceWith("Build.VERSION_CODES.S_V2"))
 const val SC_V2 = Build.VERSION_CODES.S_V2
+// TODO: Remove this when Build.VERSION_CODES.VANILLA_ICE_CREAM is available in all branches
+// where this code builds
+const val VANILLA_ICE_CREAM = 35 // Bui1ld.VERSION_CODES.VANILLA_ICE_CREAM
 
 private val MAX_TARGET_SDK_ANNOTATION_RE = Pattern.compile("MaxTargetSdk([0-9]+)$")
 private val targetSdk = InstrumentationRegistry.getContext().applicationInfo.targetSdkVersion
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index aae3425..bec9a4a 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -26,6 +26,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
@@ -63,6 +64,7 @@
 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.modules.utils.build.SdkLevel.isAtLeastV;
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 import static com.android.testutils.MiscAsserts.assertEmpty;
 import static com.android.testutils.MiscAsserts.assertThrows;
@@ -369,6 +371,9 @@
             .addCapability(NET_CAPABILITY_INTERNET)
             .addCapability(NET_CAPABILITY_EIMS)
             .addCapability(NET_CAPABILITY_NOT_METERED);
+        if (isAtLeastV()) {
+            netCap.addCapability(NET_CAPABILITY_LOCAL_NETWORK);
+        }
         if (isAtLeastS()) {
             final ArraySet<Integer> allowedUids = new ArraySet<>();
             allowedUids.add(4);
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 6de663a..b86de25 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -55,7 +55,7 @@
         "junit-params",
         "modules-utils-build",
         "net-utils-framework-common",
-        "truth-prebuilt",
+        "truth",
         "TetheringIntegrationTestsBaseLib",
     ],
 
@@ -74,7 +74,10 @@
 // devices.
 android_test {
     name: "CtsNetTestCases",
-    defaults: ["CtsNetTestCasesDefaults", "ConnectivityNextEnableDefaults"],
+    defaults: [
+        "CtsNetTestCasesDefaults",
+        "ConnectivityNextEnableDefaults",
+    ],
     static_libs: [
         "DhcpPacketLib",
         "NetworkStackApiCurrentShims",
@@ -129,7 +132,7 @@
 }
 
 android_test {
-    name: "CtsNetTestCasesMaxTargetSdk33",  // Must match CtsNetTestCasesMaxTargetSdk33 annotation.
+    name: "CtsNetTestCasesMaxTargetSdk33", // Must match CtsNetTestCasesMaxTargetSdk33 annotation.
     defaults: ["CtsNetTestCasesMaxTargetSdkDefaults"],
     target_sdk_version: "33",
     package_name: "android.net.cts.maxtargetsdk33",
@@ -137,17 +140,17 @@
 }
 
 android_test {
-    name: "CtsNetTestCasesMaxTargetSdk31",  // Must match CtsNetTestCasesMaxTargetSdk31 annotation.
+    name: "CtsNetTestCasesMaxTargetSdk31", // Must match CtsNetTestCasesMaxTargetSdk31 annotation.
     defaults: ["CtsNetTestCasesMaxTargetSdkDefaults"],
     target_sdk_version: "31",
-    package_name: "android.net.cts.maxtargetsdk31",  // CTS package names must be unique.
+    package_name: "android.net.cts.maxtargetsdk31", // CTS package names must be unique.
     instrumentation_target_package: "android.net.cts.maxtargetsdk31",
 }
 
 android_test {
-    name: "CtsNetTestCasesMaxTargetSdk30",  // Must match CtsNetTestCasesMaxTargetSdk30 annotation.
+    name: "CtsNetTestCasesMaxTargetSdk30", // Must match CtsNetTestCasesMaxTargetSdk30 annotation.
     defaults: ["CtsNetTestCasesMaxTargetSdkDefaults"],
     target_sdk_version: "30",
-    package_name: "android.net.cts.maxtargetsdk30",  // CTS package names must be unique.
+    package_name: "android.net.cts.maxtargetsdk30", // CTS package names must be unique.
     instrumentation_target_package: "android.net.cts.maxtargetsdk30",
 }
diff --git a/tests/cts/net/api23Test/Android.bp b/tests/cts/net/api23Test/Android.bp
index 9b81a56..1f1dd5d 100644
--- a/tests/cts/net/api23Test/Android.bp
+++ b/tests/cts/net/api23Test/Android.bp
@@ -41,7 +41,7 @@
         "mockwebserver",
         "junit",
         "junit-params",
-        "truth-prebuilt",
+        "truth",
     ],
 
     platform_apis: true,
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index 637ed26..594f3fb 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -19,8 +19,10 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
 import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
@@ -28,6 +30,8 @@
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
+import static com.android.testutils.DevSdkIgnoreRuleKt.VANILLA_ICE_CREAM;
+
 import static junit.framework.Assert.fail;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -104,6 +108,23 @@
         verifyNoCapabilities(nr);
     }
 
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testForbiddenCapabilities() {
+        final NetworkRequest.Builder builder = new NetworkRequest.Builder();
+        builder.addForbiddenCapability(NET_CAPABILITY_MMS);
+        assertTrue(builder.build().hasForbiddenCapability(NET_CAPABILITY_MMS));
+        builder.removeForbiddenCapability(NET_CAPABILITY_MMS);
+        assertFalse(builder.build().hasCapability(NET_CAPABILITY_MMS));
+        builder.addCapability(NET_CAPABILITY_MMS);
+        assertFalse(builder.build().hasForbiddenCapability(NET_CAPABILITY_MMS));
+        assertTrue(builder.build().hasCapability(NET_CAPABILITY_MMS));
+        builder.addForbiddenCapability(NET_CAPABILITY_MMS);
+        assertTrue(builder.build().hasForbiddenCapability(NET_CAPABILITY_MMS));
+        assertFalse(builder.build().hasCapability(NET_CAPABILITY_MMS));
+        builder.clearCapabilities();
+        verifyNoCapabilities(builder.build());
+    }
+
     @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
     public void testTemporarilyNotMeteredCapability() {
         assertTrue(new NetworkRequest.Builder()
@@ -472,6 +493,32 @@
         assertArrayEquals(netCapabilities, nr.getCapabilities());
     }
 
+    @Test @IgnoreUpTo(VANILLA_ICE_CREAM)
+    public void testDefaultCapabilities() {
+        final NetworkRequest defaultNR = new NetworkRequest.Builder().build();
+        assertTrue(defaultNR.hasForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK));
+        assertFalse(defaultNR.hasCapability(NET_CAPABILITY_LOCAL_NETWORK));
+        assertTrue(defaultNR.hasCapability(NET_CAPABILITY_NOT_VPN));
+
+        final NetworkCapabilities emptyNC =
+                NetworkCapabilities.Builder.withoutDefaultCapabilities().build();
+        assertFalse(defaultNR.canBeSatisfiedBy(emptyNC));
+
+        // defaultNC represent the capabilities of a network agent, so they must not contain
+        // forbidden capabilities by default.
+        final NetworkCapabilities defaultNC = new NetworkCapabilities.Builder().build();
+        assertArrayEquals(new int[0], defaultNC.getForbiddenCapabilities());
+        // A default NR can be satisfied by default NC.
+        assertTrue(defaultNR.canBeSatisfiedBy(defaultNC));
+
+        // Conversely, network requests have forbidden capabilities by default to manage
+        // backward compatibility, so test that these forbidden capabilities are in place.
+        // Starting in V, NET_CAPABILITY_LOCAL_NETWORK is introduced but is not seen by
+        // default, thanks to a default forbidden capability in NetworkRequest.
+        defaultNC.addCapability(NET_CAPABILITY_LOCAL_NETWORK);
+        assertFalse(defaultNR.canBeSatisfiedBy(defaultNC));
+    }
+
     @Test
     public void testBuildRequestFromExistingRequestWithBuilder() {
         assumeTrue(TestUtils.shouldTestSApis());
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index 0c424e9..cff4d6f 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -40,6 +40,7 @@
 #define TETHERING "/sys/fs/bpf/tethering/"
 #define PRIVATE "/sys/fs/bpf/net_private/"
 #define SHARED "/sys/fs/bpf/net_shared/"
+#define NETD_RO "/sys/fs/bpf/netd_readonly/"
 #define NETD "/sys/fs/bpf/netd_shared/"
 
 class BpfExistenceTest : public ::testing::Test {
@@ -119,9 +120,9 @@
 };
 
 // Provided by *current* mainline module for T+ devices with 5.4+ kernels
-static const set<string> MAINLINE_FOR_T_5_4_PLUS = {
-    SHARED "prog_block_bind4_block_port",
-    SHARED "prog_block_bind6_block_port",
+static const set<string> MAINLINE_FOR_T_4_19_PLUS = {
+    NETD_RO "prog_block_bind4_block_port",
+    NETD_RO "prog_block_bind6_block_port",
 };
 
 // Provided by *current* mainline module for T+ devices with 5.15+ kernels
@@ -176,7 +177,7 @@
     // T still only requires Linux Kernel 4.9+.
     DO_EXPECT(IsAtLeastT(), MAINLINE_FOR_T_PLUS);
     DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(4, 14, 0), MAINLINE_FOR_T_4_14_PLUS);
-    DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(5, 4, 0), MAINLINE_FOR_T_5_4_PLUS);
+    DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(4, 19, 0), MAINLINE_FOR_T_4_19_PLUS);
     DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(5, 15, 0), MAINLINE_FOR_T_5_15_PLUS);
 
     // U requires Linux Kernel 4.14+, but nothing (as yet) added or removed in U.
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index eb03157..16f0c44 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -418,7 +418,6 @@
 import com.android.server.connectivity.UidRangeUtils;
 import com.android.server.connectivity.Vpn;
 import com.android.server.connectivity.VpnProfileStore;
-import com.android.server.net.LockdownVpnTracker;
 import com.android.server.net.NetworkPinner;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
@@ -1500,14 +1499,13 @@
         private int mVpnType = VpnManager.TYPE_VPN_SERVICE;
         private UnderlyingNetworkInfo mUnderlyingNetworkInfo;
 
-        // These ConditionVariables allow tests to wait for LegacyVpnRunner to be stopped/started.
+        // This ConditionVariable allow tests to wait for LegacyVpnRunner to be started.
         // TODO: this scheme is ad-hoc and error-prone because it does not fail if, for example, the
         // test expects two starts in a row, or even if the production code calls start twice in a
         // row. find a better solution. Simply putting a method to create a LegacyVpnRunner into
         // Vpn.Dependencies doesn't work because LegacyVpnRunner is not a static class and has
         // extensive access into the internals of Vpn.
         private ConditionVariable mStartLegacyVpnCv = new ConditionVariable();
-        private ConditionVariable mStopVpnRunnerCv = new ConditionVariable();
 
         public MockVpn(int userId) {
             super(startHandlerThreadAndReturnLooper(), mServiceContext,
@@ -1676,12 +1674,6 @@
         public void expectStartLegacyVpnRunner() {
             assertTrue("startLegacyVpnRunner not called after " + TIMEOUT_MS + " ms",
                     mStartLegacyVpnCv.block(TIMEOUT_MS));
-
-            // startLegacyVpn calls stopVpnRunnerPrivileged, which will open mStopVpnRunnerCv, just
-            // before calling startLegacyVpnRunner. Restore mStopVpnRunnerCv, so the test can expect
-            // that the VpnRunner is stopped and immediately restarted by calling
-            // expectStartLegacyVpnRunner() and expectStopVpnRunnerPrivileged() back-to-back.
-            mStopVpnRunnerCv = new ConditionVariable();
         }
 
         @Override
@@ -1692,12 +1684,6 @@
                 mStartLegacyVpnCv = new ConditionVariable();
             }
             mVpnRunner = null;
-            mStopVpnRunnerCv.open();
-        }
-
-        public void expectStopVpnRunnerPrivileged() {
-            assertTrue("stopVpnRunnerPrivileged not called after " + TIMEOUT_MS + " ms",
-                    mStopVpnRunnerCv.block(TIMEOUT_MS));
         }
 
         @Override
@@ -10238,74 +10224,28 @@
         // Pretend lockdown VPN was configured.
         final VpnProfile profile = setupLegacyLockdownVpn();
 
-        // LockdownVpnTracker disables the Vpn teardown code and enables lockdown.
-        // Check the VPN's state before it does so.
-        assertTrue(mMockVpn.getEnableTeardown());
-        assertFalse(mMockVpn.getLockdown());
-
-        // VMSHandlerThread was used inside VpnManagerService and taken into LockDownVpnTracker.
-        // VpnManagerService was decoupled from this test but this handlerThread is still required
-        // in LockDownVpnTracker. Keep it until LockDownVpnTracker related verification is moved to
-        // its own test.
-        final HandlerThread VMSHandlerThread = new HandlerThread("TestVpnManagerService");
-        VMSHandlerThread.start();
-
-        // LockdownVpnTracker is created from VpnManagerService but VpnManagerService is decoupled
-        // from ConnectivityServiceTest. Create it directly to simulate LockdownVpnTracker is
-        // created.
-        // TODO: move LockdownVpnTracker related tests to its own test.
-        // Lockdown VPN disables teardown and enables lockdown.
-        final LockdownVpnTracker lockdownVpnTracker = new LockdownVpnTracker(mServiceContext,
-                VMSHandlerThread.getThreadHandler(), mMockVpn, profile);
-        lockdownVpnTracker.init();
-        assertFalse(mMockVpn.getEnableTeardown());
-        assertTrue(mMockVpn.getLockdown());
+        // Init lockdown state to simulate LockdownVpnTracker behavior.
+        mCm.setLegacyLockdownVpnEnabled(true);
+        mMockVpn.setEnableTeardown(false);
+        mMockVpn.setLockdown(true);
 
         // Bring up a network.
-        // Expect nothing to happen because the network does not have an IPv4 default route: legacy
-        // VPN only supports IPv4.
         final LinkProperties cellLp = new LinkProperties();
         cellLp.setInterfaceName("rmnet0");
-        cellLp.addLinkAddress(new LinkAddress("2001:db8::1/64"));
-        cellLp.addRoute(new RouteInfo(new IpPrefix("::/0"), null, "rmnet0"));
-        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
-        mCellAgent.connect(false /* validated */);
-        callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellAgent);
-        defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellAgent);
-        systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellAgent);
-        waitForIdle();
-        assertNull(mMockVpn.getAgent());
-
-        // Add an IPv4 address. Ideally the VPN should start, but it doesn't because nothing calls
-        // LockdownVpnTracker#handleStateChangedLocked. This is a bug.
-        // TODO: consider fixing this.
         cellLp.addLinkAddress(new LinkAddress("192.0.2.2/25"));
         cellLp.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0"), null, "rmnet0"));
-        mCellAgent.sendLinkProperties(cellLp);
-        callback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
-        defaultCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
-        systemDefaultCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
-        waitForIdle();
-        assertNull(mMockVpn.getAgent());
-
-        // Disconnect, then try again with a network that supports IPv4 at connection time.
-        // Expect lockdown VPN to come up.
-        ExpectedBroadcast b1 = expectConnectivityAction(TYPE_MOBILE, DetailedState.DISCONNECTED);
-        mCellAgent.disconnect();
-        callback.expect(LOST, mCellAgent);
-        defaultCallback.expect(LOST, mCellAgent);
-        systemDefaultCallback.expect(LOST, mCellAgent);
-        b1.expectBroadcast();
-
         // When lockdown VPN is active, the NetworkInfo state in CONNECTIVITY_ACTION is overwritten
         // with the state of the VPN network. So expect a CONNECTING broadcast.
-        b1 = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTING);
+        ExpectedBroadcast b1 = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTING);
         mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
         mCellAgent.connect(false /* validated */);
         callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellAgent);
         defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellAgent);
         systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellAgent);
         b1.expectBroadcast();
+        // Simulate LockdownVpnTracker attempting to start the VPN since it received the
+        // systemDefault callback.
+        mMockVpn.startLegacyVpnPrivileged(profile, mCellAgent.getNetwork(), cellLp);
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
         assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
@@ -10359,23 +10299,25 @@
         b1 = expectConnectivityAction(TYPE_MOBILE, DetailedState.DISCONNECTED);
         // Wifi is CONNECTING because the VPN isn't up yet.
         b2 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTING);
-        ExpectedBroadcast b3 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED);
         mWiFiAgent.connect(false /* validated */);
+        // Wifi is not blocked since VPN network is still connected.
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        defaultCallback.assertNoCallback();
+        systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
         b1.expectBroadcast();
         b2.expectBroadcast();
-        b3.expectBroadcast();
-        mMockVpn.expectStopVpnRunnerPrivileged();
-        mMockVpn.expectStartLegacyVpnRunner();
 
-        // TODO: why is wifi not blocked? Is it because when this callback is sent, the VPN is still
-        // connected, so the network is not considered blocked by the lockdown UID ranges? But the
-        // fact that a VPN is connected should only result in the VPN itself being unblocked, not
-        // any other network. Bug in isUidBlockedByVpn?
-        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        // Simulate LockdownVpnTracker restarting the VPN since it received the systemDefault
+        // callback with different network.
+        final ExpectedBroadcast b3 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED);
+        mMockVpn.stopVpnRunnerPrivileged();
+        mMockVpn.startLegacyVpnPrivileged(profile, mWiFiAgent.getNetwork(), wifiLp);
+        mMockVpn.expectStartLegacyVpnRunner();
         callback.expect(LOST, mMockVpn);
         defaultCallback.expect(LOST, mMockVpn);
         defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiAgent);
-        systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        systemDefaultCallback.assertNoCallback();
+        b3.expectBroadcast();
 
         // While the VPN is reconnecting on the new network, everything is blocked.
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
@@ -10420,15 +10362,22 @@
         b2 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED);
         mWiFiAgent.disconnect();
         callback.expect(LOST, mWiFiAgent);
+        callback.expectCaps(mMockVpn, c -> !c.hasTransport(TRANSPORT_WIFI));
+        defaultCallback.expectCaps(mMockVpn, c -> !c.hasTransport(TRANSPORT_WIFI));
+        systemDefaultCallback.expect(LOST, mWiFiAgent);
+        // TODO: There should only be one LOST callback. Since the WIFI network is underlying a VPN
+        // network, ConnectivityService#propagateUnderlyingNetworkCapabilities() causes a rematch to
+        // occur. Notably, this happens before setting the satisfiers of its network requests to
+        // null. Since the satisfiers are set to null in the rematch, an extra LOST callback is
+        // called.
         systemDefaultCallback.expect(LOST, mWiFiAgent);
         b1.expectBroadcast();
-        callback.expectCaps(mMockVpn, c -> !c.hasTransport(TRANSPORT_WIFI));
-        mMockVpn.expectStopVpnRunnerPrivileged();
+        mMockVpn.stopVpnRunnerPrivileged();
         callback.expect(LOST, mMockVpn);
+        defaultCallback.expect(LOST, mMockVpn);
         b2.expectBroadcast();
 
-        VMSHandlerThread.quitSafely();
-        VMSHandlerThread.join();
+        assertNoCallbacks(callback, defaultCallback, systemDefaultCallback);
     }
 
     @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 56346ad..d674767 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -1839,6 +1839,22 @@
         // a subsequent CL.
     }
 
+    @Test
+    public void testStartLegacyVpnIpv6() throws Exception {
+        setMockedUsers(PRIMARY_USER);
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+        final LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName(EGRESS_IFACE);
+        lp.addLinkAddress(new LinkAddress("2001:db8::1/64"));
+        final RouteInfo defaultRoute = new RouteInfo(
+                new IpPrefix(Inet6Address.ANY, 0), null, EGRESS_IFACE);
+        lp.addRoute(defaultRoute);
+
+        // IllegalStateException thrown since legacy VPN only supports IPv4.
+        assertThrows(IllegalStateException.class,
+                () -> vpn.startLegacyVpn(mVpnProfile, EGRESS_NETWORK, lp));
+    }
+
     private Vpn startLegacyVpn(final Vpn vpn, final VpnProfile vpnProfile) throws Exception {
         setMockedUsers(PRIMARY_USER);
 
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
index c39ee1e..2797462 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
@@ -82,7 +82,8 @@
 
     @Test
     fun testAnnounce() {
-        val replySender = MdnsReplySender( thread.looper, socket, buffer, sharedLog)
+        val replySender = MdnsReplySender(
+                thread.looper, socket, buffer, sharedLog, true /* enableDebugLog */)
         @Suppress("UNCHECKED_CAST")
         val cb = mock(MdnsPacketRepeater.PacketRepeaterCallback::class.java)
                 as MdnsPacketRepeater.PacketRepeaterCallback<BaseAnnouncementInfo>
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
index c19747e..a67dc5e 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
@@ -190,8 +190,8 @@
     fun testReplyToQuery() {
         addServiceAndFinishProbing(TEST_SERVICE_ID_1, TEST_SERVICE_1)
 
-        val mockReply = mock(MdnsRecordRepository.ReplyInfo::class.java)
-        doReturn(mockReply).`when`(repository).getReply(any(), any())
+        val testReply = MdnsReplyInfo(emptyList(), emptyList(), 0, InetSocketAddress(0))
+        doReturn(testReply).`when`(repository).getReply(any(), any())
 
         // Query obtained with:
         // scapy.raw(scapy.DNS(
@@ -216,7 +216,7 @@
             assertContentEquals(arrayOf("_testservice", "_tcp", "local"), it.questions[0].name)
         }
 
-        verify(replySender).queueReply(mockReply)
+        verify(replySender).queueReply(testReply)
     }
 
     @Test
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
index 3701b0c..8917ed3 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
@@ -42,6 +42,7 @@
 import com.android.testutils.DevSdkIgnoreRunner;
 import com.android.testutils.HandlerUtils;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -69,6 +70,7 @@
     @Mock private SocketCreationCallback mSocketCreationCallback;
     @Mock private SharedLog mSharedLog;
     private MdnsMultinetworkSocketClient mSocketClient;
+    private HandlerThread mHandlerThread;
     private Handler mHandler;
     private SocketKey mSocketKey;
 
@@ -76,14 +78,23 @@
     public void setUp() throws SocketException {
         MockitoAnnotations.initMocks(this);
 
-        final HandlerThread thread = new HandlerThread("MdnsMultinetworkSocketClientTest");
-        thread.start();
-        mHandler = new Handler(thread.getLooper());
+        mHandlerThread = new HandlerThread("MdnsMultinetworkSocketClientTest");
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
         mSocketKey = new SocketKey(1000 /* interfaceIndex */);
-        mSocketClient = new MdnsMultinetworkSocketClient(thread.getLooper(), mProvider, mSharedLog);
+        mSocketClient = new MdnsMultinetworkSocketClient(
+                mHandlerThread.getLooper(), mProvider, mSharedLog);
         mHandler.post(() -> mSocketClient.setCallback(mCallback));
     }
 
+    @After
+    public void tearDown() throws Exception {
+        if (mHandlerThread != null) {
+            mHandlerThread.quitSafely();
+            mHandlerThread.join();
+        }
+    }
+
     private SocketCallback expectSocketCallback() {
         return expectSocketCallback(mListener, mNetwork);
     }
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
index f284819..5b7c0ba 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
@@ -119,7 +119,8 @@
 
     @Test
     fun testProbe() {
-        val replySender = MdnsReplySender(thread.looper, socket, buffer, sharedLog)
+        val replySender = MdnsReplySender(
+                thread.looper, socket, buffer, sharedLog, true /* enableDebugLog */)
         val prober = TestProber(thread.looper, replySender, cb, sharedLog)
         val probeInfo = TestProbeInfo(
                 listOf(makeServiceRecord(TEST_SERVICE_NAME_1, 37890)))
@@ -143,7 +144,8 @@
 
     @Test
     fun testProbeMultipleRecords() {
-        val replySender = MdnsReplySender(thread.looper, socket, buffer, sharedLog)
+        val replySender = MdnsReplySender(
+                thread.looper, socket, buffer, sharedLog, true /* enableDebugLog */)
         val prober = TestProber(thread.looper, replySender, cb, sharedLog)
         val probeInfo = TestProbeInfo(listOf(
                 makeServiceRecord(TEST_SERVICE_NAME_1, 37890),
@@ -181,7 +183,8 @@
 
     @Test
     fun testStopProbing() {
-        val replySender = MdnsReplySender(thread.looper, socket, buffer, sharedLog)
+        val replySender = MdnsReplySender(
+                thread.looper, socket, buffer, sharedLog, true /* enableDebugLog */)
         val prober = TestProber(thread.looper, replySender, cb, sharedLog)
         val probeInfo = TestProbeInfo(
                 listOf(makeServiceRecord(TEST_SERVICE_NAME_1, 37890)),
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSBasicMethodsTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSBasicMethodsTest.kt
index 6f8ba6c..58f20a9 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSBasicMethodsTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSBasicMethodsTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 @file:Suppress("DEPRECATION") // This file tests a bunch of deprecated methods : don't warn about it
 
 package com.android.server
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt
new file mode 100644
index 0000000..86426c2
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 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.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.net.NetworkScore
+import android.net.NetworkScore.KEEP_CONNECTED_DOWNSTREAM_NETWORK
+import android.net.NetworkScore.KEEP_CONNECTED_FOR_TEST
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry.Lost
+import com.android.testutils.TestableNetworkCallback
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+class CSKeepConnectedTest : CSTest() {
+    @Test
+    fun testKeepConnectedLocalAgent() {
+        deps.setBuildSdk(VERSION_V)
+        val nc = NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_WIFI)
+                .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+                .build()
+        val keepConnectedAgent = Agent(nc = nc, score = FromS(NetworkScore.Builder()
+                .setKeepConnectedReason(KEEP_CONNECTED_DOWNSTREAM_NETWORK)
+                .build()))
+        val dontKeepConnectedAgent = Agent(nc = nc)
+        doTestKeepConnected(keepConnectedAgent, dontKeepConnectedAgent)
+    }
+
+    @Test
+    fun testKeepConnectedForTest() {
+        val keepAgent = Agent(score = FromS(NetworkScore.Builder()
+                .setKeepConnectedReason(KEEP_CONNECTED_FOR_TEST)
+                .build()))
+        val dontKeepAgent = Agent()
+        doTestKeepConnected(keepAgent, dontKeepAgent)
+    }
+
+    fun doTestKeepConnected(keepAgent: CSAgentWrapper, dontKeepAgent: CSAgentWrapper) {
+        val cb = TestableNetworkCallback()
+        cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities().build(), cb)
+
+        keepAgent.connect()
+        dontKeepAgent.connect()
+
+        cb.expectAvailableCallbacks(keepAgent.network, validated = false)
+        cb.expectAvailableCallbacks(dontKeepAgent.network, validated = false)
+
+        // After the nascent timer, the agent without keep connected gets lost.
+        cb.expect<Lost>(dontKeepAgent.network)
+        cb.assertNoCallback()
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
index a5a1aeb..1d0d5df 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 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.content.Context
@@ -19,6 +35,7 @@
 import android.os.HandlerThread
 import com.android.modules.utils.build.SdkLevel
 import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.Lost
 import com.android.testutils.TestableNetworkCallback
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.any
@@ -31,6 +48,8 @@
 import kotlin.test.assertEquals
 import kotlin.test.fail
 
+const val SHORT_TIMEOUT_MS = 200L
+
 private inline fun <reified T> ArgumentCaptor() = ArgumentCaptor.forClass(T::class.java)
 
 private val agentCounter = AtomicInteger(1)
@@ -44,6 +63,7 @@
  */
 class CSAgentWrapper(
         val context: Context,
+        val deps: ConnectivityService.Dependencies,
         csHandlerThread: HandlerThread,
         networkStack: NetworkStackClientBase,
         nac: NetworkAgentConfig,
@@ -78,7 +98,7 @@
                 nmCbCaptor.capture())
 
         // Create the actual agent. NetworkAgent is abstract, so make an anonymous subclass.
-        if (SdkLevel.isAtLeastS()) {
+        if (deps.isAtLeastS()) {
             agent = object : NetworkAgent(context, csHandlerThread.looper, TAG,
                     nc, lp, score.value, nac, provider) {}
         } else {
@@ -92,7 +112,7 @@
     }
 
     private fun onValidationRequested() {
-        if (SdkLevel.isAtLeastT()) {
+        if (deps.isAtLeastT()) {
             verify(networkMonitor).notifyNetworkConnectedParcel(any())
         } else {
             verify(networkMonitor).notifyNetworkConnected(any(), any())
@@ -109,9 +129,10 @@
 
     fun connect() {
         val mgr = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
-        val request = NetworkRequest.Builder().clearCapabilities()
-                .addTransportType(nc.transportTypes[0])
-                .build()
+        val request = NetworkRequest.Builder().apply {
+            clearCapabilities()
+            if (nc.transportTypes.isNotEmpty()) addTransportType(nc.transportTypes[0])
+        }.build()
         val cb = TestableNetworkCallback()
         mgr.registerNetworkCallback(request, cb)
         agent.markConnected()
@@ -133,6 +154,15 @@
     }
 
     fun disconnect() {
+        val mgr = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+        val request = NetworkRequest.Builder().apply {
+            clearCapabilities()
+            if (nc.transportTypes.isNotEmpty()) addTransportType(nc.transportTypes[0])
+        }.build()
+        val cb = TestableNetworkCallback(timeoutMs = SHORT_TIMEOUT_MS)
+        mgr.registerNetworkCallback(request, cb)
+        cb.eventuallyExpect<Available> { it.network == agent.network }
         agent.unregister()
+        cb.eventuallyExpect<Lost> { it.network == agent.network }
     }
 }
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
index b11878d..2f78212 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 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.content.BroadcastReceiver
@@ -10,19 +26,20 @@
 import android.net.ConnectivityManager
 import android.net.INetd
 import android.net.InetAddresses
-import android.net.IpPrefix
-import android.net.LinkAddress
 import android.net.LinkProperties
 import android.net.NetworkAgentConfig
 import android.net.NetworkCapabilities
-import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
-import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
+import android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
+import android.net.NetworkCapabilities.TRANSPORT_TEST
+import android.net.NetworkCapabilities.TRANSPORT_VPN
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.net.NetworkPolicyManager
 import android.net.NetworkProvider
 import android.net.NetworkScore
 import android.net.PacProxyManager
-import android.net.RouteInfo
 import android.net.networkstack.NetworkStackClientBase
 import android.os.BatteryStatsManager
 import android.os.Handler
@@ -60,6 +77,25 @@
 
 open class FromS<Type>(val value: Type)
 
+internal const val VERSION_UNMOCKED = -1
+internal const val VERSION_R = 1
+internal const val VERSION_S = 2
+internal const val VERSION_T = 3
+internal const val VERSION_U = 4
+internal const val VERSION_V = 5
+internal const val VERSION_MAX = VERSION_V
+
+private fun NetworkCapabilities.getLegacyType() =
+        when (transportTypes.getOrElse(0) { TRANSPORT_WIFI }) {
+            TRANSPORT_BLUETOOTH -> ConnectivityManager.TYPE_BLUETOOTH
+            TRANSPORT_CELLULAR -> ConnectivityManager.TYPE_MOBILE
+            TRANSPORT_ETHERNET -> ConnectivityManager.TYPE_ETHERNET
+            TRANSPORT_TEST -> ConnectivityManager.TYPE_TEST
+            TRANSPORT_VPN -> ConnectivityManager.TYPE_VPN
+            TRANSPORT_WIFI -> ConnectivityManager.TYPE_WIFI
+            else -> ConnectivityManager.TYPE_NONE
+        }
+
 /**
  * Base class for tests testing ConnectivityService and its satellites.
  *
@@ -108,9 +144,10 @@
     val networkStack = mock<NetworkStackClientBase>()
     val csHandlerThread = HandlerThread("CSTestHandler")
     val sysResources = mock<Resources>().also { initMockedResources(it) }
-    val packageManager = makeMockPackageManager()
+    val packageManager = makeMockPackageManager(instrumentationContext)
     val connResources = makeMockConnResources(sysResources, packageManager)
 
+    val netd = mock<INetd>()
     val bpfNetMaps = mock<BpfNetMaps>()
     val clatCoordinator = mock<ClatCoordinator>()
     val proxyTracker = ProxyTracker(context, mock<Handler>(), 16 /* EVENT_PROXY_HAS_CHANGED */)
@@ -122,7 +159,7 @@
     }
 
     val deps = CSDeps()
-    val service = makeConnectivityService(context, deps).also { it.systemReadyInternal() }
+    val service = makeConnectivityService(context, netd, deps).also { it.systemReadyInternal() }
     val cm = ConnectivityManager(context, service)
     val csHandler = Handler(csHandlerThread.looper)
 
@@ -176,6 +213,24 @@
                 changeId in enabledChangeIds
         override fun isChangeEnabled(changeId: Long, uid: Int) =
                 changeId in enabledChangeIds
+
+        // In AOSP, build version codes can't always distinguish between some versions (e.g. at the
+        // time of this writing U == V). Define custom ones.
+        private var sdkLevel = VERSION_UNMOCKED
+        private val isSdkUnmocked get() = sdkLevel == VERSION_UNMOCKED
+
+        fun setBuildSdk(sdkLevel: Int) {
+            require(sdkLevel <= VERSION_MAX) {
+                "setBuildSdk must not be called with Build.VERSION constants but " +
+                        "CsTest.VERSION_* constants"
+            }
+            visibleOnHandlerThread(csHandler) { this.sdkLevel = sdkLevel }
+        }
+
+        override fun isAtLeastS() = if (isSdkUnmocked) super.isAtLeastS() else sdkLevel >= VERSION_S
+        override fun isAtLeastT() = if (isSdkUnmocked) super.isAtLeastT() else sdkLevel >= VERSION_T
+        override fun isAtLeastU() = if (isSdkUnmocked) super.isAtLeastU() else sdkLevel >= VERSION_U
+        override fun isAtLeastV() = if (isSdkUnmocked) super.isAtLeastV() else sdkLevel >= VERSION_V
     }
 
     inner class CSContext(base: Context) : BroadcastInterceptingContext(base) {
@@ -231,28 +286,15 @@
     // Utility methods for subclasses to use
     fun waitForIdle() = csHandlerThread.waitForIdle(HANDLER_TIMEOUT_MS)
 
-    private fun emptyAgentConfig() = NetworkAgentConfig.Builder().build()
-    private fun defaultNc() = NetworkCapabilities.Builder()
-            // Add sensible defaults for agents that don't want to care
-            .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
-            .addCapability(NET_CAPABILITY_NOT_ROAMING)
-            .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
-            .build()
-    private fun defaultScore() = FromS<NetworkScore>(NetworkScore.Builder().build())
-    private fun defaultLp() = LinkProperties().apply {
-        addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 32))
-        addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
-    }
-
     // Network agents. See CSAgentWrapper. This class contains utility methods to simplify
     // creation.
     fun Agent(
-            nac: NetworkAgentConfig = emptyAgentConfig(),
             nc: NetworkCapabilities = defaultNc(),
+            nac: NetworkAgentConfig = emptyAgentConfig(nc.getLegacyType()),
             lp: LinkProperties = defaultLp(),
             score: FromS<NetworkScore> = defaultScore(),
             provider: NetworkProvider? = null
-    ) = CSAgentWrapper(context, csHandlerThread, networkStack, nac, nc, lp, score, provider)
+    ) = CSAgentWrapper(context, deps, csHandlerThread, networkStack, nac, nc, lp, score, provider)
 
     fun Agent(vararg transports: Int, lp: LinkProperties = defaultLp()): CSAgentWrapper {
         val nc = NetworkCapabilities.Builder().apply {
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTestHelpers.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTestHelpers.kt
index b8f2151..c1828b2 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTestHelpers.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTestHelpers.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 @file:JvmName("CsTestHelpers")
 
 package com.android.server
@@ -14,7 +30,15 @@
 import android.content.res.Resources
 import android.net.IDnsResolver
 import android.net.INetd
+import android.net.IpPrefix
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.NetworkAgentConfig
+import android.net.NetworkCapabilities
+import android.net.NetworkScore
+import android.net.RouteInfo
 import android.net.metrics.IpConnectivityLog
+import android.os.Binder
 import android.os.Handler
 import android.os.HandlerThread
 import android.os.SystemClock
@@ -31,19 +55,38 @@
 import com.android.server.connectivity.ConnectivityResources
 import org.mockito.ArgumentMatchers
 import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.ArgumentMatchers.argThat
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mockito
 import org.mockito.Mockito.doAnswer
-import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.doReturn
 import kotlin.test.fail
 
 internal inline fun <reified T> mock() = Mockito.mock(T::class.java)
 internal inline fun <reified T> any() = any(T::class.java)
 
+internal fun emptyAgentConfig(legacyType: Int) = NetworkAgentConfig.Builder()
+        .setLegacyType(legacyType)
+        .build()
+
+internal fun defaultNc() = NetworkCapabilities.Builder()
+        // Add sensible defaults for agents that don't want to care
+        .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+        .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
+        .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+        .build()
+
+internal fun defaultScore() = FromS(NetworkScore.Builder().build())
+
+internal fun defaultLp() = LinkProperties().apply {
+    addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 32))
+    addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
+}
+
 internal fun makeMockContentResolver(context: Context) = MockContentResolver(context).apply {
     addProvider(Settings.AUTHORITY, FakeSettingsProvider())
 }
@@ -59,9 +102,22 @@
     }
 }
 
-internal fun makeMockPackageManager() = mock<PackageManager>().also { pm ->
+internal fun makeMockPackageManager(realContext: Context) = mock<PackageManager>().also { pm ->
     val supported = listOf(FEATURE_WIFI, FEATURE_WIFI_DIRECT, FEATURE_BLUETOOTH, FEATURE_ETHERNET)
     doReturn(true).`when`(pm).hasSystemFeature(argThat { supported.contains(it) })
+    val myPackageName = realContext.packageName
+    val myPackageInfo = realContext.packageManager.getPackageInfo(myPackageName,
+            PackageManager.GET_PERMISSIONS)
+    // Very high version code so that the checks for the module version will always
+    // say that it is recent enough. This is the most sensible default, but if some
+    // test needs to test with different version codes they can re-mock this with a
+    // different value.
+    myPackageInfo.longVersionCode = 9999999L
+    doReturn(arrayOf(myPackageName)).`when`(pm).getPackagesForUid(Binder.getCallingUid())
+    doReturn(myPackageInfo).`when`(pm).getPackageInfoAsUser(
+            eq(myPackageName), anyInt(), eq(UserHandle.getCallingUserId()))
+    doReturn(listOf(myPackageInfo)).`when`(pm)
+            .getInstalledPackagesAsUser(eq(PackageManager.GET_PERMISSIONS), anyInt())
 }
 
 internal fun makeMockConnResources(resources: Resources, pm: PackageManager) = mock<Context>().let {
@@ -129,12 +185,13 @@
 
 private val TEST_LINGER_DELAY_MS = 400
 private val TEST_NASCENT_DELAY_MS = 300
-internal fun makeConnectivityService(context: Context, deps: Dependencies) = ConnectivityService(
-        context,
-        mock<IDnsResolver>(),
-        mock<IpConnectivityLog>(),
-        mock<INetd>(),
-        deps).also {
-    it.mLingerDelayMs = TEST_LINGER_DELAY_MS
-    it.mNascentDelayMs = TEST_NASCENT_DELAY_MS
-}
+internal fun makeConnectivityService(context: Context, netd: INetd, deps: Dependencies) =
+        ConnectivityService(
+                context,
+                mock<IDnsResolver>(),
+                mock<IpConnectivityLog>(),
+                netd,
+                deps).also {
+            it.mLingerDelayMs = TEST_LINGER_DELAY_MS
+            it.mNascentDelayMs = TEST_NASCENT_DELAY_MS
+        }
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
index 292f77e..c477b2c 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -59,6 +59,7 @@
 import com.android.testutils.DevSdkIgnoreRunner;
 import com.android.testutils.HandlerUtils;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -139,6 +140,14 @@
         mUsageCallback = new TestableUsageCallback(mUsageCallbackBinder);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        if (mObserverHandlerThread != null) {
+            mObserverHandlerThread.quitSafely();
+            mObserverHandlerThread.join();
+        }
+    }
+
     @Test
     public void testRegister_thresholdTooLow_setsDefaultThreshold() throws Exception {
         final long thresholdTooLowBytes = 1L;
diff --git a/thread/tests/cts/Android.bp b/thread/tests/cts/Android.bp
index 96056c6..278798e 100644
--- a/thread/tests/cts/Android.bp
+++ b/thread/tests/cts/Android.bp
@@ -38,7 +38,7 @@
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
         "net-tests-utils",
-        "truth-prebuilt",
+        "truth",
     ],
     libs: [
         "android.test.base",