Connectivity: Add capability to allow tethering to use VPN upstreams

Updated for Android 14, which does not handle CONNECTIVITY_ACTION and
therefore does not react to VPN connections as it previously did. The
new way to handle this is EVENT_DEFAULT_SWITCHED, but this only
considered the system default network, not VPNs; now, in 14, when VPN
upstreams are allowed, we follow the default network for ROOT_UID,
mirroring this change's existing consideration of ROOT_UID.

Uses AOSP Settings for easier APEX updates

Squash of:

Author: Sam Mortimer <sam@mortimer.me.uk>
Date:   Thu Aug 15 19:40:55 2019 -0700

    fw/b: Add capability to allow tethering to use VPN upstreams

    * Toggled on/off at runtime via a new hotspot lineage setting.

    * Dynamically updates the tethering upstream for existing hotspot
      clients as VPNs are brought up / down or the hotspot setting
      is changed.

    * This implementation depends on fw/b config_tether_upstream_automatic
      being set to true.

    Change-Id: I2ac0b4acc0ea686dfdf54561cb3428808e337160

Author: Sam Mortimer <sam@mortimer.me.uk>
Date:   Fri Sep 13 16:27:37 2019 -0700

    fw/b: Prevent trying to enable hw offload for tethering via VPN upstreams

    * Tethering via VPN upstream requires a sw path.

    * hw offload setup happened to be being disabled anyway owing to a fail
      return code from setDataLimit().  However, it was causing offload to be
      disabled entirely (until next hotspot off / on event).

    * Gracefully skip hw offload for vpn upstreams so that it is automatically
      used again when a vpn is disconnected.

    Change-Id: I4df13f02889305560903b7b1e919eedc7af78c07

* Previously if you had a VPN running in a work profile, it'd end up using that over user 0 VPN
* Only use user 0 VPNs, as that makes most sense logically for a device-wide feature (hotspot)

Issue: calyxos#912
Co-authored-by: Tommy Webb <tommy@calyxinstitute.org>
Change-Id: I77ed0142e653f4993486eea44e4dac21e3f67f17
Signed-off-by: Dmitrii <bankersenator@gmail.com>
Signed-off-by: Jis G Jacob <studiokeys@blissroms.org>
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 5022b40..a4d103f 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;
@@ -178,6 +179,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 TETHERING_ALLOW_VPN_UPSTREAMS = "tethering_allow_vpn_upstreams";
+
     private static final Class[] sMessageClasses = {
             Tethering.class, TetherMainSM.class, IpServer.class
     };
@@ -502,6 +506,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(Settings.Secure.getUriFor(
+                TETHERING_ALLOW_VPN_UPSTREAMS), false, vpnSettingObserver);
     }
 
     private class TetheringThreadExecutor implements Executor {
@@ -2306,6 +2321,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/UpstreamNetworkMonitor.java b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
index 7a05d74..de7a98c 100644
--- a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
+++ b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
@@ -37,16 +37,21 @@
 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 java.util.HashMap;
@@ -84,6 +89,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 TETHERING_ALLOW_VPN_UPSTREAMS = "tethering_allow_vpn_upstreams";
+
     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 +140,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 +166,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 +195,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 Settings.Secure.getInt(mContext.getContentResolver(),
+                TETHERING_ALLOW_VPN_UPSTREAMS, 0) == 1;
+    }
+
     private void reevaluateUpstreamRequirements(boolean tryCell, boolean autoUpstream,
             boolean dunRequired) {
         final boolean mobileRequestRequired = tryCell && (dunRequired || !autoUpstream);
@@ -317,6 +356,10 @@
      * Returns null if no current upstream is available.
      */
     public UpstreamNetworkState getCurrentPreferredUpstream() {
+        // Use VPN upstreams if hotspot settings allow.
+        if (mTetheringUpstreamVpn != null && isAllowedToUseVpnUpstreams()) {
+            return mNetworkMap.get(mTetheringUpstreamVpn);
+        }
         final UpstreamNetworkState dfltState = (mDefaultInternetNetwork != null)
                 ? mNetworkMap.get(mDefaultInternetNetwork)
                 : null;
@@ -358,6 +401,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 +467,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 +684,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) {