Merge "Replace buildTemplate* with NetworkTemplate.Builder"
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 638eb29..f3d6aee 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -31,7 +31,6 @@
         "apishim/**/*.java",
         "src/**/*.java",
         ":framework-connectivity-shared-srcs",
-        ":tethering-module-utils-srcs",
         ":services-tethering-shared-srcs",
         ":statslog-tethering-java-gen",
     ],
@@ -212,8 +211,11 @@
 
 sdk {
     name: "tethering-module-sdk",
-    bootclasspath_fragments: ["com.android.tethering-bootclasspath-fragment"],
-    systemserverclasspath_fragments: ["com.android.tethering-systemserverclasspath-fragment"],
+    apexes: [
+        // Adds exportable dependencies of the APEX to the sdk,
+        // e.g. *classpath_fragments.
+        "com.android.tethering",
+    ],
 }
 
 java_library_static {
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index b3cae7c..299a88e 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -18,6 +18,20 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+prebuilt_etc {
+    name: "TetheringInProcessFlag",
+    src: "in-process",
+    filename_from_src: true,
+    sub_dir: "flag",
+}
+
+prebuilt_etc {
+    name: "TetheringOutOfProcessFlag",
+    src: "out-of-process",
+    filename_from_src: true,
+    sub_dir: "flag",
+}
+
 // Defaults to enable/disable java targets which uses development APIs. "enabled" may have a
 // different value depending on the branch.
 java_defaults {
@@ -74,7 +88,9 @@
         "dscp_policy.o",
         "netd.o",
         "offload.o",
+        "offload@btf.o",
         "test.o",
+        "test@btf.o",
     ],
     apps: [
         "ServiceConnectivityResources",
@@ -82,6 +98,7 @@
     prebuilts: [
         "current_sdkinfo",
         "privapp_allowlist_com.android.tethering",
+        "TetheringOutOfProcessFlag",
     ],
     manifest: "manifest.json",
     key: "com.android.tethering.key",
@@ -183,8 +200,21 @@
     base: "com.android.tethering",
     package_name: "com.android.tethering.inprocess",
     enabled: enable_tethering_next_apex,
+    bpfs: [
+        "block.o",
+        "clatd.o",
+        "dscp_policy.o",
+        "netd.o",
+        "offload@inprocess.o",
+        "test@inprocess.o",
+    ],
     apps: [
         "ServiceConnectivityResources",
         "InProcessTethering",
     ],
+    prebuilts: [
+        "current_sdkinfo",
+        "privapp_allowlist_com.android.tethering",
+        "TetheringInProcessFlag",
+    ],
 }
diff --git a/Tethering/apex/in-process b/Tethering/apex/in-process
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tethering/apex/in-process
diff --git a/Tethering/apex/out-of-process b/Tethering/apex/out-of-process
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tethering/apex/out-of-process
diff --git a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
index 741af5c..784ebd5 100644
--- a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
+++ b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
@@ -296,7 +296,7 @@
      *  4th priority : Checks whether provisioning is required from RRO configuration.
      *
      * @param config
-     * @return integer {@see #TETHERING_PROVISIONING_NOT_REQUIRED,
+     * @return integer See {@link #TETHERING_PROVISIONING_NOT_REQUIRED,
      *                 #TETHERING_PROVISIONING_REQUIRED,
      *                 #TETHERING_PROVISIONING_CARRIER_UNSUPPORT}
      */
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 3f328db..89ed620 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -98,7 +98,6 @@
 import android.net.TetheringManager.TetheringRequest;
 import android.net.TetheringRequestParcel;
 import android.net.ip.IpServer;
-import android.net.shared.NetdUtils;
 import android.net.wifi.WifiClient;
 import android.net.wifi.WifiManager;
 import android.net.wifi.p2p.WifiP2pGroup;
@@ -135,6 +134,7 @@
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
 import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.NetdUtils;
 import com.android.net.module.util.SharedLog;
 import com.android.networkstack.apishim.common.BluetoothPanShim;
 import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim;
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index 78fca29..6ab5281 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -92,6 +92,29 @@
 }
 
 bpf {
+    name: "offload@btf.o",
+    srcs: ["offload@btf.c"],
+    btf: true,
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-DBTF",
+    ],
+}
+
+bpf {
+    name: "offload@inprocess.o",
+    srcs: ["offload@inprocess.c"],
+    btf: true,
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-DBTF",
+        "-DINPROCESS",
+    ],
+}
+
+bpf {
     name: "test.o",
     srcs: ["test.c"],
     cflags: [
@@ -101,6 +124,29 @@
 }
 
 bpf {
+    name: "test@btf.o",
+    srcs: ["test@btf.c"],
+    btf: true,
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-DBTF",
+    ],
+}
+
+bpf {
+    name: "test@inprocess.o",
+    srcs: ["test@inprocess.c"],
+    btf: true,
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-DBTF",
+        "-DINPROCESS",
+    ],
+}
+
+bpf {
     name: "clatd.o",
     srcs: ["clatd.c"],
     btf: true,
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index 2ec0792..4eb1e8d 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -24,8 +24,27 @@
 #define __kernel_udphdr udphdr
 #include <linux/udp.h>
 
+#ifdef BTF
+// BTF is incompatible with bpfloaders < v0.10, hence for S (v0.2) we must
+// ship a different file than for later versions, but we need bpfloader v0.25+
+// for obj@ver.o support
+#define BPFLOADER_MIN_VER BPFLOADER_OBJ_AT_VER_VERSION
+#else /* BTF */
 // The resulting .o needs to load on the Android S bpfloader
 #define BPFLOADER_MIN_VER BPFLOADER_S_VERSION
+#define BPFLOADER_MAX_VER BPFLOADER_OBJ_AT_VER_VERSION
+#endif /* BTF */
+
+// Warning: values other than AID_ROOT don't work for map uid on BpfLoader < v0.21
+#define TETHERING_UID AID_ROOT
+
+#ifdef INPROCESS
+#define DEFAULT_BPF_MAP_SELINUX_CONTEXT "fs_bpf_net_shared"
+#define DEFAULT_BPF_PROG_SELINUX_CONTEXT "fs_bpf_net_shared"
+#define TETHERING_GID AID_SYSTEM
+#else
+#define TETHERING_GID AID_NETWORK_STACK
+#endif
 
 #include "bpf_helpers.h"
 #include "bpf_net_helpers.h"
@@ -73,7 +92,7 @@
 // ----- Tethering Error Counters -----
 
 DEFINE_BPF_MAP_GRW(tether_error_map, ARRAY, uint32_t, uint32_t, BPF_TETHER_ERR__MAX,
-                   AID_NETWORK_STACK)
+                   TETHERING_GID)
 
 #define COUNT_AND_RETURN(counter, ret) do {                     \
     uint32_t code = BPF_TETHER_ERR_ ## counter;                 \
@@ -91,22 +110,22 @@
 // ----- Tethering Data Stats and Limits -----
 
 // Tethering stats, indexed by upstream interface.
-DEFINE_BPF_MAP_GRW(tether_stats_map, HASH, TetherStatsKey, TetherStatsValue, 16, AID_NETWORK_STACK)
+DEFINE_BPF_MAP_GRW(tether_stats_map, HASH, TetherStatsKey, TetherStatsValue, 16, TETHERING_GID)
 
 // Tethering data limit, indexed by upstream interface.
 // (tethering allowed when stats[iif].rxBytes + stats[iif].txBytes < limit[iif])
-DEFINE_BPF_MAP_GRW(tether_limit_map, HASH, TetherLimitKey, TetherLimitValue, 16, AID_NETWORK_STACK)
+DEFINE_BPF_MAP_GRW(tether_limit_map, HASH, TetherLimitKey, TetherLimitValue, 16, TETHERING_GID)
 
 // ----- IPv6 Support -----
 
 DEFINE_BPF_MAP_GRW(tether_downstream6_map, HASH, TetherDownstream6Key, Tether6Value, 64,
-                   AID_NETWORK_STACK)
+                   TETHERING_GID)
 
 DEFINE_BPF_MAP_GRW(tether_downstream64_map, HASH, TetherDownstream64Key, TetherDownstream64Value,
-                   1024, AID_NETWORK_STACK)
+                   1024, TETHERING_GID)
 
 DEFINE_BPF_MAP_GRW(tether_upstream6_map, HASH, TetherUpstream6Key, Tether6Value, 64,
-                   AID_NETWORK_STACK)
+                   TETHERING_GID)
 
 static inline __always_inline int do_forward6(struct __sk_buff* skb, const bool is_ethernet,
         const bool downstream) {
@@ -280,13 +299,13 @@
     return bpf_redirect(v->oif, 0 /* this is effectively BPF_F_EGRESS */);
 }
 
-DEFINE_BPF_PROG("schedcls/tether_downstream6_ether", AID_ROOT, AID_NETWORK_STACK,
+DEFINE_BPF_PROG("schedcls/tether_downstream6_ether", TETHERING_UID, TETHERING_GID,
                 sched_cls_tether_downstream6_ether)
 (struct __sk_buff* skb) {
     return do_forward6(skb, /* is_ethernet */ true, /* downstream */ true);
 }
 
-DEFINE_BPF_PROG("schedcls/tether_upstream6_ether", AID_ROOT, AID_NETWORK_STACK,
+DEFINE_BPF_PROG("schedcls/tether_upstream6_ether", TETHERING_UID, TETHERING_GID,
                 sched_cls_tether_upstream6_ether)
 (struct __sk_buff* skb) {
     return do_forward6(skb, /* is_ethernet */ true, /* downstream */ false);
@@ -305,13 +324,13 @@
 // and thus a 5.4 kernel always supports this.
 //
 // Hence, these mandatory (must load successfully) implementations for 5.4+ kernels:
-DEFINE_BPF_PROG_KVER("schedcls/tether_downstream6_rawip$5_4", AID_ROOT, AID_NETWORK_STACK,
+DEFINE_BPF_PROG_KVER("schedcls/tether_downstream6_rawip$5_4", TETHERING_UID, TETHERING_GID,
                      sched_cls_tether_downstream6_rawip_5_4, KVER(5, 4, 0))
 (struct __sk_buff* skb) {
     return do_forward6(skb, /* is_ethernet */ false, /* downstream */ true);
 }
 
-DEFINE_BPF_PROG_KVER("schedcls/tether_upstream6_rawip$5_4", AID_ROOT, AID_NETWORK_STACK,
+DEFINE_BPF_PROG_KVER("schedcls/tether_upstream6_rawip$5_4", TETHERING_UID, TETHERING_GID,
                      sched_cls_tether_upstream6_rawip_5_4, KVER(5, 4, 0))
 (struct __sk_buff* skb) {
     return do_forward6(skb, /* is_ethernet */ false, /* downstream */ false);
@@ -319,7 +338,7 @@
 
 // and these identical optional (may fail to load) implementations for [4.14..5.4) patched kernels:
 DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream6_rawip$4_14",
-                                    AID_ROOT, AID_NETWORK_STACK,
+                                    TETHERING_UID, TETHERING_GID,
                                     sched_cls_tether_downstream6_rawip_4_14,
                                     KVER(4, 14, 0), KVER(5, 4, 0))
 (struct __sk_buff* skb) {
@@ -327,7 +346,7 @@
 }
 
 DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream6_rawip$4_14",
-                                    AID_ROOT, AID_NETWORK_STACK,
+                                    TETHERING_UID, TETHERING_GID,
                                     sched_cls_tether_upstream6_rawip_4_14,
                                     KVER(4, 14, 0), KVER(5, 4, 0))
 (struct __sk_buff* skb) {
@@ -337,13 +356,13 @@
 // and define no-op stubs for [4.9,4.14) and unpatched [4.14,5.4) kernels.
 // (if the above real 4.14+ program loaded successfully, then bpfloader will have already pinned
 // it at the same location this one would be pinned at and will thus skip loading this stub)
-DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream6_rawip$stub", AID_ROOT, AID_NETWORK_STACK,
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream6_rawip$stub", TETHERING_UID, TETHERING_GID,
                            sched_cls_tether_downstream6_rawip_stub, KVER_NONE, KVER(5, 4, 0))
 (struct __sk_buff* skb) {
     return TC_ACT_PIPE;
 }
 
-DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream6_rawip$stub", AID_ROOT, AID_NETWORK_STACK,
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream6_rawip$stub", TETHERING_UID, TETHERING_GID,
                            sched_cls_tether_upstream6_rawip_stub, KVER_NONE, KVER(5, 4, 0))
 (struct __sk_buff* skb) {
     return TC_ACT_PIPE;
@@ -351,9 +370,9 @@
 
 // ----- IPv4 Support -----
 
-DEFINE_BPF_MAP_GRW(tether_downstream4_map, HASH, Tether4Key, Tether4Value, 1024, AID_NETWORK_STACK)
+DEFINE_BPF_MAP_GRW(tether_downstream4_map, HASH, Tether4Key, Tether4Value, 1024, TETHERING_GID)
 
-DEFINE_BPF_MAP_GRW(tether_upstream4_map, HASH, Tether4Key, Tether4Value, 1024, AID_NETWORK_STACK)
+DEFINE_BPF_MAP_GRW(tether_upstream4_map, HASH, Tether4Key, Tether4Value, 1024, TETHERING_GID)
 
 static inline __always_inline int do_forward4_bottom(struct __sk_buff* skb,
         const int l2_header_size, void* data, const void* data_end,
@@ -645,25 +664,25 @@
 
 // Full featured (required) implementations for 5.8+ kernels (these are S+ by definition)
 
-DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_rawip$5_8", AID_ROOT, AID_NETWORK_STACK,
+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))
 (struct __sk_buff* skb) {
     return do_forward4(skb, /* is_ethernet */ false, /* downstream */ true, /* updatetime */ true);
 }
 
-DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_rawip$5_8", AID_ROOT, AID_NETWORK_STACK,
+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))
 (struct __sk_buff* skb) {
     return do_forward4(skb, /* is_ethernet */ false, /* downstream */ false, /* updatetime */ true);
 }
 
-DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_ether$5_8", AID_ROOT, AID_NETWORK_STACK,
+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))
 (struct __sk_buff* skb) {
     return do_forward4(skb, /* is_ethernet */ true, /* downstream */ true, /* updatetime */ true);
 }
 
-DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_ether$5_8", AID_ROOT, AID_NETWORK_STACK,
+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))
 (struct __sk_buff* skb) {
     return do_forward4(skb, /* is_ethernet */ true, /* downstream */ false, /* updatetime */ true);
@@ -673,7 +692,7 @@
 // (optional, because we need to be able to fallback for 4.14/4.19/5.4 pre-S kernels)
 
 DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$opt",
-                                    AID_ROOT, AID_NETWORK_STACK,
+                                    TETHERING_UID, TETHERING_GID,
                                     sched_cls_tether_downstream4_rawip_opt,
                                     KVER(4, 14, 0), KVER(5, 8, 0))
 (struct __sk_buff* skb) {
@@ -681,7 +700,7 @@
 }
 
 DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$opt",
-                                    AID_ROOT, AID_NETWORK_STACK,
+                                    TETHERING_UID, TETHERING_GID,
                                     sched_cls_tether_upstream4_rawip_opt,
                                     KVER(4, 14, 0), KVER(5, 8, 0))
 (struct __sk_buff* skb) {
@@ -689,7 +708,7 @@
 }
 
 DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$opt",
-                                    AID_ROOT, AID_NETWORK_STACK,
+                                    TETHERING_UID, TETHERING_GID,
                                     sched_cls_tether_downstream4_ether_opt,
                                     KVER(4, 14, 0), KVER(5, 8, 0))
 (struct __sk_buff* skb) {
@@ -697,7 +716,7 @@
 }
 
 DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$opt",
-                                    AID_ROOT, AID_NETWORK_STACK,
+                                    TETHERING_UID, TETHERING_GID,
                                     sched_cls_tether_upstream4_ether_opt,
                                     KVER(4, 14, 0), KVER(5, 8, 0))
 (struct __sk_buff* skb) {
@@ -718,13 +737,13 @@
 
 // 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", AID_ROOT, AID_NETWORK_STACK,
+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))
 (struct __sk_buff* skb) {
     return do_forward4(skb, /* is_ethernet */ false, /* downstream */ true, /* updatetime */ false);
 }
 
-DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$5_4", AID_ROOT, AID_NETWORK_STACK,
+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))
 (struct __sk_buff* skb) {
     return do_forward4(skb, /* is_ethernet */ false, /* downstream */ false, /* updatetime */ false);
@@ -734,7 +753,7 @@
 // [Note: fallback for 4.14/4.19 (P/Q) kernels is below in stub section]
 
 DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$4_14",
-                                    AID_ROOT, AID_NETWORK_STACK,
+                                    TETHERING_UID, TETHERING_GID,
                                     sched_cls_tether_downstream4_rawip_4_14,
                                     KVER(4, 14, 0), KVER(5, 4, 0))
 (struct __sk_buff* skb) {
@@ -742,7 +761,7 @@
 }
 
 DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$4_14",
-                                    AID_ROOT, AID_NETWORK_STACK,
+                                    TETHERING_UID, TETHERING_GID,
                                     sched_cls_tether_upstream4_rawip_4_14,
                                     KVER(4, 14, 0), KVER(5, 4, 0))
 (struct __sk_buff* skb) {
@@ -751,13 +770,13 @@
 
 // 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", AID_ROOT, AID_NETWORK_STACK,
+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))
 (struct __sk_buff* skb) {
     return do_forward4(skb, /* is_ethernet */ true, /* downstream */ true, /* updatetime */ false);
 }
 
-DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$4_14", AID_ROOT, AID_NETWORK_STACK,
+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))
 (struct __sk_buff* skb) {
     return do_forward4(skb, /* is_ethernet */ true, /* downstream */ false, /* updatetime */ false);
@@ -767,13 +786,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", AID_ROOT, AID_NETWORK_STACK,
+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))
 (struct __sk_buff* skb) {
     return TC_ACT_PIPE;
 }
 
-DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$stub", AID_ROOT, AID_NETWORK_STACK,
+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))
 (struct __sk_buff* skb) {
     return TC_ACT_PIPE;
@@ -781,13 +800,13 @@
 
 // ETHER: 4.9-P/Q kernel
 
-DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$stub", AID_ROOT, AID_NETWORK_STACK,
+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))
 (struct __sk_buff* skb) {
     return TC_ACT_PIPE;
 }
 
-DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$stub", AID_ROOT, AID_NETWORK_STACK,
+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))
 (struct __sk_buff* skb) {
     return TC_ACT_PIPE;
@@ -795,7 +814,7 @@
 
 // ----- XDP Support -----
 
-DEFINE_BPF_MAP_GRW(tether_dev_map, DEVMAP_HASH, uint32_t, uint32_t, 64, AID_NETWORK_STACK)
+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) {
@@ -840,7 +859,7 @@
 }
 
 #define DEFINE_XDP_PROG(str, func) \
-    DEFINE_BPF_PROG_KVER(str, AID_ROOT, AID_NETWORK_STACK, func, KVER(5, 9, 0))(struct xdp_md *ctx)
+    DEFINE_BPF_PROG_KVER(str, TETHERING_UID, TETHERING_GID, func, KVER(5, 9, 0))(struct xdp_md *ctx)
 
 DEFINE_XDP_PROG("xdp/tether_downstream_ether",
                  xdp_tether_downstream_ether) {
diff --git a/bpf_progs/offload@btf.c b/bpf_progs/offload@btf.c
new file mode 120000
index 0000000..4092e0d
--- /dev/null
+++ b/bpf_progs/offload@btf.c
@@ -0,0 +1 @@
+offload.c
\ No newline at end of file
diff --git a/bpf_progs/offload@inprocess.c b/bpf_progs/offload@inprocess.c
new file mode 120000
index 0000000..4092e0d
--- /dev/null
+++ b/bpf_progs/offload@inprocess.c
@@ -0,0 +1 @@
+offload.c
\ No newline at end of file
diff --git a/bpf_progs/test.c b/bpf_progs/test.c
index f2fcc8c..d42205f 100644
--- a/bpf_progs/test.c
+++ b/bpf_progs/test.c
@@ -18,8 +18,27 @@
 #include <linux/in.h>
 #include <linux/ip.h>
 
+#ifdef BTF
+// BTF is incompatible with bpfloaders < v0.10, hence for S (v0.2) we must
+// ship a different file than for later versions, but we need bpfloader v0.25+
+// for obj@ver.o support
+#define BPFLOADER_MIN_VER BPFLOADER_OBJ_AT_VER_VERSION
+#else /* BTF */
 // The resulting .o needs to load on the Android S bpfloader
 #define BPFLOADER_MIN_VER BPFLOADER_S_VERSION
+#define BPFLOADER_MAX_VER BPFLOADER_OBJ_AT_VER_VERSION
+#endif /* BTF */
+
+// Warning: values other than AID_ROOT don't work for map uid on BpfLoader < v0.21
+#define TETHERING_UID AID_ROOT
+
+#ifdef INPROCESS
+#define DEFAULT_BPF_MAP_SELINUX_CONTEXT "fs_bpf_net_shared"
+#define DEFAULT_BPF_PROG_SELINUX_CONTEXT "fs_bpf_net_shared"
+#define TETHERING_GID AID_SYSTEM
+#else
+#define TETHERING_GID AID_NETWORK_STACK
+#endif
 
 #include "bpf_helpers.h"
 #include "bpf_net_helpers.h"
@@ -27,12 +46,11 @@
 
 // Used only by TetheringPrivilegedTests, not by production code.
 DEFINE_BPF_MAP_GRW(tether_downstream6_map, HASH, TetherDownstream6Key, Tether6Value, 16,
-                   AID_NETWORK_STACK)
+                   TETHERING_GID)
 // Used only by BpfBitmapTest, not by production code.
-DEFINE_BPF_MAP_GRW(bitmap, ARRAY, int, uint64_t, 2,
-                   AID_NETWORK_STACK)
+DEFINE_BPF_MAP_GRW(bitmap, ARRAY, int, uint64_t, 2, TETHERING_GID)
 
-DEFINE_BPF_PROG_KVER("xdp/drop_ipv4_udp_ether", AID_ROOT, AID_NETWORK_STACK,
+DEFINE_BPF_PROG_KVER("xdp/drop_ipv4_udp_ether", TETHERING_UID, TETHERING_GID,
                       xdp_test, KVER(5, 9, 0))
 (struct xdp_md *ctx) {
     void *data = (void *)(long)ctx->data;
diff --git a/bpf_progs/test@btf.c b/bpf_progs/test@btf.c
new file mode 120000
index 0000000..aeebb26
--- /dev/null
+++ b/bpf_progs/test@btf.c
@@ -0,0 +1 @@
+test.c
\ No newline at end of file
diff --git a/bpf_progs/test@inprocess.c b/bpf_progs/test@inprocess.c
new file mode 120000
index 0000000..aeebb26
--- /dev/null
+++ b/bpf_progs/test@inprocess.c
@@ -0,0 +1 @@
+test.c
\ No newline at end of file
diff --git a/framework-t/src/android/net/netstats/provider/NetworkStatsProvider.java b/framework-t/src/android/net/netstats/provider/NetworkStatsProvider.java
index d37a53d..66d99a1 100644
--- a/framework-t/src/android/net/netstats/provider/NetworkStatsProvider.java
+++ b/framework-t/src/android/net/netstats/provider/NetworkStatsProvider.java
@@ -118,7 +118,7 @@
      *
      * @param token the token under which these stats were gathered. Providers can call this method
      *              with the current token as often as they want, until the token changes.
-     *              {@see NetworkStatsProvider#onRequestStatsUpdate()}
+     *              See {@link NetworkStatsProvider#onRequestStatsUpdate(int)}
      * @param ifaceStats the {@link NetworkStats} per interface to be reported.
      *                   The provider should not include any traffic that is already counted by
      *                   kernel interface counters.
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index fad63e5..3fcc11b 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -126,7 +126,7 @@
  * http://www.iana.org/form/ports-service. Existing services can be found at
  * http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml
  *
- * {@see NsdServiceInfo}
+ * @see NsdServiceInfo
  */
 @SystemService(Context.NSD_SERVICE)
 public final class NsdManager {
diff --git a/framework-t/src/android/net/nsd/NsdServiceInfo.java b/framework-t/src/android/net/nsd/NsdServiceInfo.java
index 200c808..6438a60 100644
--- a/framework-t/src/android/net/nsd/NsdServiceInfo.java
+++ b/framework-t/src/android/net/nsd/NsdServiceInfo.java
@@ -34,7 +34,7 @@
 
 /**
  * A class representing service information for network service discovery
- * {@see NsdManager}
+ * @see NsdManager
  */
 public final class NsdServiceInfo implements Parcelable {
 
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index eeedfd1..6ccd77e 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -556,7 +556,7 @@
      *
      * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or
      *         {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an
-     *         appropriate network. {@see NetworkCapabilities} for supported transports.
+     *         appropriate network. See {@link NetworkCapabilities} for supported transports.
      */
     @Deprecated
     public static final int TYPE_MOBILE      = 0;
@@ -566,7 +566,7 @@
      *
      * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or
      *         {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an
-     *         appropriate network. {@see NetworkCapabilities} for supported transports.
+     *         appropriate network. See {@link NetworkCapabilities} for supported transports.
      */
     @Deprecated
     public static final int TYPE_WIFI        = 1;
@@ -617,7 +617,7 @@
      *
      * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or
      *         {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an
-     *         appropriate network. {@see NetworkCapabilities} for supported transports.
+     *         appropriate network. See {@link NetworkCapabilities} for supported transports.
      */
     @Deprecated
     public static final int TYPE_MOBILE_HIPRI = 5;
@@ -627,7 +627,7 @@
      *
      * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or
      *         {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an
-     *         appropriate network. {@see NetworkCapabilities} for supported transports.
+     *         appropriate network. See {@link NetworkCapabilities} for supported transports.
      */
     @Deprecated
     public static final int TYPE_WIMAX       = 6;
@@ -637,7 +637,7 @@
      *
      * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or
      *         {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an
-     *         appropriate network. {@see NetworkCapabilities} for supported transports.
+     *         appropriate network. See {@link NetworkCapabilities} for supported transports.
      */
     @Deprecated
     public static final int TYPE_BLUETOOTH   = 7;
@@ -654,7 +654,7 @@
      *
      * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or
      *         {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an
-     *         appropriate network. {@see NetworkCapabilities} for supported transports.
+     *         appropriate network. See {@link NetworkCapabilities} for supported transports.
      */
     @Deprecated
     public static final int TYPE_ETHERNET    = 9;
@@ -1204,7 +1204,7 @@
 
     /**
      * Preference for {@link ProfileNetworkPreference#setPreference(int)}.
-     * {@see #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)}
+     * See {@link #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)}
      * Specify that the traffic for this user should by follow the default rules.
      * @hide
      */
@@ -1213,7 +1213,7 @@
 
     /**
      * Preference for {@link ProfileNetworkPreference#setPreference(int)}.
-     * {@see #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)}
+     * See {@link #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)}
      * Specify that the traffic for this user should by default go on a network with
      * {@link NetworkCapabilities#NET_CAPABILITY_ENTERPRISE}, and on the system default network
      * if no such network is available.
@@ -1224,7 +1224,7 @@
 
     /**
      * Preference for {@link ProfileNetworkPreference#setPreference(int)}.
-     * {@see #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)}
+     * See {@link #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)}
      * Specify that the traffic for this user should by default go on a network with
      * {@link NetworkCapabilities#NET_CAPABILITY_ENTERPRISE} and if no such network is available
      * should not go on the system default network
@@ -3383,8 +3383,8 @@
      * proxy is likely to break networking on multiple networks. This method is only meant
      * for device policy clients looking to do general internal filtering or similar use cases.
      *
-     * {@see #getGlobalProxy}
-     * {@see LinkProperties#getHttpProxy}
+     * @see #getGlobalProxy
+     * @see LinkProperties#getHttpProxy
      *
      * @param p A {@link ProxyInfo} object defining the new global HTTP proxy. Calling this
      *          method with a {@code null} value will clear the global HTTP proxy.
@@ -4277,7 +4277,7 @@
      * network, unless it becomes the best again at some later time. All callbacks are invoked
      * in order on the same thread, which by default is a thread created by the framework running
      * in the app.
-     * {@see #requestNetwork(NetworkRequest, NetworkCallback, Handler)} to change where the
+     * See {@link #requestNetwork(NetworkRequest, NetworkCallback, Handler)} to change where the
      * callbacks are invoked.
      *
      * <p>This{@link NetworkRequest} will live until released via
diff --git a/framework/src/android/net/DnsResolver.java b/framework/src/android/net/DnsResolver.java
index 164160f..5e637f9 100644
--- a/framework/src/android/net/DnsResolver.java
+++ b/framework/src/android/net/DnsResolver.java
@@ -137,7 +137,7 @@
          * @param answer <T> answer to the query.
          * @param rcode The response code in the DNS response.
          *
-         * {@see android.net.DnsResolver#query query()}
+         * @see android.net.DnsResolver#query query()
          */
         void onAnswer(@NonNull T answer, int rcode);
         /**
diff --git a/framework/src/android/net/LinkProperties.java b/framework/src/android/net/LinkProperties.java
index a8f707e..b7ee846 100644
--- a/framework/src/android/net/LinkProperties.java
+++ b/framework/src/android/net/LinkProperties.java
@@ -29,6 +29,8 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.LinkPropertiesUtils;
 
 import java.net.Inet4Address;
@@ -42,7 +44,6 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.StringJoiner;
-import java.util.stream.Collectors;
 
 /**
  * Describes the properties of a network link.
@@ -759,9 +760,15 @@
      * @return An unmodifiable {@link List} of {@link RouteInfo} for this link.
      */
     public @NonNull List<RouteInfo> getRoutes() {
-        if (CompatChanges.isChangeEnabled(EXCLUDED_ROUTES)) {
+        // Before T, there's no throw routes because VpnService is not updatable, so no need to
+        // filter them out.
+        if (CompatChanges.isChangeEnabled(EXCLUDED_ROUTES) || !SdkLevel.isAtLeastT()) {
             return Collections.unmodifiableList(mRoutes);
         } else {
+            // Apps that added a throw route themselves (not obtaining LinkProperties from the
+            // system) will not see it in getRoutes on T+ if they do not have the compat change
+            // enabled (target SDK < T); but this is expected to be rare and typically only affect
+            // tests creating LinkProperties themselves (like CTS v12, which is only running on S).
             return Collections.unmodifiableList(getUnicastRoutes());
         }
     }
@@ -770,9 +777,7 @@
      * Returns all the {@link RouteInfo} of type {@link RouteInfo#RTN_UNICAST} set on this link.
      */
     private @NonNull List<RouteInfo> getUnicastRoutes() {
-        return mRoutes.stream()
-                .filter(route -> route.getType() == RouteInfo.RTN_UNICAST)
-                .collect(Collectors.toList());
+        return CollectionUtils.filter(mRoutes, route -> route.getType() == RouteInfo.RTN_UNICAST);
     }
 
     /**
diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java
index 5659a35..1486619 100644
--- a/framework/src/android/net/NetworkAgent.java
+++ b/framework/src/android/net/NetworkAgent.java
@@ -84,7 +84,7 @@
  * the correct packets. Devices typically have a small number of slots
  * per radio technology, and the specific number of slots for each
  * technology is specified in configuration files.
- * {@see SocketKeepalive} for details.
+ * See {@link SocketKeepalive} for details.
  *
  * @hide
  */
diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java
index 0d2b620..b6f3314 100644
--- a/framework/src/android/net/NetworkAgentConfig.java
+++ b/framework/src/android/net/NetworkAgentConfig.java
@@ -252,7 +252,7 @@
 
     /**
      * Whether network validation should be performed for this VPN network.
-     * {@see #isVpnValidationRequired}
+     * @see #isVpnValidationRequired
      * @hide
      */
     private boolean mVpnRequiresValidation = false;
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index dbb05a9..ea8a3df 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -192,7 +192,7 @@
     /**
      * Bitfield representing the network's enterprise capability identifier.  If any are specified
      * they will be satisfied by any Network that matches all of them.
-     * {@see addEnterpriseId} for details on how masks are added
+     * See {@link #addEnterpriseId(int)} for details on how masks are added
      */
     private int mEnterpriseId;
 
@@ -1460,7 +1460,7 @@
      * Sets the upstream bandwidth for this network in Kbps.  This always only refers to
      * the estimated first hop transport bandwidth.
      * <p>
-     * {@see Builder#setLinkUpstreamBandwidthKbps}
+     * @see Builder#setLinkUpstreamBandwidthKbps
      *
      * @param upKbps the estimated first hop upstream (device to network) bandwidth.
      * @hide
@@ -1484,7 +1484,7 @@
      * Sets the downstream bandwidth for this network in Kbps.  This always only refers to
      * the estimated first hop transport bandwidth.
      * <p>
-     * {@see Builder#setLinkUpstreamBandwidthKbps}
+     * @see Builder#setLinkUpstreamBandwidthKbps
      *
      * @param downKbps the estimated first hop downstream (network to device) bandwidth.
      * @hide
@@ -2534,7 +2534,7 @@
     /**
      * Set the uid and package name of the app causing this network to exist.
      *
-     * {@see #setRequestorUid} and {@link #setRequestorPackageName}
+     * See {@link #setRequestorUid} and {@link #setRequestorPackageName}
      *
      * @param uid UID of the app.
      * @param packageName package name of the app.
@@ -2719,7 +2719,7 @@
         /**
          * Removes the given transport type.
          *
-         * {@see #addTransportType}.
+         * @see #addTransportType
          *
          * @param transportType the transport type to be added or removed.
          * @return this builder
diff --git a/framework/src/android/net/ProfileNetworkPreference.java b/framework/src/android/net/ProfileNetworkPreference.java
index fdcab02..8b98721 100644
--- a/framework/src/android/net/ProfileNetworkPreference.java
+++ b/framework/src/android/net/ProfileNetworkPreference.java
@@ -79,7 +79,7 @@
      * if included is not empty, then only included UIDs are applied.
      * if excluded is not empty, then it is all uids in the user profile except these UIDs.
      * @return Array of uids included for the profile preference.
-     * {@see #getExcludedUids()}
+     * @see #getExcludedUids()
      */
     public @NonNull int[] getIncludedUids() {
         return mIncludedUids.clone();
@@ -93,7 +93,7 @@
      * <ul>If included is not empty, then only included UIDs are applied.</ul>
      * <ul>If excluded is not empty, then it is all uids in the user profile except these UIDs.</ul>
      * @return Array of uids not included for the profile preference.
-     * {@see #getIncludedUids()}
+     * @see #getIncludedUids()
      */
     public @NonNull int[] getExcludedUids() {
         return mExcludedUids.clone();
@@ -177,7 +177,7 @@
         /**
          * This is a array of uids for which profile perefence is set.
          * Empty would mean that this preference applies to all uids in the profile.
-         * {@see #setExcludedUids(int[])}
+         * @see #setExcludedUids(int[])
          * Included UIDs and Excluded UIDs can't both be non-empty.
          * if both are empty, it means this request applies to all uids in the user profile.
          * if included is not empty, then only included UIDs are applied.
@@ -195,7 +195,7 @@
 
         /**
          * This is a array of uids that are excluded for the profile perefence.
-         * {@see #setIncludedUids(int[])}
+         * @see #setIncludedUids(int[])
          * Included UIDs and Excluded UIDs can't both be non-empty.
          * if both are empty, it means this request applies to all uids in the user profile.
          * if included is not empty, then only included UIDs are applied.
diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
index cedffe1..604afc3 100644
--- a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -293,7 +293,7 @@
         private final Context mContext;
         private final NetworkProvider mNetworkProvider;
         private final Dependencies mDeps;
-        private final NetworkProvider.NetworkOfferCallback mNetworkOfferCallback;
+        private NetworkProvider.NetworkOfferCallback mNetworkOfferCallback;
 
         private static String sTcpBufferSizes = null;  // Lazy initialized.
 
@@ -400,8 +400,15 @@
         }
 
         private class EthernetNetworkOfferCallback implements NetworkProvider.NetworkOfferCallback {
+            private boolean isStale() {
+                return this != mNetworkOfferCallback;
+            }
+
             @Override
             public void onNetworkNeeded(@NonNull NetworkRequest request) {
+                if (isStale()) {
+                    return;
+                }
                 if (DBG) {
                     Log.d(TAG, String.format("%s: onNetworkNeeded for request: %s", name, request));
                 }
@@ -416,6 +423,9 @@
 
             @Override
             public void onNetworkUnneeded(@NonNull NetworkRequest request) {
+                if (isStale()) {
+                    return;
+                }
                 if (DBG) {
                     Log.d(TAG,
                             String.format("%s: onNetworkUnneeded for request: %s", name, request));
@@ -443,7 +453,6 @@
             mContext = context;
             mNetworkProvider = networkProvider;
             mDeps = deps;
-            mNetworkOfferCallback = new EthernetNetworkOfferCallback();
             mHwAddress = hwAddress;
         }
 
@@ -669,13 +678,21 @@
         }
 
         private void registerNetworkOffer() {
+            // If mNetworkOfferCallback is already set, it should be reused to update the existing
+            // offer.
+            if (mNetworkOfferCallback == null) {
+                mNetworkOfferCallback = new EthernetNetworkOfferCallback();
+            }
             mNetworkProvider.registerNetworkOffer(getNetworkScore(),
                     new NetworkCapabilities(mCapabilities), cmd -> mHandler.post(cmd),
                     mNetworkOfferCallback);
         }
 
-        public void unregisterNetworkOfferAndStop() {
+        private void unregisterNetworkOfferAndStop() {
             mNetworkProvider.unregisterNetworkOffer(mNetworkOfferCallback);
+            // Setting mNetworkOfferCallback to null allows the callback object to be identified
+            // as stale.
+            mNetworkOfferCallback = null;
             stop();
             mRequestIds.clear();
         }
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index e5bd94b..fef6afb 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -303,7 +303,7 @@
         /**
          * When enabled, all mobile data is reported under {@link NetworkTemplate#NETWORK_TYPE_ALL}.
          * When disabled, mobile data is broken down by a granular ratType representative of the
-         * actual ratType. {@see android.app.usage.NetworkStatsManager#getCollapsedRatType}.
+         * actual ratType. See {@link android.app.usage.NetworkStatsManager#getCollapsedRatType}.
          * Enabling this decreases the level of detail but saves performance, disk space and
          * amount of data logged.
          */
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 648ada2..d7c5a06 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -24,6 +24,8 @@
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
 import static android.system.OsConstants.EINVAL;
 import static android.system.OsConstants.ENODEV;
 import static android.system.OsConstants.ENOENT;
@@ -35,7 +37,6 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.Log;
-import android.util.SparseLongArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -95,22 +96,6 @@
     @VisibleForTesting public static final long OEM_DENY_3_MATCH = (1 << 11);
     // LINT.ThenChange(packages/modules/Connectivity/bpf_progs/bpf_shared.h)
 
-    // TODO: Use Java BpfMap instead of JNI code (TrafficController) for map update.
-    // Currently, BpfNetMaps uses TrafficController for map update and TrafficController
-    // (changeUidOwnerRule and toggleUidOwnerMap) also does conversion from "firewall chain" to
-    // "match". Migrating map update from JNI to Java BpfMap will solve this duplication.
-    private static final SparseLongArray FIREWALL_CHAIN_TO_MATCH = new SparseLongArray();
-    static {
-        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_DOZABLE, DOZABLE_MATCH);
-        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_STANDBY, STANDBY_MATCH);
-        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_POWERSAVE, POWERSAVE_MATCH);
-        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_RESTRICTED, RESTRICTED_MATCH);
-        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_LOW_POWER_STANDBY, LOW_POWER_STANDBY_MATCH);
-        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_OEM_DENY_1, OEM_DENY_1_MATCH);
-        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_OEM_DENY_2, OEM_DENY_2_MATCH);
-        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_OEM_DENY_3, OEM_DENY_3_MATCH);
-    }
-
     /**
      * Set configurationMap for test.
      */
@@ -203,11 +188,50 @@
      */
     @VisibleForTesting
     public long getMatchByFirewallChain(final int chain) {
-        final long match = FIREWALL_CHAIN_TO_MATCH.get(chain, NO_MATCH);
-        if (match == NO_MATCH) {
-            throw new ServiceSpecificException(EINVAL, "Invalid firewall chain: " + chain);
+        switch (chain) {
+            case FIREWALL_CHAIN_DOZABLE:
+                return DOZABLE_MATCH;
+            case FIREWALL_CHAIN_STANDBY:
+                return STANDBY_MATCH;
+            case FIREWALL_CHAIN_POWERSAVE:
+                return POWERSAVE_MATCH;
+            case FIREWALL_CHAIN_RESTRICTED:
+                return RESTRICTED_MATCH;
+            case FIREWALL_CHAIN_LOW_POWER_STANDBY:
+                return LOW_POWER_STANDBY_MATCH;
+            case FIREWALL_CHAIN_OEM_DENY_1:
+                return OEM_DENY_1_MATCH;
+            case FIREWALL_CHAIN_OEM_DENY_2:
+                return OEM_DENY_2_MATCH;
+            case FIREWALL_CHAIN_OEM_DENY_3:
+                return OEM_DENY_3_MATCH;
+            default:
+                throw new ServiceSpecificException(EINVAL, "Invalid firewall chain: " + chain);
         }
-        return match;
+    }
+
+    /**
+     * Get if the chain is allow list or not.
+     *
+     * ALLOWLIST means the firewall denies all by default, uids must be explicitly allowed
+     * DENYLIST means the firewall allows all by default, uids must be explicitly denyed
+     */
+    @VisibleForTesting
+    public boolean isFirewallAllowList(final int chain) {
+        switch (chain) {
+            case FIREWALL_CHAIN_DOZABLE:
+            case FIREWALL_CHAIN_POWERSAVE:
+            case FIREWALL_CHAIN_RESTRICTED:
+            case FIREWALL_CHAIN_LOW_POWER_STANDBY:
+                return true;
+            case FIREWALL_CHAIN_STANDBY:
+            case FIREWALL_CHAIN_OEM_DENY_1:
+            case FIREWALL_CHAIN_OEM_DENY_2:
+            case FIREWALL_CHAIN_OEM_DENY_3:
+                return false;
+            default:
+                throw new ServiceSpecificException(EINVAL, "Invalid firewall chain: " + chain);
+        }
     }
 
     private void maybeThrow(final int err, final String msg) {
@@ -412,9 +436,17 @@
      *                                  cause of the failure.
      */
     public void setUidRule(final int childChain, final int uid, final int firewallRule) {
-        synchronized (sUidOwnerMap) {
-            final int err = native_setUidRule(childChain, uid, firewallRule);
-            maybeThrow(err, "Unable to set uid rule");
+        throwIfPreT("setUidRule is not available on pre-T devices");
+
+        final long match = getMatchByFirewallChain(childChain);
+        final boolean isAllowList = isFirewallAllowList(childChain);
+        final boolean add = (firewallRule == FIREWALL_RULE_ALLOW && isAllowList)
+                || (firewallRule == FIREWALL_RULE_DENY && !isAllowList);
+
+        if (add) {
+            addRule(uid, match, "setUidRule");
+        } else {
+            removeRule(uid, match, "setUidRule");
         }
     }
 
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index eb8313d..7050b42 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -453,7 +453,7 @@
      * direct device-originated data traffic of the specific UIDs to the correct
      * default network for each app.
      * Order ints passed to netd must be in the 0~999 range. Larger values code for
-     * a lower priority, {@see NativeUidRangeConfig}
+     * a lower priority, see {@link NativeUidRangeConfig}.
      *
      * Requests that don't code for a per-app preference use PREFERENCE_ORDER_INVALID.
      * The default request uses PREFERENCE_ORDER_DEFAULT.
diff --git a/service/src/com/android/server/connectivity/FullScore.java b/service/src/com/android/server/connectivity/FullScore.java
index b13ba93..b156045 100644
--- a/service/src/com/android/server/connectivity/FullScore.java
+++ b/service/src/com/android/server/connectivity/FullScore.java
@@ -76,16 +76,16 @@
     public static final int POLICY_IS_VPN = 62;
 
     // This network has been selected by the user manually from settings or a 3rd party app
-    // at least once. {@see NetworkAgentConfig#explicitlySelected}.
+    // at least once. @see NetworkAgentConfig#explicitlySelected.
     /** @hide */
     public static final int POLICY_EVER_USER_SELECTED = 61;
 
     // The user has indicated in UI that this network should be used even if it doesn't
-    // validate. {@see NetworkAgentConfig#acceptUnvalidated}.
+    // validate. @see NetworkAgentConfig#acceptUnvalidated.
     /** @hide */
     public static final int POLICY_ACCEPT_UNVALIDATED = 60;
 
-    // This network is unmetered. {@see NetworkCapabilities.NET_CAPABILITY_NOT_METERED}.
+    // This network is unmetered. @see NetworkCapabilities.NET_CAPABILITY_NOT_METERED.
     /** @hide */
     public static final int POLICY_IS_UNMETERED = 59;
 
diff --git a/tests/common/java/android/net/LinkPropertiesTest.java b/tests/common/java/android/net/LinkPropertiesTest.java
index 9ed2bb3..9506fc9 100644
--- a/tests/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/common/java/android/net/LinkPropertiesTest.java
@@ -1312,7 +1312,26 @@
         assertEquals(3, lp.getRoutes().size());
     }
 
-    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R) @IgnoreAfter(Build.VERSION_CODES.S_V2)
+    @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden when targeting T+")
+    @DisableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
+    public void testExcludedRoutesDisabled_S() {
+        final LinkProperties lp = new LinkProperties();
+        assertEquals(0, lp.getRoutes().size());
+
+        lp.addRoute(new RouteInfo(new IpPrefix(ADDRV4, 0), RTN_UNREACHABLE));
+        assertEquals(1, lp.getRoutes().size());
+
+        lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 5), RTN_THROW));
+        // RTN_THROW routes are visible on S when added by the caller (but they are not added by
+        // the system). This is uncommon usage but was tested by CTSv12.
+        assertEquals(2, lp.getRoutes().size());
+
+        lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 2), RTN_UNICAST));
+        assertEquals(3, lp.getRoutes().size());
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
     @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden when targeting T+")
     @DisableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
     public void testExcludedRoutesDisabled() {
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index db39e6f..2613363 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -31,15 +31,11 @@
 using std::set;
 using std::string;
 
+using android::bpf::isAtLeastKernelVersion;
 using android::modules::sdklevel::IsAtLeastR;
 using android::modules::sdklevel::IsAtLeastS;
 using android::modules::sdklevel::IsAtLeastT;
 
-// Mainline development branches lack the constant for the current development OS.
-#ifndef __ANDROID_API_T__
-#define __ANDROID_API_T__ 33
-#endif
-
 #define PLATFORM "/sys/fs/bpf/"
 #define TETHERING "/sys/fs/bpf/tethering/"
 #define PRIVATE "/sys/fs/bpf/net_private/"
@@ -49,7 +45,8 @@
 class BpfExistenceTest : public ::testing::Test {
 };
 
-static const set<string> INTRODUCED_R = {
+// Part of Android R platform, but mainlined in S
+static const set<string> PLATFORM_ONLY_IN_R = {
     PLATFORM "map_offload_tether_ingress_map",
     PLATFORM "map_offload_tether_limit_map",
     PLATFORM "map_offload_tether_stats_map",
@@ -57,7 +54,8 @@
     PLATFORM "prog_offload_schedcls_ingress_tether_rawip",
 };
 
-static const set<string> INTRODUCED_S = {
+// Provided by *current* mainline module for S+ devices
+static const set<string> MAINLINE_FOR_S_PLUS = {
     TETHERING "map_offload_tether_dev_map",
     TETHERING "map_offload_tether_downstream4_map",
     TETHERING "map_offload_tether_downstream64_map",
@@ -67,6 +65,7 @@
     TETHERING "map_offload_tether_stats_map",
     TETHERING "map_offload_tether_upstream4_map",
     TETHERING "map_offload_tether_upstream6_map",
+    TETHERING "map_test_bitmap",
     TETHERING "map_test_tether_downstream6_map",
     TETHERING "prog_offload_schedcls_tether_downstream4_ether",
     TETHERING "prog_offload_schedcls_tether_downstream4_rawip",
@@ -78,15 +77,13 @@
     TETHERING "prog_offload_schedcls_tether_upstream6_rawip",
 };
 
-static const set<string> REMOVED_S = {
-    PLATFORM "map_offload_tether_ingress_map",
-    PLATFORM "map_offload_tether_limit_map",
-    PLATFORM "map_offload_tether_stats_map",
-    PLATFORM "prog_offload_schedcls_ingress_tether_ether",
-    PLATFORM "prog_offload_schedcls_ingress_tether_rawip",
+// Provided by *current* mainline module for S+ devices with 5.10+ kernels
+static const set<string> MAINLINE_FOR_S_5_10_PLUS = {
+    TETHERING "prog_test_xdp_drop_ipv4_udp_ether",
 };
 
-static const set<string> INTRODUCED_T = {
+// Provided by *current* mainline module for T+ devices
+static const set<string> MAINLINE_FOR_T_PLUS = {
     SHARED "map_block_blocked_ports_map",
     SHARED "map_clatd_clat_egress4_map",
     SHARED "map_clatd_clat_ingress6_map",
@@ -121,58 +118,47 @@
     NETD "prog_netd_skfilter_ingress_xtbpf",
 };
 
-static const set<string> INTRODUCED_T_5_4 = {
+// 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",
-    SHARED "prog_dscp_policy_schedcls_set_dscp_ether",
-    SHARED "prog_dscp_policy_schedcls_set_dscp_raw_ip",
 };
 
-static const set<string> REMOVED_T = {
+// Provided by *current* mainline module for T+ devices with 5.15+ kernels
+static const set<string> MAINLINE_FOR_T_5_15_PLUS = {
+    SHARED "prog_dscp_policy_schedcls_set_dscp_ether",
+    SHARED "prog_dscp_policy_schedcls_set_dscp_raw_ip",
 };
 
 void addAll(set<string>* a, const set<string>& b) {
     a->insert(b.begin(), b.end());
 }
 
-void removeAll(set<string>* a, const set<string>& b) {
-    for (const auto& toRemove : b) {
-        a->erase(toRemove);
-    }
-}
+#define DO_EXPECT(B, V) do { \
+    if (B) addAll(expected, (V)); else addAll(unexpected, (V)); \
+} while (0)
 
 void getFileLists(set<string>* expected, set<string>* unexpected) {
     unexpected->clear();
     expected->clear();
 
-    addAll(unexpected, INTRODUCED_R);
-    addAll(unexpected, INTRODUCED_S);
-    addAll(unexpected, INTRODUCED_T);
+    // We do not actually check the platform P/Q (netd) and Q (clatd) things
+    // and only verify the mainline module relevant R+ offload maps & progs.
+    //
+    // The goal of this test is to verify compatibility with the tethering mainline module,
+    // and not to test the platform itself, which may have been modified by vendor or oems,
+    // so we should only test for the removal of stuff that was mainline'd,
+    // and for the presence of mainline stuff.
+    DO_EXPECT(IsAtLeastR() && !IsAtLeastS(), PLATFORM_ONLY_IN_R);
 
-    if (IsAtLeastR()) {
-        addAll(expected, INTRODUCED_R);
-        removeAll(unexpected, INTRODUCED_R);
-        // Nothing removed in R.
-    }
-
-    if (IsAtLeastS()) {
-        addAll(expected, INTRODUCED_S);
-        removeAll(expected, REMOVED_S);
-
-        addAll(unexpected, REMOVED_S);
-        removeAll(unexpected, INTRODUCED_S);
-    }
+    DO_EXPECT(IsAtLeastS(), MAINLINE_FOR_S_PLUS);
+    DO_EXPECT(IsAtLeastS() && isAtLeastKernelVersion(5, 10, 0), MAINLINE_FOR_S_5_10_PLUS);
 
     // Nothing added or removed in SCv2.
 
-    if (IsAtLeastT()) {
-        addAll(expected, INTRODUCED_T);
-        if (android::bpf::isAtLeastKernelVersion(5, 4, 0)) addAll(expected, INTRODUCED_T_5_4);
-        removeAll(expected, REMOVED_T);
-
-        addAll(unexpected, REMOVED_T);
-        removeAll(unexpected, INTRODUCED_T);
-    }
+    DO_EXPECT(IsAtLeastT(), MAINLINE_FOR_T_PLUS);
+    DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(5, 4, 0), MAINLINE_FOR_T_5_4_PLUS);
+    DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(5, 15, 0), MAINLINE_FOR_T_5_15_PLUS);
 }
 
 void checkFiles() {
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index 98d0905..0718952 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -24,6 +24,8 @@
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
 import static android.net.INetd.PERMISSION_INTERNET;
 
 import static com.android.server.BpfNetMaps.DOZABLE_MATCH;
@@ -124,12 +126,16 @@
         verify(mNetd).trafficSetNetPermForUids(PERMISSION_INTERNET, TEST_UIDS);
     }
 
-    private void doTestIsChainEnabled(final List<Integer> enableChains) throws Exception {
+    private long getMatch(final List<Integer> chains) {
         long match = 0;
-        for (final int chain: enableChains) {
+        for (final int chain: chains) {
             match |= mBpfNetMaps.getMatchByFirewallChain(chain);
         }
-        mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(match));
+        return match;
+    }
+
+    private void doTestIsChainEnabled(final List<Integer> enableChains) throws Exception {
+        mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(getMatch(enableChains)));
 
         for (final int chain: FIREWALL_CHAINS) {
             final String testCase = "EnabledChains: " + enableChains + " CheckedChain: " + chain;
@@ -557,4 +563,90 @@
         doTestRemoveUidInterfaceRules(NO_IIF, DOZABLE_MATCH,
                 NO_IIF, DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH);
     }
+
+    private void doTestSetUidRule(final List<Integer> testChains) throws Exception {
+        mUidOwnerMap.updateEntry(new U32(TEST_UID), new UidOwnerValue(TEST_IF_INDEX, IIF_MATCH));
+
+        for (final int chain: testChains) {
+            final int ruleToAddMatch = mBpfNetMaps.isFirewallAllowList(chain)
+                    ? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DENY;
+            mBpfNetMaps.setUidRule(chain, TEST_UID, ruleToAddMatch);
+        }
+
+        checkUidOwnerValue(TEST_UID, TEST_IF_INDEX, IIF_MATCH | getMatch(testChains));
+
+        for (final int chain: testChains) {
+            final int ruleToRemoveMatch = mBpfNetMaps.isFirewallAllowList(chain)
+                    ? FIREWALL_RULE_DENY : FIREWALL_RULE_ALLOW;
+            mBpfNetMaps.setUidRule(chain, TEST_UID, ruleToRemoveMatch);
+        }
+
+        checkUidOwnerValue(TEST_UID, TEST_IF_INDEX, IIF_MATCH);
+    }
+
+    private void doTestSetUidRule(final int testChain) throws Exception {
+        doTestSetUidRule(List.of(testChain));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetUidRule() throws Exception {
+        doTestSetUidRule(FIREWALL_CHAIN_DOZABLE);
+        doTestSetUidRule(FIREWALL_CHAIN_STANDBY);
+        doTestSetUidRule(FIREWALL_CHAIN_POWERSAVE);
+        doTestSetUidRule(FIREWALL_CHAIN_RESTRICTED);
+        doTestSetUidRule(FIREWALL_CHAIN_LOW_POWER_STANDBY);
+        doTestSetUidRule(FIREWALL_CHAIN_OEM_DENY_1);
+        doTestSetUidRule(FIREWALL_CHAIN_OEM_DENY_2);
+        doTestSetUidRule(FIREWALL_CHAIN_OEM_DENY_3);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetUidRuleMultipleChain() throws Exception {
+        doTestSetUidRule(List.of(
+                FIREWALL_CHAIN_DOZABLE,
+                FIREWALL_CHAIN_STANDBY));
+        doTestSetUidRule(List.of(
+                FIREWALL_CHAIN_DOZABLE,
+                FIREWALL_CHAIN_STANDBY,
+                FIREWALL_CHAIN_POWERSAVE,
+                FIREWALL_CHAIN_RESTRICTED));
+        doTestSetUidRule(FIREWALL_CHAINS);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetUidRuleRemoveRuleFromUidWithNoRule() {
+        final Class<ServiceSpecificException> expected = ServiceSpecificException.class;
+        assertThrows(expected,
+                () -> mBpfNetMaps.setUidRule(FIREWALL_CHAIN_DOZABLE, TEST_UID, FIREWALL_RULE_DENY));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetUidRuleInvalidChain() {
+        final Class<ServiceSpecificException> expected = ServiceSpecificException.class;
+        assertThrows(expected,
+                () -> mBpfNetMaps.setUidRule(-1 /* childChain */, TEST_UID, FIREWALL_RULE_ALLOW));
+        assertThrows(expected,
+                () -> mBpfNetMaps.setUidRule(1000 /* childChain */, TEST_UID, FIREWALL_RULE_ALLOW));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetUidRuleInvalidRule() {
+        final Class<ServiceSpecificException> expected = ServiceSpecificException.class;
+        assertThrows(expected, () ->
+                mBpfNetMaps.setUidRule(FIREWALL_CHAIN_DOZABLE, TEST_UID, -1 /* firewallRule */));
+        assertThrows(expected, () ->
+                mBpfNetMaps.setUidRule(FIREWALL_CHAIN_DOZABLE, TEST_UID, 1000 /* firewallRule */));
+    }
+
+    @Test
+    @IgnoreAfter(Build.VERSION_CODES.S_V2)
+    public void testSetUidRuleBeforeT() {
+        assertThrows(UnsupportedOperationException.class, () ->
+                mBpfNetMaps.setUidRule(FIREWALL_CHAIN_DOZABLE, TEST_UID, FIREWALL_RULE_ALLOW));
+    }
 }
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 9365bee..58d002a 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -86,10 +86,15 @@
 @SmallTest
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
 public class NsdServiceTest {
-
     static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
     private static final long CLEANUP_DELAY_MS = 500;
     private static final long TIMEOUT_MS = 500;
+    private static final String SERVICE_NAME = "a_name";
+    private static final String SERVICE_TYPE = "a_type";
+    private static final String SERVICE_FULL_NAME = SERVICE_NAME + "." + SERVICE_TYPE;
+    private static final String DOMAIN_NAME = "mytestdevice.local";
+    private static final int PORT = 2201;
+    private static final int IFACE_IDX_ANY = 0;
 
     // Records INsdManagerCallback created when NsdService#connect is called.
     // Only accessed on the test thread, since NsdService#connect is called by the NsdManager
@@ -103,6 +108,7 @@
     @Mock MDnsManager mMockMDnsM;
     HandlerThread mThread;
     TestHandler mHandler;
+    NsdService mService;
 
     private static class LinkToDeathRecorder extends Binder {
         IBinder.DeathRecipient mDr;
@@ -134,6 +140,8 @@
         doReturn(true).when(mMockMDnsM).discover(anyInt(), anyString(), anyInt());
         doReturn(true).when(mMockMDnsM).resolve(
                 anyInt(), anyString(), anyString(), anyString(), anyInt());
+
+        mService = makeService();
     }
 
     @After
@@ -147,18 +155,14 @@
     @Test
     @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
     public void testPreSClients() throws Exception {
-        NsdService service = makeService();
-
         // Pre S client connected, the daemon should be started.
-        connectClient(service);
-        waitForIdle();
+        connectClient(mService);
         final INsdManagerCallback cb1 = getCallback();
         final IBinder.DeathRecipient deathRecipient1 = verifyLinkToDeath(cb1);
         verify(mMockMDnsM, times(1)).registerEventListener(any());
         verify(mMockMDnsM, times(1)).startDaemon();
 
-        connectClient(service);
-        waitForIdle();
+        connectClient(mService);
         final INsdManagerCallback cb2 = getCallback();
         final IBinder.DeathRecipient deathRecipient2 = verifyLinkToDeath(cb2);
         // Daemon has been started, it should not try to start it again.
@@ -178,19 +182,15 @@
     @Test
     @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
     public void testNoDaemonStartedWhenClientsConnect() throws Exception {
-        final NsdService service = makeService();
-
         // Creating an NsdManager will not cause daemon startup.
-        connectClient(service);
-        waitForIdle();
+        connectClient(mService);
         verify(mMockMDnsM, never()).registerEventListener(any());
         verify(mMockMDnsM, never()).startDaemon();
         final INsdManagerCallback cb1 = getCallback();
         final IBinder.DeathRecipient deathRecipient1 = verifyLinkToDeath(cb1);
 
         // Creating another NsdManager will not cause daemon startup either.
-        connectClient(service);
-        waitForIdle();
+        connectClient(mService);
         verify(mMockMDnsM, never()).registerEventListener(any());
         verify(mMockMDnsM, never()).startDaemon();
         final INsdManagerCallback cb2 = getCallback();
@@ -216,70 +216,66 @@
     @Test
     @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
     public void testClientRequestsAreGCedAtDisconnection() throws Exception {
-        NsdService service = makeService();
-
-        NsdManager client = connectClient(service);
-        waitForIdle();
+        final NsdManager client = connectClient(mService);
         final INsdManagerCallback cb1 = getCallback();
         final IBinder.DeathRecipient deathRecipient = verifyLinkToDeath(cb1);
         verify(mMockMDnsM, never()).registerEventListener(any());
         verify(mMockMDnsM, never()).startDaemon();
 
-        NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
-        request.setPort(2201);
+        final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
+        request.setPort(PORT);
 
         // Client registration request
         NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
         client.registerService(request, PROTOCOL, listener1);
         waitForIdle();
-        verify(mMockMDnsM, times(1)).registerEventListener(any());
-        verify(mMockMDnsM, times(1)).startDaemon();
-        verify(mMockMDnsM, times(1)).registerService(
-                eq(2), eq("a_name"), eq("a_type"), eq(2201), any(), eq(0));
+        verify(mMockMDnsM).registerEventListener(any());
+        verify(mMockMDnsM).startDaemon();
+        verify(mMockMDnsM).registerService(
+                eq(2), eq(SERVICE_NAME), eq(SERVICE_TYPE), eq(PORT), any(), eq(IFACE_IDX_ANY));
 
         // Client discovery request
         NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
-        client.discoverServices("a_type", PROTOCOL, listener2);
+        client.discoverServices(SERVICE_TYPE, PROTOCOL, listener2);
         waitForIdle();
-        verify(mMockMDnsM, times(1)).discover(eq(3), eq("a_type"), eq(0));
+        verify(mMockMDnsM).discover(3 /* id */, SERVICE_TYPE, IFACE_IDX_ANY);
 
         // Client resolve request
         NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
         client.resolveService(request, listener3);
         waitForIdle();
-        verify(mMockMDnsM, times(1)).resolve(
-                eq(4), eq("a_name"), eq("a_type"), eq("local."), eq(0));
+        verify(mMockMDnsM).resolve(
+                4 /* id */, SERVICE_NAME, SERVICE_TYPE, "local." /* domain */, IFACE_IDX_ANY);
 
         // Client disconnects, stop the daemon after CLEANUP_DELAY_MS.
         deathRecipient.binderDied();
         verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
         // checks that request are cleaned
-        verify(mMockMDnsM, times(1)).stopOperation(eq(2));
-        verify(mMockMDnsM, times(1)).stopOperation(eq(3));
-        verify(mMockMDnsM, times(1)).stopOperation(eq(4));
+        verify(mMockMDnsM).stopOperation(2 /* id */);
+        verify(mMockMDnsM).stopOperation(3 /* id */);
+        verify(mMockMDnsM).stopOperation(4 /* id */);
     }
 
     @Test
     @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
     public void testCleanupDelayNoRequestActive() throws Exception {
-        NsdService service = makeService();
-        NsdManager client = connectClient(service);
+        final NsdManager client = connectClient(mService);
 
-        NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
-        request.setPort(2201);
+        final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
+        request.setPort(PORT);
         NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
         client.registerService(request, PROTOCOL, listener1);
         waitForIdle();
-        verify(mMockMDnsM, times(1)).registerEventListener(any());
-        verify(mMockMDnsM, times(1)).startDaemon();
+        verify(mMockMDnsM).registerEventListener(any());
+        verify(mMockMDnsM).startDaemon();
         final INsdManagerCallback cb1 = getCallback();
         final IBinder.DeathRecipient deathRecipient = verifyLinkToDeath(cb1);
-        verify(mMockMDnsM, times(1)).registerService(
-                eq(2), eq("a_name"), eq("a_type"), eq(2201), any(), eq(0));
+        verify(mMockMDnsM).registerService(
+                eq(2), eq(SERVICE_NAME), eq(SERVICE_TYPE), eq(PORT), any(), eq(IFACE_IDX_ANY));
 
         client.unregisterService(listener1);
         waitForIdle();
-        verify(mMockMDnsM, times(1)).stopOperation(eq(2));
+        verify(mMockMDnsM).stopOperation(2 /* id */);
 
         verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
         reset(mMockMDnsM);
@@ -291,33 +287,28 @@
 
     @Test
     public void testDiscoverOnTetheringDownstream() throws Exception {
-        NsdService service = makeService();
-        NsdManager client = connectClient(service);
-
-        final String serviceType = "a_type";
-        final String serviceName = "a_name";
-        final String domainName = "mytestdevice.local";
+        final NsdManager client = connectClient(mService);
         final int interfaceIdx = 123;
         final NsdManager.DiscoveryListener discListener = mock(NsdManager.DiscoveryListener.class);
-        client.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discListener);
+        client.discoverServices(SERVICE_TYPE, PROTOCOL, discListener);
         waitForIdle();
 
         final ArgumentCaptor<IMDnsEventListener> listenerCaptor =
                 ArgumentCaptor.forClass(IMDnsEventListener.class);
         verify(mMockMDnsM).registerEventListener(listenerCaptor.capture());
         final ArgumentCaptor<Integer> discIdCaptor = ArgumentCaptor.forClass(Integer.class);
-        verify(mMockMDnsM).discover(discIdCaptor.capture(), eq(serviceType),
+        verify(mMockMDnsM).discover(discIdCaptor.capture(), eq(SERVICE_TYPE),
                 eq(0) /* interfaceIdx */);
         // NsdManager uses a separate HandlerThread to dispatch callbacks (on ServiceHandler), so
         // this needs to use a timeout
-        verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStarted(serviceType);
+        verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStarted(SERVICE_TYPE);
 
         final DiscoveryInfo discoveryInfo = new DiscoveryInfo(
                 discIdCaptor.getValue(),
                 IMDnsEventListener.SERVICE_FOUND,
-                serviceName,
-                serviceType,
-                domainName,
+                SERVICE_NAME,
+                SERVICE_TYPE,
+                DOMAIN_NAME,
                 interfaceIdx,
                 INetd.LOCAL_NET_ID); // LOCAL_NET_ID (99) used on tethering downstreams
         final IMDnsEventListener eventListener = listenerCaptor.getValue();
@@ -328,8 +319,8 @@
                 ArgumentCaptor.forClass(NsdServiceInfo.class);
         verify(discListener, timeout(TIMEOUT_MS)).onServiceFound(discoveredInfoCaptor.capture());
         final NsdServiceInfo foundInfo = discoveredInfoCaptor.getValue();
-        assertEquals(serviceName, foundInfo.getServiceName());
-        assertEquals(serviceType, foundInfo.getServiceType());
+        assertEquals(SERVICE_NAME, foundInfo.getServiceName());
+        assertEquals(SERVICE_TYPE, foundInfo.getServiceType());
         assertNull(foundInfo.getHost());
         assertNull(foundInfo.getNetwork());
         assertEquals(interfaceIdx, foundInfo.getInterfaceIndex());
@@ -340,19 +331,18 @@
         waitForIdle();
 
         final ArgumentCaptor<Integer> resolvIdCaptor = ArgumentCaptor.forClass(Integer.class);
-        verify(mMockMDnsM).resolve(resolvIdCaptor.capture(), eq(serviceName), eq(serviceType),
+        verify(mMockMDnsM).resolve(resolvIdCaptor.capture(), eq(SERVICE_NAME), eq(SERVICE_TYPE),
                 eq("local.") /* domain */, eq(interfaceIdx));
 
         final int servicePort = 10123;
-        final String serviceFullName = serviceName + "." + serviceType;
         final ResolutionInfo resolutionInfo = new ResolutionInfo(
                 resolvIdCaptor.getValue(),
                 IMDnsEventListener.SERVICE_RESOLVED,
                 null /* serviceName */,
                 null /* serviceType */,
                 null /* domain */,
-                serviceFullName,
-                domainName,
+                SERVICE_FULL_NAME,
+                DOMAIN_NAME,
                 servicePort,
                 new byte[0] /* txtRecord */,
                 interfaceIdx);
@@ -362,14 +352,14 @@
         waitForIdle();
 
         final ArgumentCaptor<Integer> getAddrIdCaptor = ArgumentCaptor.forClass(Integer.class);
-        verify(mMockMDnsM).getServiceAddress(getAddrIdCaptor.capture(), eq(domainName),
+        verify(mMockMDnsM).getServiceAddress(getAddrIdCaptor.capture(), eq(DOMAIN_NAME),
                 eq(interfaceIdx));
 
         final String serviceAddress = "192.0.2.123";
         final GetAddressInfo addressInfo = new GetAddressInfo(
                 getAddrIdCaptor.getValue(),
                 IMDnsEventListener.SERVICE_GET_ADDR_SUCCESS,
-                serviceFullName,
+                SERVICE_FULL_NAME,
                 serviceAddress,
                 interfaceIdx,
                 INetd.LOCAL_NET_ID);
@@ -380,8 +370,8 @@
                 ArgumentCaptor.forClass(NsdServiceInfo.class);
         verify(resolveListener, timeout(TIMEOUT_MS)).onServiceResolved(resInfoCaptor.capture());
         final NsdServiceInfo resolvedService = resInfoCaptor.getValue();
-        assertEquals(serviceName, resolvedService.getServiceName());
-        assertEquals("." + serviceType, resolvedService.getServiceType());
+        assertEquals(SERVICE_NAME, resolvedService.getServiceName());
+        assertEquals("." + SERVICE_TYPE, resolvedService.getServiceType());
         assertEquals(InetAddresses.parseNumericAddress(serviceAddress), resolvedService.getHost());
         assertEquals(servicePort, resolvedService.getPort());
         assertNull(resolvedService.getNetwork());
@@ -415,7 +405,10 @@
     }
 
     NsdManager connectClient(NsdService service) {
-        return new NsdManager(mContext, service);
+        final NsdManager nsdManager = new NsdManager(mContext, service);
+        // Wait for client registration done.
+        waitForIdle();
+        return nsdManager;
     }
 
     void verifyDelayMaybeStopDaemon(long cleanupDelayMs) throws Exception {
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
index 2178b33..8a18ee7 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
@@ -726,4 +726,16 @@
         triggerOnProvisioningSuccess();
         verifyRestart(initialIpConfig);
     }
+
+    @Test
+    public void testOnNetworkNeededOnStaleNetworkOffer() throws Exception {
+        initEthernetNetworkFactory();
+        createAndVerifyProvisionedInterface(TEST_IFACE);
+        mNetFactory.updateInterfaceLinkState(TEST_IFACE, false, null);
+        verify(mNetworkProvider).unregisterNetworkOffer(mNetworkOfferCallback);
+        // It is possible that even after a network offer is unregistered, CS still sends it
+        // onNetworkNeeded() callbacks.
+        mNetworkOfferCallback.onNetworkNeeded(createDefaultRequest());
+        verify(mIpClient, never()).startProvisioning(any());
+    }
 }