Merge tag 'android-14.0.0_r50' into staging/lineage-21.0_merge-android-14.0.0_r50

Android 14.0.0 Release 50 (AP2A.240605.024)

# -----BEGIN PGP SIGNATURE-----
#
# iF0EABECAB0WIQRDQNE1cO+UXoOBCWTorT+BmrEOeAUCZmdzpwAKCRDorT+BmrEO
# eK/kAJ0Qc8hX0IpaFh36xMDaWU5CkqYhyACfTAoS8UiuCbThp5WSk27HBcN9WyA=
# =14Bx
# -----END PGP SIGNATURE-----
# gpg: Signature made Tue Jun 11 00:44:07 2024 EEST
# gpg:                using DSA key 4340D13570EF945E83810964E8AD3F819AB10E78
# gpg: Good signature from "The Android Open Source Project <initial-contribution@android.com>" [marginal]
# gpg: initial-contribution@android.com: Verified 2514 signatures in the past
#      2 years.  Encrypted 4 messages in the past 2 years.
# gpg: WARNING: This key is not certified with sufficiently trusted signatures!
# gpg:          It is not certain that the signature belongs to the owner.
# Primary key fingerprint: 4340 D135 70EF 945E 8381  0964 E8AD 3F81 9AB1 0E78

# By Kangping Dong (45) and others
# Via Automerger Merge Worker (1227) and others
* tag 'android-14.0.0_r50': (327 commits)
  Import translations. DO NOT MERGE ANYWHERE
  Add testAllowedUids for devices without FEATURE_TELEPHONY_SUBSCRIPTION
  Fix flakes due to networks obtained via sync APIs
  Import translations. DO NOT MERGE ANYWHERE
  Revert "24Q2: use mainline netbpfload from apex"
  24Q2: use mainline netbpfload from apex
  Fix the ignore_on_* flags for egress tracing.
  Remove unused library visibility
  Connect to IBluetoothFinder and use it
  Update the imports to androidx.test.filters.*
  Move VpnManagerServiceTest and VpnTest to frameworks
  Use a TAP test network for MeshCoP service test cases
  Fix flaky multicast tests in Thread E2E test.
  NetBpfLoader: create /sys/fs/bpf/loader dir
  Revert "Use a TAP test network for MeshCoP service test cases"
  Use a TAP test network for MeshCoP service test cases
  [mdns] add service-side impl for NSD service TTL support
  Adding additional transport satellite support at getSubIdForMobile()
  netbpfload: fail if platform exec of apex fails
  [Thread] make Thread state customisable via resource overlay
  ...

 Conflicts:
	netbpfload/NetBpfLoad.cpp
	service/src/com/android/server/ConnectivityService.java

Change-Id: I6d5c5cbdd45f1dba28581d2dbf28851bb29cfd5d
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index e4e6c70..394234c 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -76,6 +76,7 @@
         "net-utils-device-common-netlink",
         "netd-client",
         "tetheringstatsprotos",
+        "org.lineageos.platform.lineagesettings",
     ],
     defaults: ["TetheringExternalLibs"],
     libs: [
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 873961a..c44a0de 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -26,6 +26,7 @@
 import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
 import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
@@ -151,6 +152,8 @@
 import com.android.networkstack.tethering.util.VersionedBroadcastListener;
 import com.android.networkstack.tethering.wear.WearableConnectionManager;
 
+import lineageos.providers.LineageSettings;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.net.InetAddress;
@@ -501,6 +504,17 @@
         }
 
         startTrackDefaultNetwork();
+
+        // Listen for allowing tethering upstream via VPN settings changes
+        final ContentObserver vpnSettingObserver = new ContentObserver(mHandler) {
+            @Override
+            public void onChange(boolean self) {
+                // Reconsider tethering upstream
+                mUpstreamNetworkMonitor.maybeUpdateDefaultNetworkCallback();
+            }
+        };
+        mContext.getContentResolver().registerContentObserver(LineageSettings.Secure.getUriFor(
+                LineageSettings.Secure.TETHERING_ALLOW_VPN_UPSTREAMS), false, vpnSettingObserver);
     }
 
     private class TetheringThreadExecutor implements Executor {
@@ -2305,6 +2319,12 @@
             }
 
             public void updateUpstreamNetworkState(UpstreamNetworkState ns) {
+                // Disable hw offload on vpn upstream interfaces.
+                // setUpstreamLinkProperties() interprets null as disable.
+                if (ns != null && ns.networkCapabilities != null
+                        && !ns.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_VPN)) {
+                    ns = null;
+                }
                 mOffloadController.setUpstreamLinkProperties(
                         (ns != null) ? ns.linkProperties : null);
             }
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 298940e..9016cdd 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -16,7 +16,6 @@
 
 package com.android.networkstack.tethering;
 
-import static android.content.Context.TELEPHONY_SERVICE;
 import static android.net.ConnectivityManager.TYPE_ETHERNET;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
@@ -34,7 +33,6 @@
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
 import androidx.annotation.NonNull;
@@ -505,10 +503,7 @@
 
     /** Check whether dun is required. */
     public static boolean checkDunRequired(Context ctx) {
-        final TelephonyManager tm = (TelephonyManager) ctx.getSystemService(TELEPHONY_SERVICE);
-        // TelephonyManager would uses the active data subscription, which should be the one used
-        // by tethering.
-        return (tm != null) ? tm.isTetheringApnRequired() : false;
+        return false;
     }
 
     public int getOffloadPollInterval() {
diff --git a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
index 7a05d74..577a5e1 100644
--- a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
+++ b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
@@ -37,18 +37,25 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.os.Handler;
+import android.os.Process;
+import android.provider.Settings;
 import android.util.Log;
+import android.util.Range;
 import android.util.SparseIntArray;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.SharedLog;
 import com.android.networkstack.apishim.ConnectivityManagerShimImpl;
 import com.android.networkstack.apishim.common.ConnectivityManagerShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
 import com.android.networkstack.tethering.util.PrefixUtils;
 
+import lineageos.providers.LineageSettings;
+
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Objects;
@@ -84,6 +91,9 @@
     private static final boolean DBG = false;
     private static final boolean VDBG = false;
 
+    // Copied from frameworks/base/core/java/android/provider/Settings.java
+    private static final String ALWAYS_ON_VPN_LOCKDOWN = "always_on_vpn_lockdown";
+
     public static final int EVENT_ON_CAPABILITIES   = 1;
     public static final int EVENT_ON_LINKPROPERTIES = 2;
     public static final int EVENT_ON_LOST           = 3;
@@ -132,6 +142,8 @@
     // The current system default network (not really used yet).
     private Network mDefaultInternetNetwork;
     private boolean mPreferTestNetworks;
+    // Set if the Internet is considered reachable via the main user's VPN network
+    private Network mTetheringUpstreamVpn;
 
     public UpstreamNetworkMonitor(Context ctx, Handler h, SharedLog log, EventListener listener) {
         mContext = ctx;
@@ -156,8 +168,7 @@
             return;
         }
         ConnectivityManagerShim mCmShim = ConnectivityManagerShimImpl.newInstance(mContext);
-        mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_DEFAULT_INTERNET);
-        mCmShim.registerSystemDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
+        registerAppropriateDefaultNetworkCallback();
         if (mEntitlementMgr == null) {
             mEntitlementMgr = entitle;
         }
@@ -186,9 +197,39 @@
         releaseCallback(mListenAllCallback);
         mListenAllCallback = null;
 
+        mTetheringUpstreamVpn = null;
         mNetworkMap.clear();
     }
 
+    public void maybeUpdateDefaultNetworkCallback() {
+        final NetworkCallback prevNetworkCallback = mDefaultNetworkCallback;
+        if (prevNetworkCallback != null) {
+            registerAppropriateDefaultNetworkCallback();
+            cm().unregisterNetworkCallback(prevNetworkCallback);
+        }
+    }
+
+    private void registerAppropriateDefaultNetworkCallback() {
+        mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_DEFAULT_INTERNET);
+        final ConnectivityManagerShim cmShim = ConnectivityManagerShimImpl.newInstance(mContext);
+        if (isAllowedToUseVpnUpstreams() && SdkLevel.isAtLeastU()) {
+            try {
+                cmShim.registerDefaultNetworkCallbackForUid(Process.ROOT_UID,
+                        mDefaultNetworkCallback, mHandler);
+            } catch (UnsupportedApiLevelException e) {
+                Log.wtf(TAG, "Unexpected exception registering network callback for root UID"
+                        + " to support hotspot VPN upstreams", e);
+            }
+        } else {
+            cmShim.registerSystemDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
+        }
+    }
+
+    private boolean isAllowedToUseVpnUpstreams() {
+        return LineageSettings.Secure.getInt(mContext.getContentResolver(),
+                LineageSettings.Secure.TETHERING_ALLOW_VPN_UPSTREAMS, 0) == 1;
+    }
+
     private void reevaluateUpstreamRequirements(boolean tryCell, boolean autoUpstream,
             boolean dunRequired) {
         final boolean mobileRequestRequired = tryCell && (dunRequired || !autoUpstream);
@@ -317,6 +358,15 @@
      * Returns null if no current upstream is available.
      */
     public UpstreamNetworkState getCurrentPreferredUpstream() {
+        // Use VPN upstreams if hotspot settings allow.
+        if (isAllowedToUseVpnUpstreams()) {
+            if (mTetheringUpstreamVpn != null) {
+                return mNetworkMap.get(mTetheringUpstreamVpn);
+            } else if (Settings.Secure.getInt(mContext.getContentResolver(),
+                    ALWAYS_ON_VPN_LOCKDOWN, 0) == 1) {
+                return null;
+            }
+        }
         final UpstreamNetworkState dfltState = (mDefaultInternetNetwork != null)
                 ? mNetworkMap.get(mDefaultInternetNetwork)
                 : null;
@@ -358,6 +408,7 @@
     }
 
     private void handleNetCap(Network network, NetworkCapabilities newNc) {
+        if (isSystemVpnUsable(newNc)) mTetheringUpstreamVpn = network;
         final UpstreamNetworkState prev = mNetworkMap.get(network);
         if (prev == null || newNc.equals(prev.networkCapabilities)) {
             // Ignore notifications about networks for which we have not yet
@@ -423,6 +474,10 @@
         //     - deletes the entry from the map only when the LISTEN_ALL
         //       callback gets notified.
 
+        if (network.equals(mTetheringUpstreamVpn)) {
+            mTetheringUpstreamVpn = null;
+        }
+
         if (!mNetworkMap.containsKey(network)) {
             // Ignore loss of networks about which we had not previously
             // learned any information or for which we have already processed
@@ -636,6 +691,22 @@
                && !isCellular(ns.networkCapabilities);
     }
 
+    private static boolean isSystemVpnUsable(NetworkCapabilities nc) {
+        return (nc != null)
+                && appliesToRootUid(nc) // Only VPNs in system user apply to root UID
+                && !nc.hasCapability(NET_CAPABILITY_NOT_VPN)
+                && nc.hasCapability(NET_CAPABILITY_INTERNET);
+    }
+
+    private static boolean appliesToRootUid(NetworkCapabilities nc) {
+        final Set<Range<Integer>> uids = nc.getUids();
+        if (uids == null) return true;
+        for (final Range<Integer> range : uids) {
+            if (range.contains(Process.ROOT_UID)) return true;
+        }
+        return false;
+    }
+
     private static UpstreamNetworkState findFirstDunNetwork(
             Iterable<UpstreamNetworkState> netStates) {
         for (UpstreamNetworkState ns : netStates) {
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 5e401aa..0fad7ec 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -398,6 +398,21 @@
     return true;  // disallowed interface
 }
 
+static __always_inline inline int bpf_owner_firewall_match(uint32_t uid) {
+    if (is_system_uid(uid)) return PASS;
+
+    const BpfConfig enabledRules = getConfig(UID_RULES_CONFIGURATION_KEY);
+    const UidOwnerValue* uidEntry = bpf_uid_owner_map_lookup_elem(&uid);
+    const uint32_t uidRules = uidEntry ? uidEntry->rule : 0;
+
+    if (enabledRules & (FIREWALL_DROP_IF_SET | FIREWALL_DROP_IF_UNSET)
+            & (uidRules ^ FIREWALL_DROP_IF_UNSET)) {
+        return DROP;
+    }
+
+    return PASS;
+}
+
 static __always_inline inline int bpf_owner_match(struct __sk_buff* skb, uint32_t uid,
                                                   const struct egress_bool egress,
                                                   const struct kver_uint kver) {
@@ -648,15 +663,14 @@
     return BPF_NOMATCH;
 }
 
-static __always_inline inline uint8_t get_app_permissions() {
-    uint64_t gid_uid = bpf_get_current_uid_gid();
+static __always_inline inline uint8_t get_app_permissions(uint32_t uid) {
     /*
      * A given app is guaranteed to have the same app ID in all the profiles in
      * which it is installed, and install permission is granted to app for all
      * user at install time so we only check the appId part of a request uid at
      * run time. See UserHandle#isSameApp for detail.
      */
-    uint32_t appId = (gid_uid & 0xffffffff) % AID_USER_OFFSET;  // == PER_USER_RANGE == 100000
+    uint32_t appId = uid % AID_USER_OFFSET;  // == PER_USER_RANGE == 100000
     uint8_t* permissions = bpf_uid_permission_map_lookup_elem(&appId);
     // if UID not in map, then default to just INTERNET permission.
     return permissions ? *permissions : BPF_PERMISSION_INTERNET;
@@ -665,8 +679,13 @@
 DEFINE_NETD_BPF_PROG_KVER("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create,
                           KVER_4_14)
 (struct bpf_sock* sk) {
+    uint64_t uid = bpf_get_current_uid_gid() & 0xffffffff;
     // A return value of 1 means allow, everything else means deny.
-    return (get_app_permissions() & BPF_PERMISSION_INTERNET) ? 1 : 0;
+    if (get_app_permissions(uid) & BPF_PERMISSION_INTERNET) {
+      return bpf_owner_firewall_match(uid) == PASS ? 1 : 0;
+    } else {
+      return 0;
+    }
 }
 
 LICENSE("Apache 2.0");
diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h
index 098147f..67c86e0 100644
--- a/bpf_progs/netd.h
+++ b/bpf_progs/netd.h
@@ -248,6 +248,9 @@
 #define DROP_IF_UNSET (DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH \
                         | LOW_POWER_STANDBY_MATCH | BACKGROUND_MATCH)
 
+#define FIREWALL_DROP_IF_SET (OEM_DENY_1_MATCH)
+#define FIREWALL_DROP_IF_UNSET (RESTRICTED_MATCH)
+
 // Warning: funky bit-wise arithmetic: in parallel, for all DROP_IF_SET/UNSET rules
 // check whether the rules are globally enabled, and if so whether the rules are
 // set/unset for the specific uid.  DROP if that is the case for ANY of the rules.
diff --git a/framework-t/src/android/net/TrafficStats.java b/framework-t/src/android/net/TrafficStats.java
index a69b38d..d509082 100644
--- a/framework-t/src/android/net/TrafficStats.java
+++ b/framework-t/src/android/net/TrafficStats.java
@@ -238,8 +238,7 @@
 
     private static class SocketTagger extends dalvik.system.SocketTagger {
 
-        // TODO: set to false
-        private static final boolean LOGD = true;
+        private static final boolean LOGD = false;
 
         SocketTagger() {
         }
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index ed7d048..8af4dd4 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -289,13 +289,11 @@
     }
 
     if (isAtLeastT && !android::bpf::isAtLeastKernelVersion(4, 9, 0)) {
-        ALOGE("Android T requires kernel 4.9.");
-        return 1;
+        ALOGW("Android T requires kernel 4.9.");
     }
 
     if (isAtLeastU && !android::bpf::isAtLeastKernelVersion(4, 14, 0)) {
-        ALOGE("Android U requires kernel 4.14.");
-        return 1;
+        ALOGW("Android U requires kernel 4.14.");
     }
 
     if (isAtLeastV && !android::bpf::isAtLeastKernelVersion(4, 19, 0)) {
@@ -353,12 +351,14 @@
         //  kernel does not have CONFIG_BPF_JIT=y)
         // BPF_JIT is required by R VINTF (which means 4.14/4.19/5.4 kernels),
         // but 4.14/4.19 were released with P & Q, and only 5.4 is new in R+.
-        if (writeProcSysFile("/proc/sys/net/core/bpf_jit_enable", "1\n")) return 1;
+        if (writeProcSysFile("/proc/sys/net/core/bpf_jit_enable", "1\n") &&
+            android::bpf::isAtLeastKernelVersion(4, 14, 0)) return 1;
 
         // Enable JIT kallsyms export for privileged users only
         // (Note: this (open) will fail with ENOENT 'No such file or directory' if
         //  kernel does not have CONFIG_HAVE_EBPF_JIT=y)
-        if (writeProcSysFile("/proc/sys/net/core/bpf_jit_kallsyms", "1\n")) return 1;
+        if (writeProcSysFile("/proc/sys/net/core/bpf_jit_kallsyms", "1\n") &&
+            android::bpf::isAtLeastKernelVersion(4, 14, 0)) return 1;
     }
 
     // Create all the pin subdirectories
diff --git a/netbpfload/loader.cpp b/netbpfload/loader.cpp
index c534b2c..d9d3b50 100644
--- a/netbpfload/loader.cpp
+++ b/netbpfload/loader.cpp
@@ -574,6 +574,14 @@
 
 static bool mapMatchesExpectations(const unique_fd& fd, const string& mapName,
                                    const struct bpf_map_def& mapDef, const enum bpf_map_type type) {
+    // bpfGetFd... family of functions require at minimum a 4.14 kernel,
+    // so on 4.9-T kernels just pretend the map matches our expectations.
+    // Additionally we'll get almost equivalent test coverage on newer devices/kernels.
+    // This is because the primary failure mode we're trying to detect here
+    // is either a source code misconfiguration (which is likely kernel independent)
+    // or a newly introduced kernel feature/bug (which is unlikely to get backported to 4.9).
+    if (!isAtLeastKernelVersion(4, 14, 0)) return true;
+
     // Assuming fd is a valid Bpf Map file descriptor then
     // all the following should always succeed on a 4.14+ kernel.
     // If they somehow do fail, they'll return -1 (and set errno),
@@ -711,6 +719,16 @@
         }
 
         enum bpf_map_type type = md[i].type;
+        if (type == BPF_MAP_TYPE_DEVMAP && !isAtLeastKernelVersion(4, 14, 0)) {
+            // On Linux Kernels older than 4.14 this map type doesn't exist, but it can kind
+            // of be approximated: ARRAY has the same userspace api, though it is not usable
+            // by the same ebpf programs.  However, that's okay because the bpf_redirect_map()
+            // helper doesn't exist on 4.9-T anyway (so the bpf program would fail to load,
+            // and thus needs to be tagged as 4.14+ either way), so there's nothing useful you
+            // could do with a DEVMAP anyway (that isn't already provided by an ARRAY)...
+            // Hence using an ARRAY instead of a DEVMAP simply makes life easier for userspace.
+            type = BPF_MAP_TYPE_ARRAY;
+        }
         if (type == BPF_MAP_TYPE_DEVMAP_HASH && !isAtLeastKernelVersion(5, 4, 0)) {
             // On Linux Kernels older than 5.4 this map type doesn't exist, but it can kind
             // of be approximated: HASH has the same userspace visible api.
@@ -766,7 +784,8 @@
               .max_entries = max_entries,
               .map_flags = md[i].map_flags,
             };
-            strlcpy(req.map_name, mapNames[i].c_str(), sizeof(req.map_name));
+            if (isAtLeastKernelVersion(4, 14, 0))
+                strlcpy(req.map_name, mapNames[i].c_str(), sizeof(req.map_name));
             fd.reset(bpf(BPF_MAP_CREATE, req));
             saved_errno = errno;
             ALOGD("bpf_create_map name %s, ret: %d", mapNames[i].c_str(), fd.get());
@@ -1008,7 +1027,8 @@
               .log_size = static_cast<__u32>(log_buf.size()),
               .expected_attach_type = cs[i].expected_attach_type,
             };
-            strlcpy(req.prog_name, cs[i].name.c_str(), sizeof(req.prog_name));
+            if (isAtLeastKernelVersion(4, 14, 0))
+                strlcpy(req.prog_name, cs[i].name.c_str(), sizeof(req.prog_name));
             fd.reset(bpf(BPF_PROG_LOAD, req));
 
             ALOGD("BPF_PROG_LOAD call for %s (%s) returned fd: %d (%s)", elfPath,
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index a00c363..2252e07 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -75,16 +75,6 @@
     // This code was mainlined in T, so this should be trivially satisfied.
     if (!modules::sdklevel::IsAtLeastT()) return Status("S- platform is unsupported");
 
-    // S requires eBPF support which was only added in 4.9, so this should be satisfied.
-    if (!bpf::isAtLeastKernelVersion(4, 9, 0)) {
-        return Status("kernel version < 4.9.0 is unsupported");
-    }
-
-    // U bumps the kernel requirement up to 4.14
-    if (modules::sdklevel::IsAtLeastU() && !bpf::isAtLeastKernelVersion(4, 14, 0)) {
-        return Status("U+ platform with kernel version < 4.14.0 is unsupported");
-    }
-
     if (modules::sdklevel::IsAtLeastV()) {
         // V bumps the kernel requirement up to 4.19
         // see also: //system/netd/tests/kernel_test.cpp TestKernel419
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 6839c22..e33bb2e 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -267,6 +267,7 @@
 import android.util.Range;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
+import android.util.SparseLongArray;
 import android.util.StatsEvent;
 
 import androidx.annotation.RequiresApi;
@@ -379,6 +380,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 /**
  * @hide
@@ -481,6 +483,333 @@
      */
     private final SparseIntArray mUidBlockedReasons = new SparseIntArray();
 
+    /** Map of UID to its bit-packed allowed transports. */
+    private SparseLongArray mUidAllowedTransports = new SparseLongArray();
+
+    /** Allowed UID ranges provided to Netd, tracked based on the netId to which they belong. */
+    @GuardedBy("mNetworkForNetId")
+    private Map<Integer, NativeUidRangeConfig> mNetIdToAllowlist =
+            new HashMap<Integer, NativeUidRangeConfig>();
+
+    /**
+     * UIDs that we wish to be denied access to networks based on certain policies, grouped by the
+     * netId to which they belong. Used to prevent affected UIDs from getting online through a VPN
+     * or from receiving incoming traffic when their active network is denied to them.
+     */
+    @GuardedBy("mNetworkForNetId")
+    private Map<Integer, List<Integer>> mNetIdToDisallowedUids = new HashMap<>();
+
+    private void setUidsAllowedTransports(@NonNull final int[] uids,
+            @NonNull final long[] allowedTransportsPacked) {
+        mHandler.post(() -> handleSetUidsAllowedTransports(uids, allowedTransportsPacked));
+    }
+
+    private void handleSetUidsAllowedTransports(@NonNull final int[] uids,
+            @NonNull final long[] allowedTransportsPacked) {
+        for (int i = 0; i < uids.length; i++) {
+            final int uid = uids[i];
+            final long transportsPacked = allowedTransportsPacked[i];
+            mUidAllowedTransports.put(uid, transportsPacked);
+        }
+        if (DDBG) Log.d(TAG, "setUidsAllowedTransports: Processing " + uids.length + " UIDs...");
+        for (final var nai : mNetworkAgentInfos) {
+            updateDisallowedUidsForNetwork(nai);
+            if (nai.isVPN()) {
+                // VPNs manage their own allowed UID ranges, so we handle them via denylist above.
+                continue;
+            }
+            for (final int uid : uids) {
+                if (nai.networkCapabilities.appliesToUid(uid)) {
+                    updateAllowedUidsForNetwork(nai);
+                    break;
+                }
+            }
+        }
+        if (DDBG) Log.d(TAG, "setUidsAllowedTransports: Processed " + uids.length + " UIDs.");
+    }
+
+    // The last UID denylist that we supplied to BPF.
+    // To support access across threads, do not modify after assignment.
+    private Set<Integer> mLastDisallowedUidsDenylist = Set.of();
+
+    /**
+     * Computes which policies apply to the given network to determine which UIDs should be
+     * prevented from accessing that network. Then, for each network with disallowed UIDs, checks
+     * whether the network is currently the active network for the UID, and if so, prevents the
+     * UID from accessing networks entirely via a denylist. Without this, UIDs are able to use VPNs
+     * even if their policy disallows it, because the VPN itself manages its allowed UID ranges.
+     * Additionally, this prevents incoming traffic for UIDs whose active network is disallowed.
+     *
+     * NOTE: UIDs with restricted networking permission are never disallowed.
+     */
+    private void updateDisallowedUidsForNetwork(@NonNull final NetworkAgentInfo nai) {
+        ensureRunningOnConnectivityServiceThread();
+
+        final var netId = nai.network.netId;
+        final var ourTag = "updateDisallowedUidsForNetwork(" + netId + "): ";
+        if (DDBG) Log.d(TAG, ourTag + "begin");
+        final var disallowedUids = getUidsDisallowedByPolicyForNetwork(nai);
+        final Set<Integer> prevDenylist;
+        final Set<Integer> newDenylist = new ArraySet<Integer>();
+        synchronized (mNetworkForNetId) {
+            prevDenylist = mLastDisallowedUidsDenylist;
+            if (mNetworkForNetId.contains(netId)) {
+                // Only store the disallowed UIDs if this network still exists.
+                mNetIdToDisallowedUids.put(netId, disallowedUids);
+            }
+            for (final var entry : mNetIdToDisallowedUids.entrySet()) {
+                final var entryNetId = entry.getKey();
+                final var entryDisallowedUids = entry.getValue();
+                entryDisallowedUids.forEach(uid -> {
+                    if (mPermissionMonitor.hasRestrictedNetworksPermission(uid)) {
+                        // Skip denylist for UIDs with restricted networks permission.
+                        return;
+                    }
+                    // Disallowing UIDs is only applicable if the network is the UID's active
+                    // network. For us, a VPN that is still connecting should still be considered,
+                    // because it is *about* to become the active network, and we are reacting
+                    // early. If we do not include connecting VPNs, we get no result.
+                    // Also, if we don't know the active network, denying is fine for now.
+                    final var activeNetwork = getActiveNetworkOrConnectingVpnForUidInternal(
+                            uid, true /* ignoreBlocked */);
+                    if (activeNetwork == null || entryNetId == activeNetwork.netId) {
+                        newDenylist.add(uid);
+                    }
+                });
+            }
+            mLastDisallowedUidsDenylist = Collections.unmodifiableSet(newDenylist);
+        }
+        // NOTE: Setting UID rules individually is exponentially faster than replacing the whole
+        // firewall chain for some reason (<1ms vs 10+ms, even with few UIDs for the latter
+        // and hundreds for the former).
+        final var toAdd = newDenylist.stream().filter(uid -> !prevDenylist.contains(uid))
+                .collect(Collectors.toSet());
+        if (DDBG) {
+            synchronized (mNetworkForNetId) {
+                Log.d(TAG, ourTag + "Disallowed UIDs per netId: " + mNetIdToDisallowedUids);
+            }
+            Log.d(TAG, ourTag + "Prev denylist: " + prevDenylist);
+            Log.d(TAG, ourTag + "New denylist: " + newDenylist);
+        }
+        final var toRemove = prevDenylist.stream().filter(uid -> !newDenylist.contains(uid))
+                .collect(Collectors.toSet());
+        if (DDBG) Log.d(TAG, ourTag + "toAdd: " + toAdd + "; toRemove: " + toRemove);
+        if (toAdd.isEmpty() && toRemove.isEmpty()) {
+            // Nothing to do.
+            return;
+        }
+        boolean anyFailed = false;
+        for (final int uid : toAdd) {
+            try {
+                mBpfNetMaps.setUidRule(ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1, uid,
+                        FIREWALL_RULE_DENY);
+            } catch (ServiceSpecificException e) {
+                loge(ourTag + "Failed to add uid " + uid, e);
+                anyFailed = true;
+                break;
+            }
+        }
+        if (!anyFailed) {
+            for (final int uid : toRemove) {
+                try {
+                    mBpfNetMaps.setUidRule(ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1, uid,
+                            FIREWALL_RULE_ALLOW);
+                } catch (ServiceSpecificException e) {
+                    loge(ourTag + "Failed to remove uid " + uid, e);
+                    anyFailed = true;
+                    break;
+                }
+            }
+        }
+        if (anyFailed) {
+            // Should never happen, but just in case.
+            replaceFirewallChain(ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1,
+                    newDenylist.stream().mapToInt(Integer::intValue).toArray());
+        }
+        // Tell NPMS about the changes, primarily so the firewall icon can reflect them.
+        mPolicyManager.notifyDenylistChanged(toAdd.stream().mapToInt(Integer::intValue).toArray(),
+                toRemove.stream().mapToInt(Integer::intValue).toArray());
+        if (DDBG) Log.d(TAG, ourTag + "end");
+    }
+
+
+    /**
+     * Generates an allowlist configuration for Netd that reflects a network's allowed UIDs and
+     * includes a sub priority to allow the network to act as a default network for the UIDs
+     * if it is considered to be the default network overall. This default network handling is
+     * required, or else UIDs that do not make specific network requests will have no connectivity.
+     */
+    private NativeUidRangeConfig getAllowlistedNativeUidRangeConfigForNetwork(
+            @NonNull final NetworkAgentInfo nai) {
+        final int netId = nai.network.netId;
+        final var uidsAllowedByPolicy = getUidRangeParcelsAllowedByPolicyForNetwork(nai);
+        final boolean isDefault = isDefaultNetwork(nai);
+        final int subPriority = isDefault ? PREFERENCE_ORDER_LOWEST_WITH_DEFAULT
+                : PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT;
+        return new NativeUidRangeConfig(netId, uidsAllowedByPolicy, subPriority);
+    }
+
+    /**
+     * Updates our tracked configurations for Netd based on changes to default networks, and
+     * informs Netd if any changes need to be made to ensure that allowed UIDs do or do not utilize
+     * changed networks as their default network. This default network handling is required, or
+     * else UIDs that do not make specific network requests will have no connectivity or will
+     * have their traffic traverse the wrong network.
+     */
+    private void updateUidDefaultNetworkRules(@Nullable final NetworkAgentInfo newDefaultNetwork) {
+        final Integer defaultNetId = newDefaultNetwork == null ? null
+                : newDefaultNetwork.network.netId;
+        final var configs = new ArrayList<Pair<NativeUidRangeConfig, NativeUidRangeConfig>>();
+        final var networksForUpdateDisallowedUids = new ArrayList<NetworkAgentInfo>();
+        synchronized (mNetworkForNetId) {
+            for (final var config : mNetIdToAllowlist.values()) {
+                final var configIsDefault =
+                        config.subPriority == PREFERENCE_ORDER_LOWEST_WITH_DEFAULT;
+                final int subPriority;
+                if (configIsDefault && !Objects.equals(config.netId, defaultNetId)) {
+                    // Remove and replace any existing rules with a subPriority for default.
+                    subPriority = PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT;
+                } else if (!configIsDefault && Objects.equals(config.netId, defaultNetId)) {
+                    // Remove and replace any existing rules with a subPriority for default.
+                    subPriority = PREFERENCE_ORDER_LOWEST_WITH_DEFAULT;
+                } else {
+                    continue;
+                }
+                final var newConfig =
+                        new NativeUidRangeConfig(config.netId, config.uidRanges, subPriority);
+                mNetIdToAllowlist.put(newConfig.netId, newConfig);
+                configs.add(new Pair<>(config, newConfig));
+                final var nai = mNetworkForNetId.get(config.netId);
+                if (nai != null) {
+                    networksForUpdateDisallowedUids.add(nai);
+                }
+            }
+        }
+        for (final var pair : configs) {
+            try {
+                mNetd.networkAddUidRangesParcel(pair.second);
+                mNetd.networkRemoveUidRangesParcel(pair.first);
+            } catch (RemoteException | ServiceSpecificException e) {
+                loge("updateUidDefaultNetworkRules: Exception while updating", e);
+            }
+        }
+        for (final var nai : networksForUpdateDisallowedUids) {
+            updateDisallowedUidsForNetwork(nai);
+        }
+    }
+
+    /**
+     * Generates and stores an allowlist configuration based on the policies that apply to the
+     * given network, and sends this configuration to Netd. Uses the same methods for adding UID
+     * range-based IP rules as is used by VPNs or by restricted networks which manually specify
+     * their allowed UIDs, ensuring absent UIDs cannot be routed there. {@see updateAllowedUids}
+     */
+    private void updateAllowedUidsForNetwork(@NonNull final NetworkAgentInfo nai) {
+        if (!nai.networkInfo.isConnected()) {
+            // Network is not connected, so we cannot currently update allowed UIDs.
+            // If we try, Netd responds with: "Machine is not on the network (code 64)"
+            return;
+        }
+        final int netId = nai.network.netId;
+        final var ourTag = "updateAllowedUidsForNetwork(" + netId + "): ";
+        if (DDBG) Log.d(TAG, ourTag + "begin");
+        final var config = getAllowlistedNativeUidRangeConfigForNetwork(nai);
+        final NativeUidRangeConfig lastConfig;
+        synchronized (mNetworkForNetId) {
+            lastConfig = mNetIdToAllowlist.get(netId);
+            mNetIdToAllowlist.put(netId, config);
+        }
+        try {
+            if (config.uidRanges != null && config.uidRanges.length > 0) {
+                mNetd.networkAddUidRangesParcel(config);
+            }
+            if (lastConfig != null && lastConfig.uidRanges != null
+                    && lastConfig.uidRanges.length > 0) {
+                mNetd.networkRemoveUidRangesParcel(lastConfig);
+            }
+        } catch (Exception e) {
+            loge(ourTag + "Exception encountered", e);
+        }
+        if (DDBG) Log.d(TAG, ourTag + "end");
+    }
+
+    private static final long TRANSPORT_VPN_FLAG = 1 << TRANSPORT_VPN;
+
+    /**
+     * Returns true if all of the provided transports are allowed, or if transports represent
+     * a VPN and VPNs are allowed. Otherwise, returns false.
+     */
+    private static boolean areTransportsAllowed(final long transports,
+            final long allowedTransports) {
+        // TODO: In the future, we may want to provide a way to restrict VPN access based on
+        // its other transports, e.g. to block apps from using mobile data even if that data
+        // is over a VPN. This is possible by removing or extending the check below.
+        if ((transports & TRANSPORT_VPN_FLAG) != 0) {
+            // For a VPN, all that matters is VPN access, nothing else.
+            return (allowedTransports & TRANSPORT_VPN_FLAG) != 0;
+        }
+        return (allowedTransports & transports) == transports;
+    }
+
+    /**
+     * Returns a list of UIDs that are policy-restricted based on the provided network and its
+     * capabilities. Networks that are restricted by the system choose their own allowed UIDs,
+     * so we do not disallow any UIDs for those networks.
+     */
+    private List<Integer> getUidsDisallowedByPolicyForNetwork(
+            @NonNull final NetworkAgentInfo nai) {
+        final var ourTag = "getUidsDisallowedByPolicyForNetwork(" + nai.network.netId + "): ";
+        final var uids = new ArrayList<Integer>();
+        if (!nai.networkInfo.isConnectedOrConnecting()) {
+            // Network is not connected so nothing needs to be disallowed.
+            if (DDBG) Log.d(TAG, ourTag + "not connected, so empty");
+            return uids;
+        }
+        final NetworkCapabilities nc = nai.networkCapabilities;
+        if (!nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
+            // We do not want to meddle with networks that are already considered restricted.
+            // These will have their own allowed UIDs.
+            if (DDBG) Log.d(TAG, ourTag + "restricted, so empty");
+            return uids;
+        }
+        final long packedTransports = BitUtils.packBits(nc.getTransportTypes());
+        if (packedTransports == 0L) {
+            // If we are not supplied with transports to check, nothing is allowed.
+            if (DDBG) Log.d(TAG, ourTag + "transports == 0L, so empty");
+            return uids;
+        }
+        final int size = mUidAllowedTransports.size();
+        int lastUid = 0;
+        for (int i = 0; i < size; i++) {
+            int uid = mUidAllowedTransports.keyAt(i);
+            long allowedTransports = mUidAllowedTransports.valueAt(i);
+            if (!nc.appliesToUid(uid)) continue;
+            if (!areTransportsAllowed(packedTransports, allowedTransports)) {
+                uids.add(uid);
+            }
+        }
+        return uids;
+    }
+
+    /**
+     * Returns an array of UidRangeParcel representing all possible UIDs that are not restricted
+     * by policy, based on the provided network and its capabilities.
+     */
+    private UidRangeParcel[] getUidRangeParcelsAllowedByPolicyForNetwork(
+            @NonNull final NetworkAgentInfo nai) {
+        final var uids = getUidsDisallowedByPolicyForNetwork(nai);
+        final var ranges = new ArrayList<UidRangeParcel>();
+        int lastUid = 0;
+        for (final Integer uid : uids) {
+            if (uid != lastUid) {
+                ranges.add(new UidRangeParcel(lastUid, uid - 1));
+            }
+            lastUid = uid + 1;
+        }
+        ranges.add(new UidRangeParcel(lastUid, Integer.MAX_VALUE));
+        return ranges.toArray(new UidRangeParcel[0]);
+    }
+
     private final Context mContext;
     private final ConnectivityResources mResources;
     private final int mWakeUpMark;
@@ -574,6 +903,8 @@
     // with role_sms role and android.permission.SATELLITE_COMMUNICATION permission detected
     @VisibleForTesting
     static final int PREFERENCE_ORDER_SATELLITE_FALLBACK = 40;
+    // Lowest subpriority that still adds default network rules.
+    static final int PREFERENCE_ORDER_LOWEST_WITH_DEFAULT = 998;
     // Preference order that signifies the network shouldn't be set as a default network for
     // the UIDs, only give them access to it. TODO : replace this with a boolean
     // in NativeUidRangeConfig
@@ -1816,6 +2147,13 @@
             mSatelliteAccessController = null;
         }
 
+        // Enable the OEM denylist chain. {@see mNetIdToDisallowedUids}
+        try {
+            mBpfNetMaps.setChildChain(ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1, true);
+        } catch (ServiceSpecificException e) {
+            logwtf("Could not enable fw_oem_deny_1 chain", e);
+        }
+
         // To ensure uid state is synchronized with Network Policy, register for
         // NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
         // reading existing policy from disk.
@@ -2203,6 +2541,21 @@
     }
 
     @Nullable
+    private NetworkAgentInfo getConnectedOrConnectingVpnForUid(int uid) {
+        synchronized (mNetworkForNetId) {
+            for (int i = 0; i < mNetworkForNetId.size(); i++) {
+                final NetworkAgentInfo nai = mNetworkForNetId.valueAt(i);
+                if (nai.isVPN()
+                        && (nai.everConnected() || nai.networkInfo.isConnectedOrConnecting())
+                        && nai.networkCapabilities.appliesToUid(uid)) {
+                    return nai;
+                }
+            }
+        }
+        return null;
+    }
+
+    @Nullable
     private Network[] getVpnUnderlyingNetworks(int uid) {
         if (mLockdownEnabled) return null;
         final NetworkAgentInfo nai = getVpnForUid(uid);
@@ -2238,6 +2591,7 @@
         if (ignoreBlocked) {
             return false;
         }
+        if (isUidCurrentlyDisallowedByPolicy(uid)) return true;
         if (isUidBlockedByVpn(uid, mVpnBlockedUidRanges)) return true;
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -2248,6 +2602,11 @@
         }
     }
 
+    /** Check if UID is currently disallowed general network access based on policies. */
+    private boolean isUidCurrentlyDisallowedByPolicy(int uid) {
+        return mLastDisallowedUidsDenylist.contains(uid);
+    }
+
     private void maybeLogBlockedNetworkInfo(NetworkInfo ni, int uid) {
         if (ni == null || !LOGD_BLOCKED_NETWORKINFO) {
             return;
@@ -2341,7 +2700,17 @@
 
     @Nullable
     private Network getActiveNetworkForUidInternal(final int uid, boolean ignoreBlocked) {
-        final NetworkAgentInfo vpnNai = getVpnForUid(uid);
+        return getActiveNetworkForUidInternal(getVpnForUid(uid), uid, ignoreBlocked);
+    }
+
+    private Network getActiveNetworkOrConnectingVpnForUidInternal(final int uid,
+            boolean ignoreBlocked) {
+        return getActiveNetworkForUidInternal(
+                getConnectedOrConnectingVpnForUid(uid), uid, ignoreBlocked);
+    }
+
+    private Network getActiveNetworkForUidInternal(final NetworkAgentInfo vpnNai,
+            final int uid, boolean ignoreBlocked) {
         if (vpnNai != null) {
             final NetworkCapabilities requiredCaps = createDefaultNetworkCapabilitiesForUid(uid);
             if (requiredCaps.satisfiedByNetworkCapabilities(vpnNai.networkCapabilities)) {
@@ -3280,6 +3649,11 @@
             mHandler.sendMessage(mHandler.obtainMessage(EVENT_UID_BLOCKED_REASON_CHANGED,
                     uid, blockedReasons));
         }
+
+        @Override
+        public void onUidsAllowedTransportsChanged(int[] uids, long[] allowedTransports) {
+            setUidsAllowedTransports(uids, allowedTransports);
+        }
     };
 
     private void handleUidBlockedReasonChanged(int uid, @BlockedReason int blockedReasons) {
@@ -5024,7 +5398,10 @@
                 final boolean wasDefault = isDefaultNetwork(nai);
                 synchronized (mNetworkForNetId) {
                     mNetworkForNetId.remove(nai.network.getNetId());
+                    mNetIdToAllowlist.remove(nai.network.getNetId());
+                    mNetIdToDisallowedUids.remove(nai.network.getNetId());
                 }
+                updateDisallowedUidsForNetwork(nai);
                 mNetIdManager.releaseNetId(nai.network.getNetId());
                 // Just in case.
                 mLegacyTypeTracker.remove(nai, wasDefault);
@@ -5149,7 +5526,10 @@
             // Remove the NetworkAgent, but don't mark the netId as
             // available until we've told netd to delete it below.
             mNetworkForNetId.remove(nai.network.getNetId());
+            mNetIdToAllowlist.remove(nai.network.getNetId());
+            mNetIdToDisallowedUids.remove(nai.network.getNetId());
         }
+        updateDisallowedUidsForNetwork(nai);
         propagateUnderlyingNetworkCapabilities(nai.network);
         // Update allowed network lists in netd. This should be called after removing nai
         // from mNetworkAgentInfos.
@@ -7002,6 +7382,10 @@
             final boolean curMetered = nai.networkCapabilities.isMetered();
             maybeNotifyNetworkBlocked(nai, curMetered, curMetered,
                     mVpnBlockedUidRanges, newVpnBlockedUidRanges);
+
+            if (nai.isVPN()) {
+                updateVpnFiltering(nai.linkProperties, nai.linkProperties, nai, true /* force */);
+            }
         }
 
         mVpnBlockedUidRanges = newVpnBlockedUidRanges;
@@ -8569,6 +8953,7 @@
         nai.notifyRegistered();
         NetworkInfo networkInfo = nai.networkInfo;
         updateNetworkInfo(nai, networkInfo);
+        updateDisallowedUidsForNetwork(nai);
         updateVpnUids(nai, null, nai.networkCapabilities);
     }
 
@@ -8675,11 +9060,16 @@
         // the LinkProperties for the network are accurate.
         networkAgent.clatd.fixupLinkProperties(oldLp, newLp);
 
-        updateInterfaces(newLp, oldLp, netId, networkAgent);
+        final boolean anyIfaceChanges =
+                updateInterfaces(newLp, oldLp, netId, networkAgent);
+
+        // If any interface names changed, ensure VPN filtering is updated so that ingress filtering
+        // uses the most up-to-date allowed interface.
+        final boolean shouldForceUpdateVpnFiltering = networkAgent.isVPN() && anyIfaceChanges;
 
         // update filtering rules, need to happen after the interface update so netd knows about the
         // new interface (the interface name -> index map becomes initialized)
-        updateVpnFiltering(newLp, oldLp, networkAgent);
+        updateVpnFiltering(newLp, oldLp, networkAgent, shouldForceUpdateVpnFiltering);
 
         updateMtu(newLp, oldLp);
         // TODO - figure out what to do for clat
@@ -8830,7 +9220,8 @@
         }
     }
 
-    private void updateInterfaces(final @NonNull LinkProperties newLp,
+    /** Return whether there were any added or removed interface names. */
+    private boolean updateInterfaces(final @NonNull LinkProperties newLp,
             final @Nullable LinkProperties oldLp, final int netId,
             final @NonNull NetworkAgentInfo nai) {
         final CompareResult<String> interfaceDiff = new CompareResult<>(
@@ -8857,6 +9248,7 @@
                 loge("Exception removing interface: " + e);
             }
         }
+        return !(interfaceDiff.added.isEmpty() && interfaceDiff.removed.isEmpty());
     }
 
     /**
@@ -8938,7 +9330,7 @@
     }
 
     private void updateVpnFiltering(@NonNull LinkProperties newLp, @Nullable LinkProperties oldLp,
-            @NonNull NetworkAgentInfo nai) {
+            @NonNull NetworkAgentInfo nai, boolean force) {
         final String oldIface = getVpnIsolationInterface(nai, nai.networkCapabilities, oldLp);
         final String newIface = getVpnIsolationInterface(nai, nai.networkCapabilities, newLp);
         final boolean wasFiltering = requiresVpnAllowRule(nai, oldLp, oldIface);
@@ -8949,7 +9341,7 @@
             return;
         }
 
-        if (Objects.equals(oldIface, newIface) && (wasFiltering == needsFiltering)) {
+        if (!force && Objects.equals(oldIface, newIface) && (wasFiltering == needsFiltering)) {
             // Nothing changed.
             return;
         }
@@ -8966,9 +9358,13 @@
         // old rules are being removed.
         if (wasFiltering) {
             mPermissionMonitor.onVpnUidRangesRemoved(oldIface, ranges, vpnAppUid);
+            mPermissionMonitor.updateVpnLockdownUidInterfaceRules(oldLp.getInterfaceName(), ranges,
+                    vpnAppUid, false /* add */);
         }
         if (needsFiltering) {
             mPermissionMonitor.onVpnUidRangesAdded(newIface, ranges, vpnAppUid);
+            mPermissionMonitor.updateVpnLockdownUidInterfaceRules(newLp.getInterfaceName(), ranges,
+                    vpnAppUid, true /* add */);
         }
     }
 
@@ -9215,6 +9611,9 @@
         updateNetworkPermissions(nai, newNc);
         final NetworkCapabilities prevNc = nai.getAndSetNetworkCapabilities(newNc);
 
+        if (prevNc != null && (!prevNc.equalsUids(newNc) || !prevNc.equalsTransportTypes(newNc))) {
+            updateDisallowedUidsForNetwork(nai);
+        }
         updateVpnUids(nai, prevNc, newNc);
         updateAllowedUids(nai, prevNc, newNc);
         nai.updateScoreForNetworkAgentUpdate();
@@ -9604,9 +10003,15 @@
             if (wasFiltering && !prevRanges.isEmpty()) {
                 mPermissionMonitor.onVpnUidRangesRemoved(oldIface, prevRanges,
                         prevNc.getOwnerUid());
+                mPermissionMonitor.updateVpnLockdownUidInterfaceRules(
+                        nai.linkProperties.getInterfaceName(), prevRanges, prevNc.getOwnerUid(),
+                        false /* add */);
             }
             if (shouldFilter && !newRanges.isEmpty()) {
                 mPermissionMonitor.onVpnUidRangesAdded(newIface, newRanges, newNc.getOwnerUid());
+                mPermissionMonitor.updateVpnLockdownUidInterfaceRules(
+                        nai.linkProperties.getInterfaceName(), newRanges, newNc.getOwnerUid(),
+                        true /* add */);
             }
         } catch (Exception e) {
             // Never crash!
@@ -9616,6 +10021,9 @@
 
     private void updateAllowedUids(@NonNull NetworkAgentInfo nai,
             @Nullable NetworkCapabilities prevNc, @Nullable NetworkCapabilities newNc) {
+        if (!nai.isVPN()) {
+            updateAllowedUidsForNetwork(nai);
+        }
         // In almost all cases both NC code for empty access UIDs. return as fast as possible.
         final boolean prevEmpty = null == prevNc || prevNc.getAllowedUidsNoCopy().isEmpty();
         final boolean newEmpty = null == newNc || newNc.getAllowedUidsNoCopy().isEmpty();
@@ -9996,6 +10404,7 @@
         } catch (RemoteException | ServiceSpecificException e) {
             loge("Exception setting app default network", e);
         }
+        updateUidDefaultNetworkRules(newDefaultNetwork);
     }
 
     /**
@@ -10080,6 +10489,7 @@
         } catch (RemoteException | ServiceSpecificException e) {
             loge("Exception setting default network :" + e);
         }
+        updateUidDefaultNetworkRules(newDefaultNetwork);
     }
 
     private void processListenRequests(@NonNull final NetworkAgentInfo nai) {
@@ -10773,6 +11183,7 @@
                 updateCapabilitiesForNetwork(networkAgent);
             }
             networkAgent.onNetworkCreated();
+            updateDisallowedUidsForNetwork(networkAgent);
             updateAllowedUids(networkAgent, null, networkAgent.networkCapabilities);
             updateProfileAllowedNetworks();
         }
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index beaa174..c9b6387 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -526,7 +526,6 @@
         // TODO : remove carryover package check in the future(b/31479477). All apps should just
         //  request the appropriate permission for their use case since android Q.
         return isCarryoverPackage(app.applicationInfo)
-                || isUidAllowedOnRestrictedNetworks(app.applicationInfo)
                 || hasPermission(app, PERMISSION_MAINLINE_NETWORK_STACK)
                 || hasPermission(app, NETWORK_STACK)
                 || hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS);
@@ -969,6 +968,7 @@
         // packets to that UID is fine.
         final Set<Integer> changedUids = intersectUids(rangesToAdd, mAllApps);
         removeBypassingUids(changedUids, vpnAppUid);
+        removeVpnLockdownUids(iface, changedUids);
         updateVpnUidsInterfaceRules(iface, changedUids, true /* add */);
         if (mVpnInterfaceUidRanges.containsKey(iface)) {
             mVpnInterfaceUidRanges.get(iface).addAll(rangesToAdd);
@@ -991,6 +991,7 @@
         // ranges and update Netd about them.
         final Set<Integer> changedUids = intersectUids(rangesToRemove, mAllApps);
         removeBypassingUids(changedUids, vpnAppUid);
+        removeVpnLockdownUids(iface, changedUids);
         updateVpnUidsInterfaceRules(iface, changedUids, false /* add */);
         Set<UidRange> existingRanges = mVpnInterfaceUidRanges.getOrDefault(iface, null);
         if (existingRanges == null) {
@@ -1004,6 +1005,27 @@
     }
 
     /**
+     * Called when a set of UID ranges are added/removed from an active VPN network and when
+     * UID ranges under VPN Lockdown are updated
+     *
+     * @param iface The VPN network's interface name. Null iface indicates that the interface is not
+     *              available.
+     * @param rangesToModify Existing UID ranges to be modified on the VPN network
+     * @param add {@code true} to add the UID rules, {@code false} to remove them.
+     * @param vpnAppUid The uid of the VPN app
+     */
+    public synchronized void updateVpnLockdownUidInterfaceRules(@Nullable String iface,
+            Set<UidRange> rangesToModify, int vpnAppUid, boolean add) {
+        if (iface != null) {
+            Set<Integer> uidsToModify = intersectUids(rangesToModify, mAllApps);
+            removeBypassingUids(uidsToModify, vpnAppUid);
+            Set<Integer> vpnLockdownUids = intersectUids(mVpnLockdownUidRanges.getSet(), mAllApps);
+            uidsToModify.retainAll(vpnLockdownUids);
+            updateVpnUidsInterfaceRules(iface, uidsToModify, add);
+        }
+    }
+
+    /**
      * Called when UID ranges under VPN Lockdown are updated
      *
      * @param add {@code true} if the uids are to be added to the Lockdown, {@code false} if they
@@ -1092,6 +1114,18 @@
     }
 
     /**
+     * Remove all apps which are under VPN Lockdown from the list of uids
+     *
+     * @param iface The interface name of the active VPN connection
+     * @param uids The list of uids to operate on
+     */
+    private void removeVpnLockdownUids(@Nullable String iface, Set<Integer> uids) {
+        if (iface == null) {
+            uids.removeAll(intersectUids(mVpnLockdownUidRanges.getSet(), mAllApps));
+        }
+    }
+
+    /**
      * Update netd about the list of uids that are under an active VPN connection which they cannot
      * bypass.
      *