Merge "offload - finish ipv4 tethering"
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 7427481..73175580 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -62,7 +62,10 @@
         "com.android.tethering",
     ],
     min_sdk_version: "30",
-    header_libs: ["bpf_syscall_wrappers"],
+    header_libs: [
+        "bpf_syscall_wrappers",
+        "bpf_tethering_headers",
+    ],
     srcs: [
         "jni/*.cpp",
     ],
diff --git a/Tethering/bpf_progs/Android.bp b/Tethering/bpf_progs/Android.bp
index d62e9a1..b06528b 100644
--- a/Tethering/bpf_progs/Android.bp
+++ b/Tethering/bpf_progs/Android.bp
@@ -15,6 +15,26 @@
 //
 
 //
+// struct definitions shared with JNI
+//
+cc_library_headers {
+    name: "bpf_tethering_headers",
+    vendor_available: false,
+    host_supported: false,
+    export_include_dirs: ["."],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    sdk_version: "30",
+    min_sdk_version: "30",
+    apex_available: ["com.android.tethering"],
+    visibility: [
+        "//packages/modules/Connectivity/Tethering",
+    ],
+}
+
+//
 // bpf kernel programs
 //
 bpf {
diff --git a/Tethering/bpf_progs/bpf_tethering.h b/Tethering/bpf_progs/bpf_tethering.h
new file mode 100644
index 0000000..d83ae34
--- /dev/null
+++ b/Tethering/bpf_progs/bpf_tethering.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+// Common definitions for BPF code in the tethering mainline module.
+// These definitions are available to:
+// - The BPF programs in Tethering/bpf_progs/
+// - JNI code that depends on the bpf_tethering_headers library.
+
+#define BPF_TETHER_ERRORS    \
+    ERR(INVALID_IP_VERSION)  \
+    ERR(LOW_TTL)             \
+    ERR(INVALID_TCP_HEADER)  \
+    ERR(TCP_CONTROL_PACKET)  \
+    ERR(NON_GLOBAL_SRC)      \
+    ERR(NON_GLOBAL_DST)      \
+    ERR(LOCAL_SRC_DST)       \
+    ERR(NO_STATS_ENTRY)      \
+    ERR(NO_LIMIT_ENTRY)      \
+    ERR(BELOW_IPV6_MTU)      \
+    ERR(LIMIT_REACHED)       \
+    ERR(CHANGE_HEAD_FAILED)  \
+    ERR(TOO_SHORT)           \
+    ERR(_MAX)
+
+#define ERR(x) BPF_TETHER_ERR_ ##x,
+enum {
+    BPF_TETHER_ERRORS
+};
+#undef ERR
+
+#define ERR(x) #x,
+static const char *bpf_tether_errors[] = {
+    BPF_TETHER_ERRORS
+};
+#undef ERR
diff --git a/Tethering/bpf_progs/offload.c b/Tethering/bpf_progs/offload.c
index 8a2e6bc..0b2c08d 100644
--- a/Tethering/bpf_progs/offload.c
+++ b/Tethering/bpf_progs/offload.c
@@ -26,6 +26,7 @@
 
 #include "bpf_helpers.h"
 #include "bpf_net_helpers.h"
+#include "bpf_tethering.h"
 #include "netdbpf/bpf_shared.h"
 
 // From kernel:include/net/ip.h
@@ -87,6 +88,19 @@
 DEFINE_BPF_MAP_GRW(tether_upstream6_map, HASH, TetherUpstream6Key, Tether6Value, 64,
                    AID_NETWORK_STACK)
 
+DEFINE_BPF_MAP_GRW(tether_error_map, ARRAY, __u32, __u32, BPF_TETHER_ERR__MAX,
+                   AID_NETWORK_STACK)
+
+#define COUNT_AND_RETURN(counter, ret) do {                    \
+    __u32 code = BPF_TETHER_ERR_ ## counter;                 \
+    __u32 *count = bpf_tether_error_map_lookup_elem(&code);  \
+    if (count) __sync_fetch_and_add(count, 1);               \
+    return ret;                                              \
+} while(0)
+
+#define DROP(counter) COUNT_AND_RETURN(counter, TC_ACT_SHOT)
+#define PUNT(counter) COUNT_AND_RETURN(counter, TC_ACT_OK)
+
 static inline __always_inline int do_forward6(struct __sk_buff* skb, const bool is_ethernet,
         const bool downstream) {
     const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
@@ -108,11 +122,11 @@
     if (is_ethernet && (eth->h_proto != htons(ETH_P_IPV6))) return TC_ACT_OK;
 
     // IP version must be 6
-    if (ip6->version != 6) return TC_ACT_OK;
+    if (ip6->version != 6) PUNT(INVALID_IP_VERSION);
 
     // Cannot decrement during forward if already zero or would be zero,
     // Let the kernel's stack handle these cases and generate appropriate ICMP errors.
-    if (ip6->hop_limit <= 1) return TC_ACT_OK;
+    if (ip6->hop_limit <= 1) PUNT(LOW_TTL);
 
     // If hardware offload is running and programming flows based on conntrack entries,
     // try not to interfere with it.
@@ -120,27 +134,28 @@
         struct tcphdr* tcph = (void*)(ip6 + 1);
 
         // Make sure we can get at the tcp header
-        if (data + l2_header_size + sizeof(*ip6) + sizeof(*tcph) > data_end) return TC_ACT_OK;
+        if (data + l2_header_size + sizeof(*ip6) + sizeof(*tcph) > data_end)
+            PUNT(INVALID_TCP_HEADER);
 
         // Do not offload TCP packets with any one of the SYN/FIN/RST flags
-        if (tcph->syn || tcph->fin || tcph->rst) return TC_ACT_OK;
+        if (tcph->syn || tcph->fin || tcph->rst) PUNT(TCP_CONTROL_PACKET);
     }
 
     // Protect against forwarding packets sourced from ::1 or fe80::/64 or other weirdness.
     __be32 src32 = ip6->saddr.s6_addr32[0];
     if (src32 != htonl(0x0064ff9b) &&                        // 64:ff9b:/32 incl. XLAT464 WKP
         (src32 & htonl(0xe0000000)) != htonl(0x20000000))    // 2000::/3 Global Unicast
-        return TC_ACT_OK;
+        PUNT(NON_GLOBAL_SRC);
 
     // Protect against forwarding packets destined to ::1 or fe80::/64 or other weirdness.
     __be32 dst32 = ip6->daddr.s6_addr32[0];
     if (dst32 != htonl(0x0064ff9b) &&                        // 64:ff9b:/32 incl. XLAT464 WKP
         (dst32 & htonl(0xe0000000)) != htonl(0x20000000))    // 2000::/3 Global Unicast
-        return TC_ACT_OK;
+        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]))
-        return TC_ACT_OK;
+        PUNT(LOCAL_SRC_DST);
 
     TetherDownstream6Key kd = {
             .iif = skb->ifindex,
@@ -162,15 +177,15 @@
     TetherStatsValue* stat_v = bpf_tether_stats_map_lookup_elem(&stat_and_limit_k);
 
     // If we don't have anywhere to put stats, then abort...
-    if (!stat_v) return TC_ACT_OK;
+    if (!stat_v) PUNT(NO_STATS_ENTRY);
 
     uint64_t* limit_v = bpf_tether_limit_map_lookup_elem(&stat_and_limit_k);
 
     // If we don't have a limit, then abort...
-    if (!limit_v) return TC_ACT_OK;
+    if (!limit_v) PUNT(NO_LIMIT_ENTRY);
 
     // Required IPv6 minimum mtu is 1280, below that not clear what we should do, abort...
-    if (v->pmtu < IPV6_MIN_MTU) return TC_ACT_OK;
+    if (v->pmtu < IPV6_MIN_MTU) PUNT(BELOW_IPV6_MTU);
 
     // Approximate handling of TCP/IPv6 overhead for incoming LRO/GRO packets: default
     // outbound path mtu of 1500 is not necessarily correct, but worst case we simply
@@ -195,7 +210,7 @@
     // a packet we let the core stack deal with things.
     // (The core stack needs to handle limits correctly anyway,
     // since we don't offload all traffic in both directions)
-    if (stat_v->rxBytes + stat_v->txBytes + bytes > *limit_v) return TC_ACT_OK;
+    if (stat_v->rxBytes + stat_v->txBytes + bytes > *limit_v) PUNT(LIMIT_REACHED);
 
     if (!is_ethernet) {
         // Try to inject an ethernet header, and simply return if we fail.
@@ -203,7 +218,7 @@
         // 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);
-            return TC_ACT_OK;
+            PUNT(CHANGE_HEAD_FAILED);
         }
 
         // bpf_skb_change_head() invalidates all pointers - reload them
@@ -215,7 +230,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);
-            return TC_ACT_SHOT;
+            DROP(TOO_SHORT);
         }
     };
 
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index 42f83bf..67e6f0d 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -70,6 +70,7 @@
     test_config: "AndroidTest_Coverage.xml",
     defaults: ["libnetworkstackutilsjni_deps"],
     static_libs: [
+        "NetdStaticLibTestsLib",
         "NetworkStaticLibTestsLib",
         "NetworkStackTestsLib",
         "TetheringTestsLib",
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MeterednessConfigurationRule.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MeterednessConfigurationRule.java
index 8fadf9e..5c99c67 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MeterednessConfigurationRule.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MeterednessConfigurationRule.java
@@ -15,21 +15,20 @@
  */
 package com.android.cts.net.hostside;
 
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.resetMeteredNetwork;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setupMeteredNetwork;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setupActiveNetworkMeteredness;
 import static com.android.cts.net.hostside.Property.METERED_NETWORK;
 import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
 
 import android.util.ArraySet;
-import android.util.Pair;
 
 import com.android.compatibility.common.util.BeforeAfterRule;
+import com.android.compatibility.common.util.ThrowingRunnable;
 
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
 public class MeterednessConfigurationRule extends BeforeAfterRule {
-    private Pair<String, Boolean> mSsidAndInitialMeteredness;
+    private ThrowingRunnable mMeterednessResetter;
 
     @Override
     public void onBefore(Statement base, Description description) throws Throwable {
@@ -48,13 +47,13 @@
     }
 
     public void configureNetworkMeteredness(boolean metered) throws Exception {
-        mSsidAndInitialMeteredness = setupMeteredNetwork(metered);
+        mMeterednessResetter = setupActiveNetworkMeteredness(metered);
     }
 
     public void resetNetworkMeteredness() throws Exception {
-        if (mSsidAndInitialMeteredness != null) {
-            resetMeteredNetwork(mSsidAndInitialMeteredness.first,
-                    mSsidAndInitialMeteredness.second);
+        if (mMeterednessResetter != null) {
+            mMeterednessResetter.run();
+            mMeterednessResetter = null;
         }
     }
 }
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
index 2ac29e7..955317b 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
@@ -17,16 +17,13 @@
 package com.android.cts.net.hostside;
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isActiveNetworkMetered;
 import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
 import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
@@ -186,7 +183,7 @@
     public void setUp() throws Exception {
         super.setUp();
 
-        assumeTrue(isActiveNetworkMetered(true) || canChangeActiveNetworkMeteredness());
+        assumeTrue(canChangeActiveNetworkMeteredness());
 
         registerBroadcastReceiver();
 
@@ -198,13 +195,13 @@
         setBatterySaverMode(false);
         setRestrictBackground(false);
 
-        // Make wifi a metered network.
+        // Mark network as metered.
         mMeterednessConfiguration.configureNetworkMeteredness(true);
 
         // Register callback
         registerNetworkCallback((INetworkCallback.Stub) mTestNetworkCallback);
-        // Once the wifi is marked as metered, the wifi will reconnect. Wait for onAvailable()
-        // callback to ensure wifi is connected before the test and store the default network.
+        // Wait for onAvailable() callback to ensure network is available before the test
+        // and store the default network.
         mNetwork = mTestNetworkCallback.expectAvailableCallbackAndGetNetwork();
         // Check that the network is metered.
         mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
index 3041dfa..e05fbea 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
@@ -20,14 +20,17 @@
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
+import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TEST_PKG;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -41,24 +44,30 @@
 import android.net.NetworkCapabilities;
 import android.net.wifi.WifiManager;
 import android.os.Process;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionPlan;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.Pair;
+
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.AppStandbyUtils;
 import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.ThrowingRunnable;
 
+import java.time.Period;
+import java.time.ZonedDateTime;
+import java.util.Arrays;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-import androidx.test.platform.app.InstrumentationRegistry;
-
 public class NetworkPolicyTestUtils {
 
     private static final int TIMEOUT_CHANGE_METEREDNESS_MS = 10_000;
 
     private static ConnectivityManager mCm;
     private static WifiManager mWm;
+    private static SubscriptionManager mSm;
 
     private static Boolean mBatterySaverSupported;
     private static Boolean mDataSaverSupported;
@@ -135,16 +144,40 @@
     }
 
     public static boolean canChangeActiveNetworkMeteredness() {
-        final Network activeNetwork = getConnectivityManager().getActiveNetwork();
-        final NetworkCapabilities networkCapabilities
-                = getConnectivityManager().getNetworkCapabilities(activeNetwork);
-        return networkCapabilities.hasTransport(TRANSPORT_WIFI);
+        final NetworkCapabilities networkCapabilities = getActiveNetworkCapabilities();
+        return networkCapabilities.hasTransport(TRANSPORT_WIFI)
+                || networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
     }
 
-    public static Pair<String, Boolean> setupMeteredNetwork(boolean metered) throws Exception {
+    /**
+     * Updates the meteredness of the active network. Right now we can only change meteredness
+     * of either Wifi or cellular network, so if the active network is not either of these, this
+     * will throw an exception.
+     *
+     * @return a {@link ThrowingRunnable} object that can used to reset the meteredness change
+     *         made by this method.
+     */
+    public static ThrowingRunnable setupActiveNetworkMeteredness(boolean metered) throws Exception {
         if (isActiveNetworkMetered(metered)) {
             return null;
         }
+        final NetworkCapabilities networkCapabilities = getActiveNetworkCapabilities();
+        if (networkCapabilities.hasTransport(TRANSPORT_WIFI)) {
+            final String ssid = getWifiSsid();
+            setWifiMeteredStatus(ssid, metered);
+            return () -> setWifiMeteredStatus(ssid, !metered);
+        } else if (networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+            final int subId = SubscriptionManager.getActiveDataSubscriptionId();
+            setCellularMeteredStatus(subId, metered);
+            return () -> setCellularMeteredStatus(subId, !metered);
+        } else {
+            // Right now, we don't have a way to change meteredness of networks other
+            // than Wi-Fi or Cellular, so just throw an exception.
+            throw new IllegalStateException("Can't change meteredness of current active network");
+        }
+    }
+
+    private static String getWifiSsid() {
         final boolean isLocationEnabled = isLocationEnabled();
         try {
             if (!isLocationEnabled) {
@@ -152,8 +185,7 @@
             }
             final String ssid = unquoteSSID(getWifiManager().getConnectionInfo().getSSID());
             assertNotEquals(WifiManager.UNKNOWN_SSID, ssid);
-            setWifiMeteredStatus(ssid, metered);
-            return Pair.create(ssid, !metered);
+            return ssid;
         } finally {
             // Reset the location enabled state
             if (!isLocationEnabled) {
@@ -162,11 +194,13 @@
         }
     }
 
-    public static void resetMeteredNetwork(String ssid, boolean metered) throws Exception {
-        setWifiMeteredStatus(ssid, metered);
+    private static NetworkCapabilities getActiveNetworkCapabilities() {
+        final Network activeNetwork = getConnectivityManager().getActiveNetwork();
+        assertNotNull("No active network available", activeNetwork);
+        return getConnectivityManager().getNetworkCapabilities(activeNetwork);
     }
 
-    public static void setWifiMeteredStatus(String ssid, boolean metered) throws Exception {
+    private static void setWifiMeteredStatus(String ssid, boolean metered) throws Exception {
         assertFalse("SSID should not be empty", TextUtils.isEmpty(ssid));
         final String cmd = "cmd netpolicy set metered-network " + ssid + " " + metered;
         executeShellCommand(cmd);
@@ -174,15 +208,29 @@
         assertActiveNetworkMetered(metered);
     }
 
-    public static void assertWifiMeteredStatus(String ssid, boolean expectedMeteredStatus) {
+    private static void assertWifiMeteredStatus(String ssid, boolean expectedMeteredStatus) {
         final String result = executeShellCommand("cmd netpolicy list wifi-networks");
         final String expectedLine = ssid + ";" + expectedMeteredStatus;
         assertTrue("Expected line: " + expectedLine + "; Actual result: " + result,
                 result.contains(expectedLine));
     }
 
+    private static void setCellularMeteredStatus(int subId, boolean metered) throws Exception {
+        setSubPlanOwner(subId, TEST_PKG);
+        try {
+            getSubscriptionManager().setSubscriptionPlans(subId,
+                    Arrays.asList(buildValidSubscriptionPlan(System.currentTimeMillis())));
+            final boolean unmeteredOverride = !metered;
+            getSubscriptionManager().setSubscriptionOverrideUnmetered(subId, unmeteredOverride,
+                    /*timeoutMillis=*/ 0);
+            assertActiveNetworkMetered(metered);
+        } finally {
+            setSubPlanOwner(subId, null);
+        }
+    }
+
     // Copied from cts/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
-    public static void assertActiveNetworkMetered(boolean expectedMeteredStatus) throws Exception {
+    private static void assertActiveNetworkMetered(boolean expectedMeteredStatus) throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         final NetworkCallback networkCallback = new NetworkCallback() {
             @Override
@@ -197,12 +245,29 @@
         // with the current setting. Therefore, if the setting has already been changed,
         // this method will return right away, and if not it will wait for the setting to change.
         getConnectivityManager().registerDefaultNetworkCallback(networkCallback);
-        if (!latch.await(TIMEOUT_CHANGE_METEREDNESS_MS, TimeUnit.MILLISECONDS)) {
-            fail("Timed out waiting for active network metered status to change to "
-                    + expectedMeteredStatus + " ; network = "
-                    + getConnectivityManager().getActiveNetwork());
+        try {
+            if (!latch.await(TIMEOUT_CHANGE_METEREDNESS_MS, TimeUnit.MILLISECONDS)) {
+                fail("Timed out waiting for active network metered status to change to "
+                        + expectedMeteredStatus + "; network = "
+                        + getConnectivityManager().getActiveNetwork());
+            }
+        } finally {
+            getConnectivityManager().unregisterNetworkCallback(networkCallback);
         }
-        getConnectivityManager().unregisterNetworkCallback(networkCallback);
+    }
+
+    private static void setSubPlanOwner(int subId, String packageName) {
+        executeShellCommand("cmd netpolicy set sub-plan-owner " + subId + " " + packageName);
+    }
+
+    private static SubscriptionPlan buildValidSubscriptionPlan(long dataUsageTime) {
+        return SubscriptionPlan.Builder
+                .createRecurring(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"),
+                        Period.ofMonths(1))
+                .setTitle("CTS")
+                .setDataLimit(1_000_000_000, SubscriptionPlan.LIMIT_BEHAVIOR_DISABLED)
+                .setDataUsage(500_000_000, dataUsageTime)
+                .build();
     }
 
     public static void setRestrictBackground(boolean enabled) {
@@ -274,6 +339,14 @@
         return mWm;
     }
 
+    public static SubscriptionManager getSubscriptionManager() {
+        if (mSm == null) {
+            mSm = (SubscriptionManager) getContext().getSystemService(
+                    Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        }
+        return mSm;
+    }
+
     public static Context getContext() {
         return getInstrumentation().getContext();
     }
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
index 590e17e..1c9ff05 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
@@ -138,7 +138,7 @@
                     }
                 }
             };
-            mCm.registerNetworkCallback(makeWifiNetworkRequest(), mNetworkCallback);
+            mCm.registerNetworkCallback(makeNetworkRequest(), mNetworkCallback);
             try {
                 cb.asBinder().linkToDeath(() -> unregisterNetworkCallback(), 0);
             } catch (RemoteException e) {
@@ -156,9 +156,8 @@
         }
       };
 
-    private NetworkRequest makeWifiNetworkRequest() {
+    private NetworkRequest makeNetworkRequest() {
         return new NetworkRequest.Builder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build();
     }
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index cbf43e7..ce00652 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -25,10 +25,12 @@
 import static android.content.pm.PackageManager.FEATURE_WIFI;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.cts.util.CtsNetUtils.ConnectivityActionReceiver;
 import static android.net.cts.util.CtsNetUtils.HTTP_PORT;
@@ -43,6 +45,7 @@
 
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.testutils.MiscAsserts.assertThrows;
 import static com.android.testutils.TestPermissionUtil.runAsShell;
 
 import static org.junit.Assert.assertEquals;
@@ -68,8 +71,10 @@
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
+import android.net.InetAddresses;
 import android.net.IpSecManager;
 import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -80,6 +85,9 @@
 import android.net.NetworkRequest;
 import android.net.NetworkUtils;
 import android.net.SocketKeepalive;
+import android.net.StringNetworkSpecifier;
+import android.net.TestNetworkInterface;
+import android.net.TestNetworkManager;
 import android.net.cts.util.CtsNetUtils;
 import android.net.util.KeepaliveUtils;
 import android.net.wifi.WifiManager;
@@ -104,10 +112,10 @@
 import com.android.testutils.SkipPresubmit;
 import com.android.testutils.TestableNetworkCallback;
 
-import libcore.io.Streams;
-
 import junit.framework.AssertionFailedError;
 
+import libcore.io.Streams;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -176,6 +184,9 @@
     private static final String KEEPALIVE_RESERVED_PER_SLOT_RES_NAME =
             "config_reservedPrivilegedKeepaliveSlots";
 
+    private static final LinkAddress TEST_LINKADDR = new LinkAddress(
+            InetAddresses.parseNumericAddress("2001:db8::8"), 64);
+
     private Context mContext;
     private Instrumentation mInstrumentation;
     private ConnectivityManager mCm;
@@ -1531,4 +1542,72 @@
             throw new AssertionFailedError("Captive portal server URL is invalid: " + e);
         }
     }
+
+    /**
+     * Verify background request can only be requested when acquiring
+     * {@link android.Manifest.permission.NETWORK_SETTINGS}.
+     */
+    @SkipPresubmit(reason = "Flaky: b/179554972; add to presubmit after fixing")
+    @Test
+    public void testRequestBackgroundNetwork() throws Exception {
+        // Create a tun interface. Use the returned interface name as the specifier to create
+        // a test network request.
+        final TestNetworkInterface testNetworkInterface = runWithShellPermissionIdentity(() -> {
+            final TestNetworkManager tnm =
+                    mContext.getSystemService(TestNetworkManager.class);
+            return tnm.createTunInterface(new LinkAddress[]{TEST_LINKADDR});
+        }, android.Manifest.permission.MANAGE_TEST_NETWORKS,
+                android.Manifest.permission.NETWORK_SETTINGS);
+        assertNotNull(testNetworkInterface);
+
+        final NetworkRequest testRequest = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_TEST)
+                // Test networks do not have NOT_VPN or TRUSTED capabilities by default
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                .setNetworkSpecifier(
+                        new StringNetworkSpecifier(testNetworkInterface.getInterfaceName()))
+                .build();
+
+        // Verify background network cannot be requested without NETWORK_SETTINGS permission.
+        final TestableNetworkCallback callback = new TestableNetworkCallback();
+        assertThrows(SecurityException.class,
+                () -> mCm.requestBackgroundNetwork(testRequest, null, callback));
+
+        try {
+            // Request background test network via Shell identity which has NETWORK_SETTINGS
+            // permission granted.
+            runWithShellPermissionIdentity(
+                    () -> mCm.requestBackgroundNetwork(testRequest, null, callback),
+                    android.Manifest.permission.NETWORK_SETTINGS);
+
+            // Register the test network agent which has no foreground request associated to it.
+            // And verify it can satisfy the background network request just fired.
+            final Binder binder = new Binder();
+            runWithShellPermissionIdentity(() -> {
+                final TestNetworkManager tnm =
+                        mContext.getSystemService(TestNetworkManager.class);
+                tnm.setupTestNetwork(testNetworkInterface.getInterfaceName(), binder);
+            }, android.Manifest.permission.MANAGE_TEST_NETWORKS,
+                    android.Manifest.permission.NETWORK_SETTINGS);
+            waitForAvailable(callback);
+            final Network testNetwork = callback.getLastAvailableNetwork();
+            assertNotNull(testNetwork);
+
+            // The test network that has just connected is a foreground network,
+            // non-listen requests will get available callback before it can be put into
+            // background if no foreground request can be satisfied. Thus, wait for a short
+            // period is needed to let foreground capability go away.
+            callback.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED,
+                    callback.getDefaultTimeoutMs(),
+                    c -> c instanceof CallbackEntry.CapabilitiesChanged
+                            && !((CallbackEntry.CapabilitiesChanged) c).getCaps()
+                            .hasCapability(NET_CAPABILITY_FOREGROUND));
+            final NetworkCapabilities nc = mCm.getNetworkCapabilities(testNetwork);
+            assertFalse("expected background network, but got " + nc,
+                    nc.hasCapability(NET_CAPABILITY_FOREGROUND));
+        } finally {
+            mCm.unregisterNetworkCallback(callback);
+        }
+    }
 }
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 69d90aa..4dbde1b 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -35,6 +35,7 @@
 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED
 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.NET_CAPABILITY_NOT_VPN
 import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
 import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
@@ -321,6 +322,7 @@
             addCapability(NET_CAPABILITY_NOT_SUSPENDED)
             addCapability(NET_CAPABILITY_NOT_ROAMING)
             addCapability(NET_CAPABILITY_NOT_VPN)
+            addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
             if (null != name) {
                 setNetworkSpecifier(StringNetworkSpecifier(name))
             }
@@ -557,6 +559,7 @@
             addTransportType(TRANSPORT_TEST)
             addTransportType(TRANSPORT_VPN)
             removeCapability(NET_CAPABILITY_NOT_VPN)
+            addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
         }
         val defaultNetwork = mCM.activeNetwork
         assertNotNull(defaultNetwork)
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index d118c8a..31dc64d 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -16,8 +16,13 @@
 
 package android.net.cts;
 
+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_MMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
+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;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -29,6 +34,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import android.annotation.NonNull;
 import android.net.MacAddress;
 import android.net.MatchAllNetworkSpecifier;
 import android.net.NetworkCapabilities;
@@ -43,6 +49,7 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 
@@ -152,29 +159,44 @@
                 .getRequestorPackageName());
     }
 
+    private void addNotVcnManagedCapability(@NonNull NetworkCapabilities nc) {
+        if (SdkLevel.isAtLeastS()) {
+            nc.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+        }
+    }
+
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.Q)
     public void testCanBeSatisfiedBy() {
         final LocalNetworkSpecifier specifier1 = new LocalNetworkSpecifier(1234 /* id */);
         final LocalNetworkSpecifier specifier2 = new LocalNetworkSpecifier(5678 /* id */);
 
+        // Some requests are adding NOT_VCN_MANAGED capability automatically. Add it to the
+        // capabilities below for bypassing the check.
         final NetworkCapabilities capCellularMmsInternet = new NetworkCapabilities()
                 .addTransportType(TRANSPORT_CELLULAR)
                 .addCapability(NET_CAPABILITY_MMS)
                 .addCapability(NET_CAPABILITY_INTERNET);
+        addNotVcnManagedCapability(capCellularMmsInternet);
         final NetworkCapabilities capCellularVpnMmsInternet =
                 new NetworkCapabilities(capCellularMmsInternet).addTransportType(TRANSPORT_VPN);
+        addNotVcnManagedCapability(capCellularVpnMmsInternet);
         final NetworkCapabilities capCellularMmsInternetSpecifier1 =
                 new NetworkCapabilities(capCellularMmsInternet).setNetworkSpecifier(specifier1);
+        addNotVcnManagedCapability(capCellularMmsInternetSpecifier1);
         final NetworkCapabilities capVpnInternetSpecifier1 = new NetworkCapabilities()
                 .addCapability(NET_CAPABILITY_INTERNET)
                 .addTransportType(TRANSPORT_VPN)
                 .setNetworkSpecifier(specifier1);
+        addNotVcnManagedCapability(capVpnInternetSpecifier1);
         final NetworkCapabilities capCellularMmsInternetMatchallspecifier =
                 new NetworkCapabilities(capCellularMmsInternet)
-                    .setNetworkSpecifier(new MatchAllNetworkSpecifier());
+                        .setNetworkSpecifier(new MatchAllNetworkSpecifier());
+        addNotVcnManagedCapability(capCellularMmsInternetMatchallspecifier);
         final NetworkCapabilities capCellularMmsInternetSpecifier2 =
-                new NetworkCapabilities(capCellularMmsInternet).setNetworkSpecifier(specifier2);
+                new NetworkCapabilities(capCellularMmsInternet)
+                        .setNetworkSpecifier(specifier2);
+        addNotVcnManagedCapability(capCellularMmsInternetSpecifier2);
 
         final NetworkRequest requestCellularInternetSpecifier1 = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_CELLULAR)
@@ -239,7 +261,8 @@
 
         final NetworkCapabilities capCellInternetBWSpecifier1Signal =
                 new NetworkCapabilities.Builder(capCellInternetBWSpecifier1)
-                    .setSignalStrength(-123).build();
+                        .setSignalStrength(-123).build();
+        addNotVcnManagedCapability(capCellInternetBWSpecifier1Signal);
         assertCorrectlySatisfies(true, requestCombination,
                 capCellInternetBWSpecifier1Signal);
 
@@ -273,4 +296,75 @@
         assertEquals(Process.INVALID_UID, new NetworkRequest.Builder()
                 .clearCapabilities().build().getRequestorUid());
     }
+
+    // TODO: 1. Refactor test cases with helper method.
+    //       2. Test capability that does not yet exist.
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testBypassingVcnForNonInternetRequest() {
+        // Make an empty request. Verify the NOT_VCN_MANAGED is added.
+        final NetworkRequest emptyRequest = new NetworkRequest.Builder().build();
+        assertTrue(emptyRequest.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED));
+
+        // Make a request explicitly add NOT_VCN_MANAGED. Verify the NOT_VCN_MANAGED is preserved.
+        final NetworkRequest mmsAddNotVcnRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_MMS)
+                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+                .build();
+        assertTrue(mmsAddNotVcnRequest.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED));
+
+        // Similar to above, but the opposite order.
+        final NetworkRequest mmsAddNotVcnRequest2 = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+                .addCapability(NET_CAPABILITY_MMS)
+                .build();
+        assertTrue(mmsAddNotVcnRequest2.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED));
+
+        // Make a request explicitly remove NOT_VCN_MANAGED. Verify the NOT_VCN_MANAGED is removed.
+        final NetworkRequest removeNotVcnRequest = new NetworkRequest.Builder()
+                .removeCapability(NET_CAPABILITY_NOT_VCN_MANAGED).build();
+        assertFalse(removeNotVcnRequest.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED));
+
+        // Make a request add some capability inside VCN supported capabilities.
+        // Verify the NOT_VCN_MANAGED is added.
+        final NetworkRequest notRoamRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_NOT_ROAMING).build();
+        assertTrue(notRoamRequest.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED));
+
+        // Make a internet request. Verify the NOT_VCN_MANAGED is added.
+        final NetworkRequest internetRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_INTERNET).build();
+        assertTrue(internetRequest.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED));
+
+        // Make a internet request which explicitly removed NOT_VCN_MANAGED.
+        // Verify the NOT_VCN_MANAGED is removed.
+        final NetworkRequest internetRemoveNotVcnRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .removeCapability(NET_CAPABILITY_NOT_VCN_MANAGED).build();
+        assertFalse(internetRemoveNotVcnRequest.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED));
+
+        // Make a normal MMS request. Verify the request could bypass VCN.
+        final NetworkRequest mmsRequest =
+                new NetworkRequest.Builder().addCapability(NET_CAPABILITY_MMS).build();
+        assertFalse(mmsRequest.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED));
+
+        // Make a SUPL request along with internet. Verify NOT_VCN_MANAGED is not added since
+        // SUPL is not in the supported list.
+        final NetworkRequest suplWithInternetRequest = new NetworkRequest.Builder()
+                        .addCapability(NET_CAPABILITY_SUPL)
+                        .addCapability(NET_CAPABILITY_INTERNET).build();
+        assertFalse(suplWithInternetRequest.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED));
+
+        // Make a FOTA request with explicitly add NOT_VCN_MANAGED capability. Verify
+        // NOT_VCN_MANAGED is preserved.
+        final NetworkRequest fotaRequest = new NetworkRequest.Builder()
+                        .addCapability(NET_CAPABILITY_FOTA)
+                        .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED).build();
+        assertTrue(fotaRequest.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED));
+
+        // Make a DUN request, which is in {@code VCN_SUPPORTED_CAPABILITIES}.
+        // Verify NOT_VCN_MANAGED is preserved.
+        final NetworkRequest dunRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_DUN).build();
+        assertTrue(dunRequest.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED));
+    }
 }