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) {