diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java index e5f644ed4a..836ebfe2bc 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.hardware.usb.UsbManager.USB_FUNCTION_RNDIS; 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; @@ -175,6 +176,9 @@ public class Tethering { 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 }; @@ -492,6 +496,17 @@ public class Tethering { } 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 { @@ -2287,6 +2302,12 @@ public class Tethering { } 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 ac2aa7bfea..e1159a0171 100644 --- a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java +++ b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java @@ -37,7 +37,10 @@ import android.net.Network; 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; @@ -45,9 +48,11 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.StateMachine; +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; @@ -85,6 +90,9 @@ public class UpstreamNetworkMonitor { 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; @@ -134,6 +142,8 @@ public class UpstreamNetworkMonitor { // 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, StateMachine tgt, SharedLog log, int what) { mContext = ctx; @@ -159,8 +169,7 @@ public class UpstreamNetworkMonitor { return; } ConnectivityManagerShim mCmShim = ConnectivityManagerShimImpl.newInstance(mContext); - mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_DEFAULT_INTERNET); - mCmShim.registerSystemDefaultNetworkCallback(mDefaultNetworkCallback, mHandler); + registerAppropriateDefaultNetworkCallback(); if (mEntitlementMgr == null) { mEntitlementMgr = entitle; } @@ -189,9 +198,39 @@ public class UpstreamNetworkMonitor { 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); @@ -320,6 +359,10 @@ public class UpstreamNetworkMonitor { * 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; @@ -361,6 +404,7 @@ public class UpstreamNetworkMonitor { } 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 @@ -425,6 +469,10 @@ public class UpstreamNetworkMonitor { // - 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 @@ -645,6 +693,22 @@ public class UpstreamNetworkMonitor { && !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> uids = nc.getUids(); + if (uids == null) return true; + for (final Range range : uids) { + if (range.contains(Process.ROOT_UID)) return true; + } + return false; + } + private static UpstreamNetworkState findFirstDunNetwork( Iterable netStates) { for (UpstreamNetworkState ns : netStates) {