Implement setNetworkPreferenceForUser.

Test: FrameworksNetTests
Change-Id: I8f18083b5857289892fe8adea5f5ea3f5dbe0809
This commit is contained in:
Chalard Jean
2021-02-25 21:46:34 +09:00
parent fa45a68c91
commit b5a139f32a
3 changed files with 269 additions and 12 deletions

View File

@@ -72,6 +72,14 @@ public final class OemNetworkPreferences implements Parcelable {
@NonNull
private final Bundle mNetworkMappings;
/**
* Return whether this object is empty.
* @hide
*/
public boolean isEmpty() {
return mNetworkMappings.keySet().size() == 0;
}
/**
* Return the currently built application package name to {@link OemNetworkPreference} mappings.
* @return the current network preferences map.

View File

@@ -37,6 +37,7 @@ import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
@@ -215,6 +216,7 @@ import com.android.server.connectivity.NetworkNotificationManager;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
import com.android.server.connectivity.NetworkRanker;
import com.android.server.connectivity.PermissionMonitor;
import com.android.server.connectivity.ProfileNetworkPreferences;
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.QosCallbackTracker;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -559,8 +561,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
private static final int EVENT_SET_REQUIRE_VPN_FOR_UIDS = 47;
/**
* used internally when setting the default networks for OemNetworkPreferences.
* obj = OemNetworkPreferences
* Used internally when setting the default networks for OemNetworkPreferences.
* obj = Pair<OemNetworkPreferences, listener>
*/
private static final int EVENT_SET_OEM_NETWORK_PREFERENCE = 48;
@@ -569,6 +571,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
*/
private static final int EVENT_REPORT_NETWORK_ACTIVITY = 49;
/**
* Used internally when setting a network preference for a user profile.
* obj = Pair<ProfileNetworkPreference, Listener>
*/
private static final int EVENT_SET_PROFILE_NETWORK_PREFERENCE = 50;
/**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
@@ -1290,11 +1298,16 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
return createDefaultNetworkCapabilitiesForUidRange(new UidRange(uid, uid));
}
private static NetworkCapabilities createDefaultNetworkCapabilitiesForUidRange(
@NonNull final UidRange uids) {
final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
netCap.setSingleUid(uid);
netCap.setUids(Collections.singleton(uids));
return netCap;
}
@@ -4537,6 +4550,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
handleSetOemNetworkPreference(arg.first, arg.second);
break;
}
case EVENT_SET_PROFILE_NETWORK_PREFERENCE: {
final Pair<ProfileNetworkPreferences.Preference, IOnCompleteListener> arg =
(Pair<ProfileNetworkPreferences.Preference, IOnCompleteListener>)
msg.obj;
handleSetProfileNetworkPreference(arg.first, arg.second);
}
case EVENT_REPORT_NETWORK_ACTIVITY:
mNetworkActivityTracker.handleReportNetworkActivity();
break;
@@ -5897,10 +5916,16 @@ public class ConnectivityService extends IConnectivityManager.Stub
@GuardedBy("mBlockedAppUids")
private final HashSet<Integer> mBlockedAppUids = new HashSet<>();
// Current OEM network preferences.
// Current OEM network preferences. This object must only be written to on the handler thread.
// Since it is immutable and always non-null, other threads may read it if they only care
// about seeing a consistent object but not that it is current.
@NonNull
private OemNetworkPreferences mOemNetworkPreferences =
new OemNetworkPreferences.Builder().build();
// Current per-profile network preferences. This object follows the same threading rules as
// the OEM network preferences above.
@NonNull
private ProfileNetworkPreferences mProfileNetworkPreferences = new ProfileNetworkPreferences();
// The always-on request for an Internet-capable network that apps without a specific default
// fall back to.
@@ -9101,13 +9126,19 @@ public class ConnectivityService extends IConnectivityManager.Stub
mQosCallbackTracker.unregisterCallback(callback);
}
private void enforceAutomotiveDevice() {
final boolean isAutomotiveDevice =
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
if (!isAutomotiveDevice) {
throw new UnsupportedOperationException(
"setOemNetworkPreference() is only available on automotive devices.");
}
// Network preference per-profile and OEM network preferences can't be set at the same
// time, because it is unclear what should happen if both preferences are active for
// one given UID. To make it possible, the stack would have to clarify what would happen
// in case both are active at the same time. The implementation may have to be adjusted
// to implement the resulting rules. For example, a priority could be defined between them,
// where the OEM preference would be considered less or more important than the enterprise
// preference ; this would entail implementing the priorities somehow, e.g. by doing
// UID arithmetic with UID ranges or passing a priority to netd so that the routing rules
// are set at the right level. Other solutions are possible, e.g. merging of the
// preferences for the relevant UIDs.
private static void throwConcurrentPreferenceException() {
throw new IllegalStateException("Can't set NetworkPreferenceForUser and "
+ "set OemNetworkPreference at the same time");
}
/**
@@ -9125,7 +9156,118 @@ public class ConnectivityService extends IConnectivityManager.Stub
public void setProfileNetworkPreference(@NonNull final UserHandle profile,
@ConnectivityManager.ProfileNetworkPreference final int preference,
@Nullable final IOnCompleteListener listener) {
throw new UnsupportedOperationException("Not implemented yet");
Objects.requireNonNull(profile);
PermissionUtils.enforceNetworkStackPermission(mContext);
if (DBG) {
log("setProfileNetworkPreference " + profile + " to " + preference);
}
if (profile.getIdentifier() < 0) {
throw new IllegalArgumentException("Must explicitly specify a user handle ("
+ "UserHandle.CURRENT not supported)");
}
final UserManager um;
try {
um = mContext.createContextAsUser(profile, 0 /* flags */)
.getSystemService(UserManager.class);
} catch (IllegalStateException e) {
throw new IllegalArgumentException("Profile does not exist");
}
if (!um.isManagedProfile()) {
throw new IllegalArgumentException("Profile must be a managed profile");
}
// Strictly speaking, mOemNetworkPreferences should only be touched on the
// handler thread. However it is an immutable object, so reading the reference is
// safe - it's just possible the value is slightly outdated. For the final check,
// see #handleSetProfileNetworkPreference. But if this can be caught here it is a
// lot easier to understand, so opportunistically check it.
if (!mOemNetworkPreferences.isEmpty()) {
throwConcurrentPreferenceException();
}
final NetworkCapabilities nc;
switch (preference) {
case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT:
nc = null;
break;
case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE:
final UidRange uids = UidRange.createForUser(profile);
nc = createDefaultNetworkCapabilitiesForUidRange(uids);
nc.addCapability(NET_CAPABILITY_ENTERPRISE);
nc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
break;
default:
throw new IllegalArgumentException(
"Invalid preference in setProfileNetworkPreference");
}
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_PROFILE_NETWORK_PREFERENCE,
new Pair<>(new ProfileNetworkPreferences.Preference(profile, nc), listener)));
}
private void validateNetworkCapabilitiesOfProfileNetworkPreference(
@Nullable final NetworkCapabilities nc) {
if (null == nc) return; // Null caps are always allowed. It means to remove the setting.
ensureRequestableCapabilities(nc);
}
private ArraySet<NetworkRequestInfo> createNrisFromProfileNetworkPreferences(
@NonNull final ProfileNetworkPreferences prefs) {
final ArraySet<NetworkRequestInfo> result = new ArraySet<>();
for (final ProfileNetworkPreferences.Preference pref : prefs.preferences) {
// The NRI for a user should be comprised of two layers:
// - The request for the capabilities
// - The request for the default network, for fallback. Create an image of it to
// have the correct UIDs in it (also a request can only be part of one NRI, because
// of lookups in 1:1 associations like mNetworkRequests).
// Note that denying a fallback can be implemented simply by not adding the second
// request.
final ArrayList<NetworkRequest> nrs = new ArrayList<>();
nrs.add(createNetworkRequest(NetworkRequest.Type.REQUEST, pref.capabilities));
nrs.add(createDefaultRequest());
setNetworkRequestUids(nrs, pref.capabilities.getUids());
final NetworkRequestInfo nri = new NetworkRequestInfo(nrs);
result.add(nri);
}
return result;
}
private void handleSetProfileNetworkPreference(
@NonNull final ProfileNetworkPreferences.Preference preference,
@Nullable final IOnCompleteListener listener) {
// setProfileNetworkPreference and setOemNetworkPreference are mutually exclusive, in
// particular because it's not clear what preference should win in case both apply
// to the same app.
// The binder call has already checked this, but as mOemNetworkPreferences is only
// touched on the handler thread, it's theoretically not impossible that it has changed
// since.
if (!mOemNetworkPreferences.isEmpty()) {
logwtf("handleSetProfileNetworkPreference, but OEM network preferences not empty");
return;
}
validateNetworkCapabilitiesOfProfileNetworkPreference(preference.capabilities);
mProfileNetworkPreferences = mProfileNetworkPreferences.plus(preference);
final ArraySet<NetworkRequestInfo> nris =
createNrisFromProfileNetworkPreferences(mProfileNetworkPreferences);
replaceDefaultNetworkRequestsForPreference(nris);
// Finally, rematch.
rematchAllNetworksAndRequests();
if (null != listener) {
try {
listener.onComplete();
} catch (RemoteException e) {
loge("Listener for setProfileNetworkPreference has died");
}
}
}
private void enforceAutomotiveDevice() {
final boolean isAutomotiveDevice =
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
if (!isAutomotiveDevice) {
throw new UnsupportedOperationException(
"setOemNetworkPreference() is only available on automotive devices.");
}
}
/**
@@ -9148,6 +9290,15 @@ public class ConnectivityService extends IConnectivityManager.Stub
enforceAutomotiveDevice();
enforceOemNetworkPreferencesPermission();
if (!mProfileNetworkPreferences.isEmpty()) {
// Strictly speaking, mProfileNetworkPreferences should only be touched on the
// handler thread. However it is an immutable object, so reading the reference is
// safe - it's just possible the value is slightly outdated. For the final check,
// see #handleSetOemPreference. But if this can be caught here it is a
// lot easier to understand, so opportunistically check it.
throwConcurrentPreferenceException();
}
Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
validateOemNetworkPreferences(preference);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_OEM_NETWORK_PREFERENCE,
@@ -9171,6 +9322,17 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (DBG) {
log("set OEM network preferences :" + preference.toString());
}
// setProfileNetworkPreference and setOemNetworkPreference are mutually exclusive, in
// particular because it's not clear what preference should win in case both apply
// to the same app.
// The binder call has already checked this, but as mOemNetworkPreferences is only
// touched on the handler thread, it's theoretically not impossible that it has changed
// since.
if (!mProfileNetworkPreferences.isEmpty()) {
logwtf("handleSetOemPreference, but per-profile network preferences not empty");
return;
}
final ArraySet<NetworkRequestInfo> nris =
new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(preference);
replaceDefaultNetworkRequestsForPreference(nris);

View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 2021 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.annotation.Nullable;
import android.net.NetworkCapabilities;
import android.os.UserHandle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A data class containing all the per-profile network preferences.
*
* A given profile can only have one preference.
*/
public class ProfileNetworkPreferences {
/**
* A single preference, as it applies to a given user profile.
*/
public static class Preference {
@NonNull public final UserHandle user;
// Capabilities are only null when sending an object to remove the setting for a user
@Nullable public final NetworkCapabilities capabilities;
public Preference(@NonNull final UserHandle user,
@Nullable final NetworkCapabilities capabilities) {
this.user = user;
this.capabilities = null == capabilities ? null : new NetworkCapabilities(capabilities);
}
/** toString */
public String toString() {
return "[ProfileNetworkPreference user=" + user + " caps=" + capabilities + "]";
}
}
@NonNull public final List<Preference> preferences;
public ProfileNetworkPreferences() {
preferences = Collections.EMPTY_LIST;
}
private ProfileNetworkPreferences(@NonNull final List<Preference> list) {
preferences = Collections.unmodifiableList(list);
}
/**
* Returns a new object consisting of this object plus the passed preference.
*
* If a preference already exists for the same user, it will be replaced by the passed
* preference. Passing a Preference object containing a null capabilities object is equivalent
* to (and indeed, implemented as) removing the preference for this user.
*/
public ProfileNetworkPreferences plus(@NonNull final Preference pref) {
final ArrayList<Preference> newPrefs = new ArrayList<>();
for (final Preference existingPref : preferences) {
if (!existingPref.user.equals(pref.user)) {
newPrefs.add(existingPref);
}
}
if (null != pref.capabilities) {
newPrefs.add(pref);
}
return new ProfileNetworkPreferences(newPrefs);
}
public boolean isEmpty() {
return preferences.isEmpty();
}
}