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

Android 14.0.0 release 29

# -----BEGIN PGP SIGNATURE-----
#
# iF0EABECAB0WIQRDQNE1cO+UXoOBCWTorT+BmrEOeAUCZeZW5AAKCRDorT+BmrEO
# eAPbAJsEm9Wr9Wa247qVs8FVFwrOfTbQ+QCeNDnfvFnwSAZFPnHgHziapjITPN0=
# =jjQM
# -----END PGP SIGNATURE-----
# gpg: Signature made Tue Mar  5 01:19:00 2024 EET
# 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 2357 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 Maciej Żenczykowski (272) and others
# Via Automerger Merge Worker (13851) and others
* tag 'android-14.0.0_r29': (2002 commits)
  [nearby] Fix test failure
  Exclude user build devices for the test
  [nearby] Add nearby_enable_ble_in_init flag
  Revert "[nearby] Add disable logic"
  [nearby] Add disable logic
  [nearby] Log changes
  [nearby] Sync discoveryTimestamp field name
  [nearby] Enable BLE when init
  [nearby] Add logs to broadcaster
  [nearby] Add discoveryTimestamp field
  [nearby] Fix flakeness in the unit test
  [nearby][clean up] Remove useless legacy code
  [nearby] Fix the user-debug only test
  Don't add LOCAL_NETWORK as forbidden capability
  [nearby] Catches NPE in ChreDiscoveryProvider
  [nearby] Update README to add build env script
  Expose APIs to query the state of an IpSecTransform
  Reland "Support getting transform state in IpSecService"
  Revert "Support getting transform state in IpSecService"
  Add more timeout for verify the interaction of the mock object
  ...

 Conflicts:
	Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
	bpf_progs/netd.c
	service/src/com/android/server/ConnectivityService.java

Change-Id: If3464757e222db1be894c60f27c206be3f195eb8
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 414e50a..2735c16 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -75,6 +75,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 5022b40..7cd710c 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;
@@ -150,6 +151,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;
@@ -502,6 +505,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 {
@@ -2306,6 +2320,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 d09183a..a2e7912 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;
@@ -498,10 +496,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 f223dd1..59bbfba 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) {
@@ -637,15 +652,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;
@@ -654,8 +668,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 64ed633..681cb00 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/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 0ec0f13..9e5e03f 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -263,6 +263,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;
@@ -369,6 +370,7 @@
 import java.util.TreeSet;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
 
 /**
  * @hide
@@ -469,6 +471,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;
@@ -557,6 +886,8 @@
     // See {@link ConnectivitySettingsManager#setMobileDataPreferredUids}
     @VisibleForTesting
     static final int PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED = 30;
+    // 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
@@ -1730,6 +2061,13 @@
         mCarrierPrivilegeAuthenticator =
                 mDeps.makeCarrierPrivilegeAuthenticator(mContext, mTelephonyManager);
 
+        // 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.
@@ -2103,6 +2441,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);
@@ -2138,6 +2491,7 @@
         if (ignoreBlocked) {
             return false;
         }
+        if (isUidCurrentlyDisallowedByPolicy(uid)) return true;
         if (isUidBlockedByVpn(uid, mVpnBlockedUidRanges)) return true;
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -2148,6 +2502,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;
@@ -2241,7 +2600,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)) {
@@ -3196,6 +3565,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) {
@@ -4922,7 +5296,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);
@@ -5047,7 +5424,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.
@@ -6885,6 +7265,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;
@@ -8433,6 +8817,7 @@
         nai.notifyRegistered();
         NetworkInfo networkInfo = nai.networkInfo;
         updateNetworkInfo(nai, networkInfo);
+        updateDisallowedUidsForNetwork(nai);
         updateVpnUids(nai, null, nai.networkCapabilities);
     }
 
@@ -8539,11 +8924,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
@@ -8694,7 +9084,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<>(
@@ -8721,6 +9112,7 @@
                 loge("Exception removing interface: " + e);
             }
         }
+        return !(interfaceDiff.added.isEmpty() && interfaceDiff.removed.isEmpty());
     }
 
     /**
@@ -8802,7 +9194,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);
@@ -8813,7 +9205,7 @@
             return;
         }
 
-        if (Objects.equals(oldIface, newIface) && (wasFiltering == needsFiltering)) {
+        if (!force && Objects.equals(oldIface, newIface) && (wasFiltering == needsFiltering)) {
             // Nothing changed.
             return;
         }
@@ -8830,9 +9222,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 */);
         }
     }
 
@@ -9045,6 +9441,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();
@@ -9369,9 +9768,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!
@@ -9381,6 +9786,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();
@@ -9762,6 +10170,7 @@
         } catch (RemoteException | ServiceSpecificException e) {
             loge("Exception setting app default network", e);
         }
+        updateUidDefaultNetworkRules(newDefaultNetwork);
     }
 
     /**
@@ -9846,6 +10255,7 @@
         } catch (RemoteException | ServiceSpecificException e) {
             loge("Exception setting default network :" + e);
         }
+        updateUidDefaultNetworkRules(newDefaultNetwork);
     }
 
     private void processListenRequests(@NonNull final NetworkAgentInfo nai) {
@@ -10534,6 +10944,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.
      *