Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 64c14793 authored by Benedict Wong's avatar Benedict Wong Committed by Android (Google) Code Review
Browse files

Merge changes from topic "vpn-meteredness" into oc-mr1-dev

* changes:
  DO NOT MERGE: Add unit tests to ensure VPN meteredness
  DO NOT MERGE: Fix ConnectivityController meteredness checks
parents 2d6209f7 66bc5288
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -2504,6 +2504,28 @@ public class ConnectivityManager {
        }
    }

    /**
     * Returns if the active data network for the given UID is metered. A network
     * is classified as metered when the user is sensitive to heavy data usage on
     * that connection due to monetary costs, data limitations or
     * battery/performance issues. You should check this before doing large
     * data transfers, and warn the user or delay the operation until another
     * network is available.
     *
     * @return {@code true} if large transfers should be avoided, otherwise
     *        {@code false}.
     *
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
    public boolean isActiveNetworkMeteredForUid(int uid) {
        try {
            return mService.isActiveNetworkMeteredForUid(uid);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * If the LockdownVpn mechanism is enabled, updates the vpn
     * with a reload of its profile.
+1 −0
Original line number Diff line number Diff line
@@ -66,6 +66,7 @@ interface IConnectivityManager

    NetworkQuotaInfo getActiveNetworkQuotaInfo();
    boolean isActiveNetworkMetered();
    boolean isActiveNetworkMeteredForUid(int uid);

    boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress);

+48 −22
Original line number Diff line number Diff line
@@ -969,7 +969,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
        if (!mLockdownEnabled) {
            int user = UserHandle.getUserId(uid);
            synchronized (mVpns) {
                Vpn vpn = mVpns.get(user);
                Vpn vpn = getVpn(user);
                if (vpn != null && vpn.appliesToUid(uid)) {
                    return vpn.getUnderlyingNetworks();
                }
@@ -1017,7 +1017,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
            return false;
        }
        synchronized (mVpns) {
            final Vpn vpn = mVpns.get(UserHandle.getUserId(uid));
            final Vpn vpn = getVpn(UserHandle.getUserId(uid));
            if (vpn != null && vpn.isBlockingUid(uid)) {
                return true;
            }
@@ -1094,7 +1094,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
        final int user = UserHandle.getUserId(uid);
        int vpnNetId = NETID_UNSET;
        synchronized (mVpns) {
            final Vpn vpn = mVpns.get(user);
            final Vpn vpn = getVpn(user);
            if (vpn != null && vpn.appliesToUid(uid)) vpnNetId = vpn.getNetId();
        }
        NetworkAgentInfo nai;
@@ -1224,7 +1224,7 @@ public class ConnectivityService extends IConnectivityManager.Stub

        if (!mLockdownEnabled) {
            synchronized (mVpns) {
                Vpn vpn = mVpns.get(userId);
                Vpn vpn = getVpn(userId);
                if (vpn != null) {
                    Network[] networks = vpn.getUnderlyingNetworks();
                    if (networks != null) {
@@ -1339,7 +1339,17 @@ public class ConnectivityService extends IConnectivityManager.Stub
    public boolean isActiveNetworkMetered() {
        enforceAccessPermission();

        final int uid = Binder.getCallingUid();
        return isActiveNetworkMeteredCommon(Binder.getCallingUid());
    }

    @Override
    public boolean isActiveNetworkMeteredForUid(int uid) {
        enforceConnectivityInternalPermission();

        return isActiveNetworkMeteredCommon(uid);
    }

    private boolean isActiveNetworkMeteredCommon(int uid) {
        final NetworkCapabilities caps = getUnfilteredActiveNetworkState(uid).networkCapabilities;
        if (caps != null) {
            return !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
@@ -3414,7 +3424,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
        throwIfLockdownEnabled();

        synchronized (mVpns) {
            Vpn vpn = mVpns.get(userId);
            Vpn vpn = getVpn(userId);
            if (vpn != null) {
                return vpn.prepare(oldPackage, newPackage);
            } else {
@@ -3441,7 +3451,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
        enforceCrossUserPermission(userId);

        synchronized (mVpns) {
            Vpn vpn = mVpns.get(userId);
            Vpn vpn = getVpn(userId);
            if (vpn != null) {
                vpn.setPackageAuthorization(packageName, authorized);
            }
@@ -3460,7 +3470,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
        throwIfLockdownEnabled();
        int user = UserHandle.getUserId(Binder.getCallingUid());
        synchronized (mVpns) {
            return mVpns.get(user).establish(config);
            return getVpn(user).establish(config);
        }
    }

@@ -3477,7 +3487,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
        }
        int user = UserHandle.getUserId(Binder.getCallingUid());
        synchronized (mVpns) {
            mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress);
            getVpn(user).startLegacyVpn(profile, mKeyStore, egress);
        }
    }

@@ -3491,7 +3501,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
        enforceCrossUserPermission(userId);

        synchronized (mVpns) {
            return mVpns.get(userId).getLegacyVpnInfo();
            return getVpn(userId).getLegacyVpnInfo();
        }
    }

@@ -3555,7 +3565,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
    public VpnConfig getVpnConfig(int userId) {
        enforceCrossUserPermission(userId);
        synchronized (mVpns) {
            Vpn vpn = mVpns.get(userId);
            Vpn vpn = getVpn(userId);
            if (vpn != null) {
                return vpn.getVpnConfig();
            } else {
@@ -3589,7 +3599,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
            }
            int user = UserHandle.getUserId(Binder.getCallingUid());
            synchronized (mVpns) {
                Vpn vpn = mVpns.get(user);
                Vpn vpn = getVpn(user);
                if (vpn == null) {
                    Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown");
                    return false;
@@ -3636,7 +3646,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
     */
    private boolean startAlwaysOnVpn(int userId) {
        synchronized (mVpns) {
            Vpn vpn = mVpns.get(userId);
            Vpn vpn = getVpn(userId);
            if (vpn == null) {
                // Shouldn't happen as all codepaths that point here should have checked the Vpn
                // exists already.
@@ -3654,7 +3664,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
        enforceCrossUserPermission(userId);

        synchronized (mVpns) {
            Vpn vpn = mVpns.get(userId);
            Vpn vpn = getVpn(userId);
            if (vpn == null) {
                Slog.w(TAG, "User " + userId + " has no Vpn configuration");
                return false;
@@ -3674,7 +3684,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
        }

        synchronized (mVpns) {
            Vpn vpn = mVpns.get(userId);
            Vpn vpn = getVpn(userId);
            if (vpn == null) {
                Slog.w(TAG, "User " + userId + " has no Vpn configuration");
                return false;
@@ -3696,7 +3706,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
        enforceCrossUserPermission(userId);

        synchronized (mVpns) {
            Vpn vpn = mVpns.get(userId);
            Vpn vpn = getVpn(userId);
            if (vpn == null) {
                Slog.w(TAG, "User " + userId + " has no Vpn configuration");
                return null;
@@ -3842,22 +3852,38 @@ public class ConnectivityService extends IConnectivityManager.Stub

    private void onUserStart(int userId) {
        synchronized (mVpns) {
            Vpn userVpn = mVpns.get(userId);
            Vpn userVpn = getVpn(userId);
            if (userVpn != null) {
                loge("Starting user already has a VPN");
                return;
            }
            userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId);
            mVpns.put(userId, userVpn);
            setVpn(userId, userVpn);
        }
        if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
            updateLockdownVpn();
        }
    }

    /** @hide */
    @VisibleForTesting
    Vpn getVpn(int userId) {
        synchronized (mVpns) {
            return mVpns.get(userId);
        }
    }

    /** @hide */
    @VisibleForTesting
    void setVpn(int userId, Vpn userVpn) {
        synchronized (mVpns) {
            mVpns.put(userId, userVpn);
        }
    }

    private void onUserStop(int userId) {
        synchronized (mVpns) {
            Vpn userVpn = mVpns.get(userId);
            Vpn userVpn = getVpn(userId);
            if (userVpn == null) {
                loge("Stopped user has no VPN");
                return;
@@ -5429,7 +5455,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
        throwIfLockdownEnabled();
        int user = UserHandle.getUserId(Binder.getCallingUid());
        synchronized (mVpns) {
            return mVpns.get(user).addAddress(address, prefixLength);
            return getVpn(user).addAddress(address, prefixLength);
        }
    }

@@ -5438,7 +5464,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
        throwIfLockdownEnabled();
        int user = UserHandle.getUserId(Binder.getCallingUid());
        synchronized (mVpns) {
            return mVpns.get(user).removeAddress(address, prefixLength);
            return getVpn(user).removeAddress(address, prefixLength);
        }
    }

@@ -5448,7 +5474,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
        int user = UserHandle.getUserId(Binder.getCallingUid());
        boolean success;
        synchronized (mVpns) {
            success = mVpns.get(user).setUnderlyingNetworks(networks);
            success = getVpn(user).setUnderlyingNetworks(networks);
        }
        if (success) {
            notifyIfacesChangedForNetworkStats();
+2 −4
Original line number Diff line number Diff line
@@ -112,10 +112,8 @@ public final class ConnectivityController extends StateController implements
        final boolean connected = (info != null) && info.isConnected();
        final boolean connectionUsable = connected && validated;

        final boolean metered = connected && (capabilities != null)
                && !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
        final boolean unmetered = connected && (capabilities != null)
                && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
        final boolean metered = connected && mConnManager.isActiveNetworkMeteredForUid(jobUid);
        final boolean unmetered = connected && !mConnManager.isActiveNetworkMeteredForUid(jobUid);
        final boolean notRoaming = connected && (info != null)
                && !info.isRoaming();

+87 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.NetworkCapabilities.*;
@@ -102,6 +103,7 @@ import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkMonitor;
import com.android.server.connectivity.NetworkMonitor.CaptivePortalProbeResult;
import com.android.server.connectivity.Vpn;
import com.android.server.net.NetworkPinner;
import com.android.server.net.NetworkPolicyManagerInternal;

@@ -333,6 +335,9 @@ public class ConnectivityServiceTest extends AndroidTestCase {
                case TRANSPORT_WIFI_AWARE:
                    mScore = 20;
                    break;
                case TRANSPORT_VPN:
                    mScore = 0;
                    break;
                default:
                    throw new UnsupportedOperationException("unimplemented network type");
            }
@@ -868,6 +873,8 @@ public class ConnectivityServiceTest extends AndroidTestCase {
                return TYPE_WIFI;
            case TRANSPORT_CELLULAR:
                return TYPE_MOBILE;
            case TRANSPORT_VPN:
                return TYPE_VPN;
            default:
                return TYPE_NONE;
        }
@@ -3447,4 +3454,84 @@ public class ConnectivityServiceTest extends AndroidTestCase {
            return;
        }
    }

    @SmallTest
    public void testVpnNetworkMetered() {
        final TestNetworkCallback callback = new TestNetworkCallback();
        mCm.registerDefaultNetworkCallback(callback);

        final NetworkRequest cellRequest = new NetworkRequest.Builder()
                .addTransportType(TRANSPORT_CELLULAR).build();
        final TestNetworkCallback cellCallback = new TestNetworkCallback();
        mCm.registerNetworkCallback(cellRequest, cellCallback);

        // Setup cellular
        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
        mCellNetworkAgent.connect(true);
        callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
        cellCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
        verifyActiveNetwork(TRANSPORT_CELLULAR);

        // Verify meteredness of cellular
        assertTrue(mCm.isActiveNetworkMetered());

        // Setup Wifi
        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
        mWiFiNetworkAgent.connect(true);
        callback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
        cellCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
        verifyActiveNetwork(TRANSPORT_WIFI);

        // Verify meteredness of WiFi
        assertTrue(mCm.isActiveNetworkMetered());

        // Verify that setting unmetered on Wifi changes ActiveNetworkMetered
        mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
        callback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent);
        assertFalse(mCm.isActiveNetworkMetered());

        // Setup VPN
        final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
        vpnNetworkAgent.connect(true);

        Vpn mockVpn = mock(Vpn.class);
        when(mockVpn.appliesToUid(anyInt())).thenReturn(true);
        when(mockVpn.getNetId()).thenReturn(vpnNetworkAgent.getNetwork().netId);

        Vpn oldVpn = mService.getVpn(UserHandle.myUserId());
        mService.setVpn(UserHandle.myUserId(), mockVpn);
        assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());

        // Verify meteredness of VPN on default network
        when(mockVpn.getUnderlyingNetworks()).thenReturn(null);
        assertFalse(mCm.isActiveNetworkMetered());
        assertFalse(mCm.isActiveNetworkMeteredForUid(Process.myUid()));

        // Verify meteredness of VPN on unmetered wifi
        when(mockVpn.getUnderlyingNetworks())
                .thenReturn(new Network[] {mWiFiNetworkAgent.getNetwork()});
        assertFalse(mCm.isActiveNetworkMetered());
        assertFalse(mCm.isActiveNetworkMeteredForUid(Process.myUid()));

        // Set WiFi as metered, then check to see that it has been updated on the VPN
        mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
        callback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent);
        assertTrue(mCm.isActiveNetworkMetered());
        assertTrue(mCm.isActiveNetworkMeteredForUid(Process.myUid()));

        // Switch to cellular
        when(mockVpn.getUnderlyingNetworks())
                .thenReturn(new Network[] {mCellNetworkAgent.getNetwork()});
        assertTrue(mCm.isActiveNetworkMetered());
        assertTrue(mCm.isActiveNetworkMeteredForUid(Process.myUid()));

        // Test unmetered cellular
        mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
        cellCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent);
        assertFalse(mCm.isActiveNetworkMetered());
        assertFalse(mCm.isActiveNetworkMeteredForUid(Process.myUid()));

        mService.setVpn(UserHandle.myUserId(), oldVpn);
        mCm.unregisterNetworkCallback(callback);
    }
}