Merge changes from topics "ADD_JVMOVERLOADS", "VPN_NETWORK_PREFERENCE"

* changes:
  Add HostsideVpnTests for testing setVpnDefaultForUids()
  Add CTS for ConnectivityManager#setVpnDefaultForUids()
  Create a new API to make a set of UIDs use only VPN by default
This commit is contained in:
Lucas Lin
2022-12-21 01:13:47 +00:00
committed by Gerrit Code Review
10 changed files with 340 additions and 11 deletions

View File

@@ -31,6 +31,7 @@ package android.net {
method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreferences(@NonNull android.os.UserHandle, @NonNull java.util.List<android.net.ProfileNetworkPreference>, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setRequireVpnForUids(boolean, @NonNull java.util.Collection<android.util.Range<java.lang.Integer>>);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setUidFirewallRule(int, int, int);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setVpnDefaultForUids(@NonNull String, @NonNull java.util.Collection<android.util.Range<java.lang.Integer>>);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void startCaptivePortalApp(@NonNull android.net.Network);
method public void systemReady();

View File

@@ -1378,6 +1378,17 @@ public class ConnectivityManager {
}
}
private static UidRange[] getUidRangeArray(@NonNull Collection<Range<Integer>> ranges) {
Objects.requireNonNull(ranges);
final UidRange[] rangesArray = new UidRange[ranges.size()];
int index = 0;
for (Range<Integer> range : ranges) {
rangesArray[index++] = new UidRange(range.getLower(), range.getUpper());
}
return rangesArray;
}
/**
* Adds or removes a requirement for given UID ranges to use the VPN.
*
@@ -1397,6 +1408,12 @@ public class ConnectivityManager {
* {@link NetworkCallback#onBlockedStatusChanged} callbacks called after the changes take
* effect.
* <p>
* This method will block the specified UIDs from accessing non-VPN networks, but does not
* affect what the UIDs get as their default network.
* Compare {@link #setVpnDefaultForUids(String, Collection)}, which declares that the UIDs
* should only have a VPN as their default network, but does not block them from accessing other
* networks if they request them explicitly with the {@link Network} API.
* <p>
* This method should be called only by the VPN code.
*
* @param ranges the UID ranges to restrict
@@ -1416,11 +1433,7 @@ public class ConnectivityManager {
// This method is not necessarily expected to be used outside the system server, so
// parceling may not be necessary, but it could be used out-of-process, e.g., by the network
// stack process, or by tests.
UidRange[] rangesArray = new UidRange[ranges.size()];
int index = 0;
for (Range<Integer> range : ranges) {
rangesArray[index++] = new UidRange(range.getLower(), range.getUpper());
}
final UidRange[] rangesArray = getUidRangeArray(ranges);
try {
mService.setRequireVpnForUids(requireVpn, rangesArray);
} catch (RemoteException e) {
@@ -1428,6 +1441,41 @@ public class ConnectivityManager {
}
}
/**
* Inform the system that this VPN session should manage the passed UIDs.
*
* A VPN with the specified session ID may call this method to inform the system that the UIDs
* in the specified range are subject to a VPN.
* When this is called, the system will only choose a VPN for the default network of the UIDs in
* the specified ranges.
*
* This method declares that the UIDs in the range will only have a VPN for their default
* network, but does not block the UIDs from accessing other networks (permissions allowing) by
* explicitly requesting it with the {@link Network} API.
* Compare {@link #setRequireVpnForUids(boolean, Collection)}, which does not affect what
* network the UIDs get as default, but will block them from accessing non-VPN networks.
*
* @param session The VPN session which manages the passed UIDs.
* @param ranges The uid ranges which will treat VPN as their only default network.
*
* @hide
*/
@RequiresPermission(anyOf = {
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
android.Manifest.permission.NETWORK_STACK,
android.Manifest.permission.NETWORK_SETTINGS})
@SystemApi(client = MODULE_LIBRARIES)
public void setVpnDefaultForUids(@NonNull String session,
@NonNull Collection<Range<Integer>> ranges) {
Objects.requireNonNull(ranges);
final UidRange[] rangesArray = getUidRangeArray(ranges);
try {
mService.setVpnNetworkPreference(session, rangesArray);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Informs ConnectivityService of whether the legacy lockdown VPN, as implemented by
* LockdownVpnTracker, is in use. This is deprecated for new devices starting from Android 12

View File

@@ -249,4 +249,6 @@ interface IConnectivityManager
void replaceFirewallChain(int chain, in int[] uids);
IBinder getCompanionDeviceManagerProxyService();
void setVpnNetworkPreference(String session, in UidRange[] ranges);
}

View File

@@ -286,6 +286,7 @@ import com.android.server.connectivity.ProfileNetworkPreferenceInfo;
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.QosCallbackTracker;
import com.android.server.connectivity.UidRangeUtils;
import com.android.server.connectivity.VpnNetworkPreferenceInfo;
import com.android.server.connectivity.wear.CompanionDeviceManagerProxyService;
import libcore.io.IoUtils;
@@ -746,6 +747,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
*/
private static final int EVENT_USER_DOES_NOT_WANT = 58;
/**
* Event to set VPN as preferred network for specific apps.
* obj = VpnNetworkPreferenceInfo
*/
private static final int EVENT_SET_VPN_NETWORK_PREFERENCE = 59;
/**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
@@ -1625,6 +1632,17 @@ public class ConnectivityService extends IConnectivityManager.Stub
TYPE_NONE, NetworkRequest.Type.REQUEST);
}
private NetworkRequest createVpnRequest() {
final NetworkCapabilities netCap = new NetworkCapabilities.Builder()
.withoutDefaultCapabilities()
.addTransportType(TRANSPORT_VPN)
.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
.addCapability(NET_CAPABILITY_NOT_RESTRICTED)
.build();
netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName());
return createNetworkRequest(NetworkRequest.Type.REQUEST, netCap);
}
private NetworkRequest createDefaultInternetRequestForTransport(
int transportType, NetworkRequest.Type type) {
final NetworkCapabilities netCap = new NetworkCapabilities();
@@ -5532,6 +5550,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
nai.onPreventAutomaticReconnect();
nai.disconnect();
break;
case EVENT_SET_VPN_NETWORK_PREFERENCE:
handleSetVpnNetworkPreference((VpnNetworkPreferenceInfo) msg.obj);
break;
}
}
}
@@ -7123,6 +7144,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
private NetworkPreferenceList<UserHandle, ProfileNetworkPreferenceInfo>
mProfileNetworkPreferences = new NetworkPreferenceList<>();
// Current VPN network preferences. This object follows the same threading rules as the OEM
// network preferences above.
@NonNull
private NetworkPreferenceList<String, VpnNetworkPreferenceInfo>
mVpnNetworkPreferences = new NetworkPreferenceList<>();
// A set of UIDs that should use mobile data preferentially if available. This object follows
// the same threading rules as the OEM network preferences above.
@NonNull
@@ -11112,6 +11139,60 @@ public class ConnectivityService extends IConnectivityManager.Stub
new Pair<>(preference, listener)));
}
/**
* Sets the specified UIDs to get/receive the VPN as the only default network.
*
* Calling this will overwrite the existing network preference for this session, and the
* specified UIDs won't get any default network when no VPN is connected.
*
* @param session The VPN session which manages the passed UIDs.
* @param ranges The uid ranges which will treat VPN as the only preferred network. Clear the
* setting for this session if the array is empty. Null is not allowed, the
* method will use {@link Objects#requireNonNull(Object)} to check this variable.
* @hide
*/
@Override
public void setVpnNetworkPreference(String session, UidRange[] ranges) {
Objects.requireNonNull(ranges);
enforceNetworkStackOrSettingsPermission();
final UidRange[] sortedRanges = UidRangeUtils.sortRangesByStartUid(ranges);
if (UidRangeUtils.sortedRangesContainOverlap(sortedRanges)) {
throw new IllegalArgumentException(
"setVpnNetworkPreference: Passed UID ranges overlap");
}
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_VPN_NETWORK_PREFERENCE,
new VpnNetworkPreferenceInfo(session,
new ArraySet<UidRange>(Arrays.asList(ranges)))));
}
private void handleSetVpnNetworkPreference(VpnNetworkPreferenceInfo preferenceInfo) {
Log.d(TAG, "handleSetVpnNetworkPreference: preferenceInfo = " + preferenceInfo);
mVpnNetworkPreferences = mVpnNetworkPreferences.minus(preferenceInfo.getKey());
mVpnNetworkPreferences = mVpnNetworkPreferences.plus(preferenceInfo);
removeDefaultNetworkRequestsForPreference(PREFERENCE_ORDER_VPN);
addPerAppDefaultNetworkRequests(createNrisForVpnNetworkPreference(mVpnNetworkPreferences));
// Finally, rematch.
rematchAllNetworksAndRequests();
}
private ArraySet<NetworkRequestInfo> createNrisForVpnNetworkPreference(
@NonNull NetworkPreferenceList<String, VpnNetworkPreferenceInfo> preferenceList) {
final ArraySet<NetworkRequestInfo> nris = new ArraySet<>();
for (VpnNetworkPreferenceInfo preferenceInfo : preferenceList) {
final List<NetworkRequest> requests = new ArrayList<>();
// Request VPN only, so other networks won't be the fallback options when VPN is not
// connected temporarily.
requests.add(createVpnRequest());
final Set<UidRange> uidRanges = new ArraySet(preferenceInfo.getUidRangesNoCopy());
setNetworkRequestUids(requests, uidRanges);
nris.add(new NetworkRequestInfo(Process.myUid(), requests, PREFERENCE_ORDER_VPN));
}
return nris;
}
/**
* Check the validity of an OEM network preference to be used for testing purposes.
* @param preference the preference to validate

View File

@@ -184,4 +184,41 @@ public final class UidRangeUtils {
uidRangeSet.add(new UidRange(start, stop));
return uidRangeSet;
}
private static int compare(UidRange range1, UidRange range2) {
return range1.start - range2.start;
}
/**
* Sort the given UidRange array.
*
* @param ranges The array of UidRange which is going to be sorted.
* @return Array of UidRange.
*/
public static UidRange[] sortRangesByStartUid(UidRange[] ranges) {
final ArrayList uidRanges = new ArrayList(Arrays.asList(ranges));
Collections.sort(uidRanges, UidRangeUtils::compare);
return (UidRange[]) uidRanges.toArray(new UidRange[0]);
}
/**
* Check if the given sorted UidRange array contains overlap or not.
*
* Note that the sorted UidRange array must be sorted by increasing lower bound. If it's not,
* the behavior is undefined.
*
* @param ranges The sorted UidRange array which is going to be checked if there is an overlap
* or not.
* @return A boolean to indicate if the given sorted UidRange array contains overlap or not.
*/
public static boolean sortedRangesContainOverlap(UidRange[] ranges) {
final ArrayList uidRanges = new ArrayList(Arrays.asList(ranges));
for (int i = 0; i + 1 < uidRanges.size(); i++) {
if (((UidRange) uidRanges.get(i + 1)).start <= ((UidRange) uidRanges.get(i)).stop) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.connectivity;
import android.annotation.NonNull;
import android.net.UidRange;
import android.util.ArraySet;
/**
* Record the session and UidRange for a VPN preference.
*/
public class VpnNetworkPreferenceInfo
implements NetworkPreferenceList.NetworkPreference<String> {
@NonNull
public final String mSession;
@NonNull
public final ArraySet<UidRange> mUidRanges;
public VpnNetworkPreferenceInfo(@NonNull String session,
@NonNull ArraySet<UidRange> uidRanges) {
this.mSession = session;
this.mUidRanges = uidRanges;
}
@Override
public boolean isCancel() {
return mUidRanges.isEmpty();
}
@Override
@NonNull
public String getKey() {
return mSession;
}
@NonNull
public ArraySet<UidRange> getUidRangesNoCopy() {
return mUidRanges;
}
/** toString */
public String toString() {
return "[VpnNetworkPreference session = " + mSession
+ " uidRanges = " + mUidRanges
+ "]";
}
}

View File

@@ -97,6 +97,7 @@ import android.system.StructPollfd;
import android.telephony.TelephonyManager;
import android.test.MoreAsserts;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Range;
@@ -108,6 +109,7 @@ import com.android.net.module.util.PacketBuilder;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.RecorderCallback;
import com.android.testutils.RecorderCallback.CallbackEntry;
import com.android.testutils.TestableNetworkCallback;
import org.junit.After;
@@ -136,6 +138,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -839,8 +842,13 @@ public class VpnTest {
callback.eventuallyExpect(RecorderCallback.CallbackEntry.NETWORK_CAPS_UPDATED,
NETWORK_CALLBACK_TIMEOUT_MS,
entry -> (Objects.equals(expectUnderlyingNetworks,
((RecorderCallback.CallbackEntry.CapabilitiesChanged) entry)
.getCaps().getUnderlyingNetworks())));
entry.getCaps().getUnderlyingNetworks())));
}
private void expectVpnNetwork(TestableNetworkCallback callback) {
callback.eventuallyExpect(RecorderCallback.CallbackEntry.NETWORK_CAPS_UPDATED,
NETWORK_CALLBACK_TIMEOUT_MS,
entry -> entry.getCaps().hasTransport(TRANSPORT_VPN));
}
@Test @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
@@ -1622,7 +1630,7 @@ public class VpnTest {
mCM.registerDefaultNetworkCallbackForUid(remoteUid, remoteUidCallback,
new Handler(Looper.getMainLooper()));
}, NETWORK_SETTINGS);
remoteUidCallback.expectAvailableCallbacks(network);
remoteUidCallback.expectAvailableCallbacksWithBlockedReasonNone(network);
// The remote UDP socket can receive packets coming from the TUN interface
checkBlockIncomingPacket(tunFd, remoteUdpFd, EXPECT_PASS);
@@ -1666,6 +1674,56 @@ public class VpnTest {
});
}
@Test
public void testSetVpnDefaultForUids() throws Exception {
assumeTrue(supportedHardware());
assumeTrue(SdkLevel.isAtLeastU());
final Network defaultNetwork = mCM.getActiveNetwork();
assertNotNull("There must be a default network", defaultNetwork);
final TestableNetworkCallback defaultNetworkCallback = new TestableNetworkCallback();
final String session = UUID.randomUUID().toString();
final int myUid = Process.myUid();
testAndCleanup(() -> {
mCM.registerDefaultNetworkCallback(defaultNetworkCallback);
defaultNetworkCallback.expectAvailableCallbacks(defaultNetwork);
final Range<Integer> myUidRange = new Range<>(myUid, myUid);
runWithShellPermissionIdentity(() -> {
mCM.setVpnDefaultForUids(session, List.of(myUidRange));
}, NETWORK_SETTINGS);
// The VPN will be the only default network for the app, so it's expected to receive
// onLost() callback.
defaultNetworkCallback.eventuallyExpect(CallbackEntry.LOST);
final ArrayList<Network> underlyingNetworks = new ArrayList<>();
underlyingNetworks.add(defaultNetwork);
startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"} /* addresses */,
new String[] {"0.0.0.0/0", "::/0"} /* routes */,
"" /* allowedApplications */, "" /* disallowedApplications */,
null /* proxyInfo */, underlyingNetworks, false /* isAlwaysMetered */);
expectVpnNetwork(defaultNetworkCallback);
}, /* cleanup */ () -> {
stopVpn();
defaultNetworkCallback.eventuallyExpect(CallbackEntry.LOST);
}, /* cleanup */ () -> {
runWithShellPermissionIdentity(() -> {
mCM.setVpnDefaultForUids(session, new ArraySet<>());
}, NETWORK_SETTINGS);
// The default network of the app will be changed back to wifi when the VPN network
// preference feature is disabled.
defaultNetworkCallback.eventuallyExpect(CallbackEntry.AVAILABLE,
NETWORK_CALLBACK_TIMEOUT_MS,
entry -> defaultNetwork.equals(entry.getNetwork()));
}, /* cleanup */ () -> {
mCM.unregisterNetworkCallback(defaultNetworkCallback);
});
}
private ByteBuffer buildIpv4UdpPacket(final Inet4Address dstAddr, final Inet4Address srcAddr,
final short dstPort, final short srcPort, final byte[] payload) throws IOException {
@@ -1756,7 +1814,7 @@ public class VpnTest {
}
private class DetailedBlockedStatusCallback extends TestableNetworkCallback {
public void expectAvailableCallbacks(Network network) {
public void expectAvailableCallbacksWithBlockedReasonNone(Network network) {
super.expectAvailableCallbacks(network, false /* suspended */, true /* validated */,
BLOCKED_REASON_NONE, NETWORK_CALLBACK_TIMEOUT_MS);
}

View File

@@ -120,4 +120,8 @@ public class HostsideVpnTests extends HostsideNetworkTestCase {
public void testBlockIncomingPackets() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testBlockIncomingPackets");
}
public void testSetVpnDefaultForUids() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSetVpnDefaultForUids");
}
}

View File

@@ -233,6 +233,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
@@ -2378,7 +2379,7 @@ public class ConnectivityManagerTest {
}
private class DetailedBlockedStatusCallback extends TestableNetworkCallback {
public void expectAvailableCallbacks(Network network) {
public void expectAvailableCallbacksWithBlockedReasonNone(Network network) {
super.expectAvailableCallbacks(network, false /* suspended */, true /* validated */,
BLOCKED_REASON_NONE, NETWORK_CALLBACK_TIMEOUT_MS);
}
@@ -2431,7 +2432,7 @@ public class ConnectivityManagerTest {
final List<DetailedBlockedStatusCallback> allCallbacks =
List.of(myUidCallback, otherUidCallback);
for (DetailedBlockedStatusCallback callback : allCallbacks) {
callback.expectAvailableCallbacks(defaultNetwork);
callback.expectAvailableCallbacksWithBlockedReasonNone(defaultNetwork);
}
final Range<Integer> myUidRange = new Range<>(myUid, myUid);
@@ -2469,6 +2470,17 @@ public class ConnectivityManagerTest {
runWithShellPermissionIdentity(() -> doTestBlockedStatusCallback(), NETWORK_SETTINGS);
}
@Test
public void testSetVpnDefaultForUids() {
assumeTrue(TestUtils.shouldTestUApis());
final String session = UUID.randomUUID().toString();
assertThrows(NullPointerException.class, () -> mCm.setVpnDefaultForUids(session, null));
assertThrows(SecurityException.class,
() -> mCm.setVpnDefaultForUids(session, new ArraySet<>()));
// For testing the complete behavior of setVpnDefaultForUids(), please refer to
// HostsideVpnTests.
}
private void doTestLegacyLockdownEnabled() throws Exception {
NetworkInfo info = mCm.getActiveNetworkInfo();
assertNotNull(info);

View File

@@ -402,4 +402,27 @@ public class UidRangeUtilsTest {
expected.add(uids20_24);
assertEquals(expected, UidRangeUtils.convertArrayToUidRange(input));
}
@Test
public void testSortRangesByStartUid() throws Exception {
final UidRange uid1 = new UidRange(100, 110);
final UidRange uid2 = new UidRange(120, 130);
final UidRange[] unsortedRanges = new UidRange[] {uid2, uid1};
final UidRange[] sortedRanges = UidRangeUtils.sortRangesByStartUid(unsortedRanges);
assertEquals(uid1, sortedRanges[0]);
assertEquals(uid2, sortedRanges[1]);
}
@Test
public void testSortedRangesContainOverlap() throws Exception {
final UidRange uid1 = new UidRange(100, 110);
final UidRange uid2 = new UidRange(109, 120);
final UidRange uid3 = new UidRange(120, 130);
final UidRange[] overlapRanges1 = new UidRange[] {uid1, uid2};
final UidRange[] overlapRanges2 = new UidRange[] {uid2, uid3};
final UidRange[] notOverlapRanges = new UidRange[] {uid1, uid3};
assertTrue(UidRangeUtils.sortedRangesContainOverlap(overlapRanges1));
assertTrue(UidRangeUtils.sortedRangesContainOverlap(overlapRanges2));
assertFalse(UidRangeUtils.sortedRangesContainOverlap(notOverlapRanges));
}
}