Merge "Address API review feedback" into tm-dev
diff --git a/framework-t/api/module-lib-current.txt b/framework-t/api/module-lib-current.txt
index 3969d87..216081a 100644
--- a/framework-t/api/module-lib-current.txt
+++ b/framework-t/api/module-lib-current.txt
@@ -42,9 +42,15 @@
}
public class EthernetManager {
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void addEthernetStateListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void addInterfaceStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.EthernetManager.InterfaceStateListener);
+ method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public java.util.List<java.lang.String> getInterfaceList();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void removeEthernetStateListener(@NonNull java.util.function.IntConsumer);
method public void removeInterfaceStateListener(@NonNull android.net.EthernetManager.InterfaceStateListener);
+ method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setEthernetEnabled(boolean);
method public void setIncludeTestInterfaces(boolean);
+ field public static final int ETHERNET_STATE_DISABLED = 0; // 0x0
+ field public static final int ETHERNET_STATE_ENABLED = 1; // 0x1
field public static final int ROLE_CLIENT = 1; // 0x1
field public static final int ROLE_NONE = 0; // 0x0
field public static final int ROLE_SERVER = 2; // 0x2
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index e81f41c..fb932d4 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -303,10 +303,10 @@
public class NearbyManager {
method public static boolean getFastPairScanEnabled(@NonNull android.content.Context, boolean);
method public static void setFastPairScanEnabled(@NonNull android.content.Context, boolean);
- method public void startBroadcast(@NonNull android.nearby.BroadcastRequest, @NonNull java.util.concurrent.Executor, @NonNull android.nearby.BroadcastCallback);
- method public void startScan(@NonNull android.nearby.ScanRequest, @NonNull java.util.concurrent.Executor, @NonNull android.nearby.ScanCallback);
- method public void stopBroadcast(@NonNull android.nearby.BroadcastCallback);
- method public void stopScan(@NonNull android.nearby.ScanCallback);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void startBroadcast(@NonNull android.nearby.BroadcastRequest, @NonNull java.util.concurrent.Executor, @NonNull android.nearby.BroadcastCallback);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int startScan(@NonNull android.nearby.ScanRequest, @NonNull java.util.concurrent.Executor, @NonNull android.nearby.ScanCallback);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void stopBroadcast(@NonNull android.nearby.BroadcastCallback);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void stopScan(@NonNull android.nearby.ScanCallback);
}
public final class PresenceBroadcastRequest extends android.nearby.BroadcastRequest implements android.os.Parcelable {
@@ -466,10 +466,10 @@
package android.net {
public class EthernetManager {
- method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void connectNetwork(@NonNull String, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.BiConsumer<android.net.Network,android.net.EthernetNetworkManagementException>);
- method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void disconnectNetwork(@NonNull String, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.BiConsumer<android.net.Network,android.net.EthernetNetworkManagementException>);
+ method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void disableInterface(@NonNull String, @Nullable java.util.concurrent.Executor, @Nullable android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>);
+ method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void enableInterface(@NonNull String, @Nullable java.util.concurrent.Executor, @Nullable android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public android.net.EthernetManager.TetheredInterfaceRequest requestTetheredInterface(@NonNull java.util.concurrent.Executor, @NonNull android.net.EthernetManager.TetheredInterfaceCallback);
- method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void updateConfiguration(@NonNull String, @NonNull android.net.EthernetNetworkUpdateRequest, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.BiConsumer<android.net.Network,android.net.EthernetNetworkManagementException>);
+ method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void updateConfiguration(@NonNull String, @NonNull android.net.EthernetNetworkUpdateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>);
}
public static interface EthernetManager.TetheredInterfaceCallback {
diff --git a/framework/src/android/net/ITestNetworkManager.aidl b/framework/src/android/net/ITestNetworkManager.aidl
index 2a863ad..847f14e 100644
--- a/framework/src/android/net/ITestNetworkManager.aidl
+++ b/framework/src/android/net/ITestNetworkManager.aidl
@@ -29,8 +29,7 @@
*/
interface ITestNetworkManager
{
- TestNetworkInterface createTunInterface(in LinkAddress[] linkAddrs);
- TestNetworkInterface createTapInterface();
+ TestNetworkInterface createInterface(boolean isTun, boolean bringUp, in LinkAddress[] addrs);
void setupTestNetwork(in String iface, in LinkProperties lp, in boolean isMetered,
in int[] administratorUids, in IBinder binder);
diff --git a/framework/src/android/net/TestNetworkManager.java b/framework/src/android/net/TestNetworkManager.java
index 9ddd2f5..280e497 100644
--- a/framework/src/android/net/TestNetworkManager.java
+++ b/framework/src/android/net/TestNetworkManager.java
@@ -49,6 +49,11 @@
@NonNull private final ITestNetworkManager mService;
+ private static final boolean TAP = false;
+ private static final boolean TUN = true;
+ private static final boolean BRING_UP = true;
+ private static final LinkAddress[] NO_ADDRS = new LinkAddress[0];
+
/** @hide */
public TestNetworkManager(@NonNull ITestNetworkManager service) {
mService = Objects.requireNonNull(service, "missing ITestNetworkManager");
@@ -155,7 +160,7 @@
public TestNetworkInterface createTunInterface(@NonNull Collection<LinkAddress> linkAddrs) {
try {
final LinkAddress[] arr = new LinkAddress[linkAddrs.size()];
- return mService.createTunInterface(linkAddrs.toArray(arr));
+ return mService.createInterface(TUN, BRING_UP, linkAddrs.toArray(arr));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -173,10 +178,28 @@
@NonNull
public TestNetworkInterface createTapInterface() {
try {
- return mService.createTapInterface();
+ return mService.createInterface(TAP, BRING_UP, NO_ADDRS);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
+ /**
+ * Create a tap interface for testing purposes
+ *
+ * @param bringUp whether to bring up the interface before returning it.
+ *
+ * @return A ParcelFileDescriptor of the underlying TAP interface. Close this to tear down the
+ * TAP interface.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_TEST_NETWORKS)
+ @NonNull
+ public TestNetworkInterface createTapInterface(boolean bringUp) {
+ try {
+ return mService.createInterface(TAP, bringUp, NO_ADDRS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml
index faa9998..1af00c7 100644
--- a/service/ServiceConnectivityResources/res/values/config.xml
+++ b/service/ServiceConnectivityResources/res/values/config.xml
@@ -168,4 +168,15 @@
<!-- Regex of wired ethernet ifaces -->
<string translatable="false" name="config_ethernet_iface_regex">eth\\d</string>
+
+ <!-- Ignores Wi-Fi validation failures after roam.
+ If validation fails on a Wi-Fi network after a roam to a new BSSID,
+ assume that the roam temporarily disrupted network connectivity, and
+ ignore all failures until this time has passed.
+ NetworkMonitor will continue to attempt validation, and if it fails after this time has passed,
+ the network will be marked unvalidated.
+
+ Only supported up to S. On T+, the Wi-Fi code should use destroyAndAwaitReplacement in order
+ to ensure that apps see the network disconnect and reconnect. -->
+ <integer translatable="false" name="config_validationFailureAfterRoamIgnoreTimeMillis">-1</integer>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index 9fa6a30..b92dd08 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -40,6 +40,7 @@
<item type="string" name="config_ethernet_tcp_buffers"/>
<item type="array" name="config_ethernet_interfaces"/>
<item type="string" name="config_ethernet_iface_regex"/>
+ <item type="integer" name="config_validationFailureAfterRoamIgnoreTimeMillis" />
</policy>
</overlayable>
</resources>
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 221b65d..d647664 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -199,6 +199,7 @@
import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
+import android.net.wifi.WifiInfo;
import android.os.BatteryStatsManager;
import android.os.Binder;
import android.os.Build;
@@ -348,6 +349,9 @@
private static final int DEFAULT_LINGER_DELAY_MS = 30_000;
private static final int DEFAULT_NASCENT_DELAY_MS = 5_000;
+ // The maximum value for the blocking validation result, in milliseconds.
+ public static final int MAX_VALIDATION_FAILURE_BLOCKING_TIME_MS = 10000;
+
// The maximum number of network request allowed per uid before an exception is thrown.
@VisibleForTesting
static final int MAX_NETWORK_REQUESTS_PER_UID = 100;
@@ -3543,6 +3547,7 @@
case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: {
final NetworkCapabilities networkCapabilities = new NetworkCapabilities(
(NetworkCapabilities) arg.second);
+ maybeUpdateWifiRoamTimestamp(nai, networkCapabilities);
processCapabilitiesFromAgent(nai, networkCapabilities);
updateCapabilities(nai.getCurrentScore(), nai, networkCapabilities);
break;
@@ -3790,15 +3795,22 @@
private void handleNetworkTested(
@NonNull NetworkAgentInfo nai, int testResult, @NonNull String redirectUrl) {
+ final boolean valid = ((testResult & NETWORK_VALIDATION_RESULT_VALID) != 0);
+ if (!valid && shouldIgnoreValidationFailureAfterRoam(nai)) {
+ // Assume the validation failure is due to a temporary failure after roaming
+ // and ignore it. NetworkMonitor will continue to retry validation. If it
+ // continues to fail after the block timeout expires, the network will be
+ // marked unvalidated. If it succeeds, then validation state will not change.
+ return;
+ }
+
+ final boolean wasValidated = nai.lastValidated;
+ final boolean wasDefault = isDefaultNetwork(nai);
final boolean wasPartial = nai.partialConnectivity;
nai.partialConnectivity = ((testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0);
final boolean partialConnectivityChanged =
(wasPartial != nai.partialConnectivity);
- final boolean valid = ((testResult & NETWORK_VALIDATION_RESULT_VALID) != 0);
- final boolean wasValidated = nai.lastValidated;
- final boolean wasDefault = isDefaultNetwork(nai);
-
if (DBG) {
final String logMsg = !TextUtils.isEmpty(redirectUrl)
? " with redirect to " + redirectUrl
@@ -4197,6 +4209,23 @@
return nai.created && !nai.destroyed;
}
+ private boolean shouldIgnoreValidationFailureAfterRoam(NetworkAgentInfo nai) {
+ // T+ devices should use destroyAndAwaitReplacement.
+ if (SdkLevel.isAtLeastT()) return false;
+ final long blockTimeOut = Long.valueOf(mResources.get().getInteger(
+ R.integer.config_validationFailureAfterRoamIgnoreTimeMillis));
+ if (blockTimeOut <= MAX_VALIDATION_FAILURE_BLOCKING_TIME_MS
+ && blockTimeOut >= 0) {
+ final long currentTimeMs = SystemClock.elapsedRealtime();
+ long timeSinceLastRoam = currentTimeMs - nai.lastRoamTimestamp;
+ if (timeSinceLastRoam <= blockTimeOut) {
+ log ("blocked because only " + timeSinceLastRoam + "ms after roam");
+ return true;
+ }
+ }
+ return false;
+ }
+
private void handleNetworkAgentDisconnected(Message msg) {
NetworkAgentInfo nai = (NetworkAgentInfo) msg.obj;
disconnectAndDestroyNetwork(nai);
@@ -9613,6 +9642,18 @@
return ((VpnTransportInfo) ti).getType();
}
+ private void maybeUpdateWifiRoamTimestamp(NetworkAgentInfo nai, NetworkCapabilities nc) {
+ if (nai == null) return;
+ final TransportInfo prevInfo = nai.networkCapabilities.getTransportInfo();
+ final TransportInfo newInfo = nc.getTransportInfo();
+ if (!(prevInfo instanceof WifiInfo) || !(newInfo instanceof WifiInfo)) {
+ return;
+ }
+ if (!TextUtils.equals(((WifiInfo)prevInfo).getBSSID(), ((WifiInfo)newInfo).getBSSID())) {
+ nai.lastRoamTimestamp = SystemClock.elapsedRealtime();
+ }
+ }
+
/**
* @param connectionInfo the connection to resolve.
* @return {@code uid} if the connection is found and the app has permission to observe it
diff --git a/service/src/com/android/server/TestNetworkService.java b/service/src/com/android/server/TestNetworkService.java
index fffd2be..a0bfb4a 100644
--- a/service/src/com/android/server/TestNetworkService.java
+++ b/service/src/com/android/server/TestNetworkService.java
@@ -99,12 +99,14 @@
}
/**
- * Create a TUN or TAP interface with the given interface name and link addresses
+ * Create a TUN or TAP interface with the specified parameters.
*
* <p>This method will return the FileDescriptor to the interface. Close it to tear down the
* interface.
*/
- private TestNetworkInterface createInterface(boolean isTun, LinkAddress[] linkAddrs) {
+ @Override
+ public TestNetworkInterface createInterface(boolean isTun, boolean bringUp,
+ LinkAddress[] linkAddrs) {
enforceTestNetworkPermissions(mContext);
Objects.requireNonNull(linkAddrs, "missing linkAddrs");
@@ -122,7 +124,9 @@
addr.getPrefixLength());
}
- NetdUtils.setInterfaceUp(mNetd, iface);
+ if (bringUp) {
+ NetdUtils.setInterfaceUp(mNetd, iface);
+ }
return new TestNetworkInterface(tunIntf, iface);
} catch (RemoteException e) {
@@ -132,28 +136,6 @@
}
}
- /**
- * Create a TUN interface with the given interface name and link addresses
- *
- * <p>This method will return the FileDescriptor to the TUN interface. Close it to tear down the
- * TUN interface.
- */
- @Override
- public TestNetworkInterface createTunInterface(@NonNull LinkAddress[] linkAddrs) {
- return createInterface(true, linkAddrs);
- }
-
- /**
- * Create a TAP interface with the given interface name
- *
- * <p>This method will return the FileDescriptor to the TAP interface. Close it to tear down the
- * TAP interface.
- */
- @Override
- public TestNetworkInterface createTapInterface() {
- return createInterface(false, new LinkAddress[0]);
- }
-
// Tracker for TestNetworkAgents
@GuardedBy("mTestNetworkTracker")
@NonNull
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index cbfc4f7..b73e2cc 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -192,6 +192,8 @@
public boolean everConnected;
// Whether this network has been destroyed and is being kept temporarily until it is replaced.
public boolean destroyed;
+ // To check how long it has been since last roam.
+ public long lastRoamTimestamp;
// Set to true if this Network successfully passed validation or if it did not satisfy the
// default NetworkRequest in which case validation will not be attempted.
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index ac46054..2885ba7 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -52,6 +52,7 @@
import android.net.Uri;
import android.net.util.SharedLog;
import android.os.Build;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.SystemConfigManager;
@@ -66,7 +67,10 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.CollectionUtils;
+import com.android.networkstack.apishim.ProcessShimImpl;
+import com.android.networkstack.apishim.common.ProcessShim;
import com.android.server.BpfNetMaps;
import java.util.ArrayList;
@@ -95,6 +99,8 @@
private final Context mContext;
private final BpfNetMaps mBpfNetMaps;
+ private static final ProcessShim sProcessShim = ProcessShimImpl.newInstance();
+
@GuardedBy("this")
private final Set<UserHandle> mUsers = new HashSet<>();
@@ -235,6 +241,10 @@
}
}
+ private static boolean hasSdkSandbox(final int uid) {
+ return SdkLevel.isAtLeastT() && Process.isApplicationUid(uid);
+ }
+
// Return the network permission for the passed list of apps. Note that this depends on the
// current settings of the device (See isUidAllowedOnRestrictedNetworks).
private SparseIntArray makeUidsNetworkPerm(final List<PackageInfo> apps) {
@@ -247,6 +257,10 @@
final int permission = getPackageNetdNetworkPermission(app);
if (isHigherNetworkPermission(permission, uidsPerm.get(uid, PERMISSION_NONE))) {
uidsPerm.put(uid, permission);
+ if (hasSdkSandbox(uid)) {
+ int sdkSandboxUid = sProcessShim.toSdkSandboxUid(uid);
+ uidsPerm.put(sdkSandboxUid, permission);
+ }
}
}
return uidsPerm;
@@ -262,7 +276,11 @@
}
final int otherNetdPerms = getNetdPermissionMask(app.requestedPermissions,
app.requestedPermissionsFlags);
- appIdsPerm.put(appId, appIdsPerm.get(appId) | otherNetdPerms);
+ final int permission = appIdsPerm.get(appId) | otherNetdPerms;
+ appIdsPerm.put(appId, permission);
+ if (hasSdkSandbox(appId)) {
+ appIdsPerm.put(sProcessShim.toSdkSandboxUid(appId), permission);
+ }
}
return appIdsPerm;
}
@@ -288,11 +306,19 @@
final SparseIntArray appIdsPerm = new SparseIntArray();
for (final int uid : mSystemConfigManager.getSystemPermissionUids(INTERNET)) {
final int appId = UserHandle.getAppId(uid);
- appIdsPerm.put(appId, appIdsPerm.get(appId) | PERMISSION_INTERNET);
+ final int permission = appIdsPerm.get(appId) | PERMISSION_INTERNET;
+ appIdsPerm.put(appId, permission);
+ if (hasSdkSandbox(appId)) {
+ appIdsPerm.put(sProcessShim.toSdkSandboxUid(appId), permission);
+ }
}
for (final int uid : mSystemConfigManager.getSystemPermissionUids(UPDATE_DEVICE_STATS)) {
final int appId = UserHandle.getAppId(uid);
- appIdsPerm.put(appId, appIdsPerm.get(appId) | PERMISSION_UPDATE_DEVICE_STATS);
+ final int permission = appIdsPerm.get(appId) | PERMISSION_UPDATE_DEVICE_STATS;
+ appIdsPerm.put(appId, permission);
+ if (hasSdkSandbox(appId)) {
+ appIdsPerm.put(sProcessShim.toSdkSandboxUid(appId), permission);
+ }
}
return appIdsPerm;
}
@@ -592,6 +618,12 @@
SparseIntArray apps = new SparseIntArray();
apps.put(uid, permission);
+
+ if (hasSdkSandbox(uid)) {
+ int sdkSandboxUid = sProcessShim.toSdkSandboxUid(uid);
+ mUidToNetworkPerm.put(sdkSandboxUid, permission);
+ apps.put(sdkSandboxUid, permission);
+ }
sendUidsNetworkPermission(apps, true /* add */);
}
@@ -654,13 +686,25 @@
+ ", tPerm=" + permissionToString(trafficPerm));
if (permission != currentPermission) {
final SparseIntArray apps = new SparseIntArray();
+ int sdkSandboxUid = -1;
+ if (hasSdkSandbox(uid)) {
+ sdkSandboxUid = sProcessShim.toSdkSandboxUid(uid);
+ }
if (permission == PERMISSION_NONE) {
mUidToNetworkPerm.delete(uid);
apps.put(uid, PERMISSION_NETWORK); // doesn't matter which permission we pick here
+ if (sdkSandboxUid != -1) {
+ mUidToNetworkPerm.delete(sdkSandboxUid);
+ apps.put(sdkSandboxUid, PERMISSION_NETWORK);
+ }
sendUidsNetworkPermission(apps, false);
} else {
mUidToNetworkPerm.put(uid, permission);
apps.put(uid, permission);
+ if (sdkSandboxUid != -1) {
+ mUidToNetworkPerm.put(sdkSandboxUid, permission);
+ apps.put(sdkSandboxUid, permission);
+ }
sendUidsNetworkPermission(apps, true);
}
}
@@ -828,6 +872,10 @@
void sendPackagePermissionsForAppId(int appId, int permissions) {
SparseIntArray netdPermissionsAppIds = new SparseIntArray();
netdPermissionsAppIds.put(appId, permissions);
+ if (hasSdkSandbox(appId)) {
+ int sdkSandboxAppId = sProcessShim.toSdkSandboxUid(appId);
+ netdPermissionsAppIds.put(sdkSandboxAppId, permissions);
+ }
sendAppIdsTrafficPermission(netdPermissionsAppIds);
}
@@ -925,9 +973,19 @@
// Doesn't matter which permission is set here.
removedUids.put(uid, PERMISSION_NETWORK);
mUidToNetworkPerm.delete(uid);
+ if (hasSdkSandbox(uid)) {
+ int sdkSandboxUid = sProcessShim.toSdkSandboxUid(uid);
+ removedUids.put(sdkSandboxUid, PERMISSION_NETWORK);
+ mUidToNetworkPerm.delete(sdkSandboxUid);
+ }
} else {
updatedUids.put(uid, permission);
mUidToNetworkPerm.put(uid, permission);
+ if (hasSdkSandbox(uid)) {
+ int sdkSandboxUid = sProcessShim.toSdkSandboxUid(uid);
+ updatedUids.put(sdkSandboxUid, permission);
+ mUidToNetworkPerm.put(sdkSandboxUid, permission);
+ }
}
}
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
new file mode 100644
index 0000000..2737258
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -0,0 +1,214 @@
+/*
+ * 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 android.net.cts
+
+import android.Manifest.permission.MANAGE_TEST_NETWORKS
+import android.Manifest.permission.NETWORK_SETTINGS
+import android.net.IpConfiguration
+import android.net.TestNetworkInterface
+import android.net.TestNetworkManager
+import android.platform.test.annotations.AppModeFull
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import com.android.net.module.util.ArrayTrackRecord
+import com.android.net.module.util.TrackRecord
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.SC_V2
+import com.android.testutils.runAsShell
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertNull
+import kotlin.test.fail
+import android.net.cts.EthernetManagerTest.EthernetStateListener.CallbackEntry.InterfaceStateChanged
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.Looper
+import com.android.networkstack.apishim.common.EthernetManagerShim.InterfaceStateListener
+import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_ABSENT
+import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_LINK_DOWN
+import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_LINK_UP
+import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_CLIENT
+import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_NONE
+import com.android.networkstack.apishim.EthernetManagerShimImpl
+import java.util.concurrent.Executor
+import kotlin.test.assertFalse
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+private const val TIMEOUT_MS = 1000L
+private const val NO_CALLBACK_TIMEOUT_MS = 200L
+private val DEFAULT_IP_CONFIGURATION = IpConfiguration(IpConfiguration.IpAssignment.DHCP,
+ IpConfiguration.ProxySettings.NONE, null, null)
+
+@AppModeFull(reason = "Instant apps can't access EthernetManager")
+@RunWith(AndroidJUnit4::class)
+class EthernetManagerTest {
+ // EthernetManager is not updatable before T, so tests do not need to be backwards compatible
+ @get:Rule
+ val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = SC_V2)
+
+ private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
+ private val em by lazy { EthernetManagerShimImpl.newInstance(context) }
+
+ private val createdIfaces = ArrayList<TestNetworkInterface>()
+ private val addedListeners = ArrayList<InterfaceStateListener>()
+
+ private open class EthernetStateListener private constructor(
+ private val history: ArrayTrackRecord<CallbackEntry>
+ ) : InterfaceStateListener,
+ TrackRecord<EthernetStateListener.CallbackEntry> by history {
+ constructor() : this(ArrayTrackRecord())
+
+ val events = history.newReadHead()
+
+ sealed class CallbackEntry {
+ data class InterfaceStateChanged(
+ val iface: String,
+ val state: Int,
+ val role: Int,
+ val configuration: IpConfiguration?
+ ) : CallbackEntry()
+ }
+
+ override fun onInterfaceStateChanged(
+ iface: String,
+ state: Int,
+ role: Int,
+ cfg: IpConfiguration?
+ ) {
+ add(InterfaceStateChanged(iface, state, role, cfg))
+ }
+
+ fun <T : CallbackEntry> expectCallback(expected: T): T {
+ val event = pollForNextCallback()
+ assertEquals(expected, event)
+ return event as T
+ }
+
+ fun expectCallback(iface: TestNetworkInterface, state: Int, role: Int) {
+ expectCallback(InterfaceStateChanged(iface.interfaceName, state, role,
+ if (state != STATE_ABSENT) DEFAULT_IP_CONFIGURATION else null))
+ }
+
+ fun pollForNextCallback(): CallbackEntry {
+ return events.poll(TIMEOUT_MS) ?: fail("Did not receive callback after ${TIMEOUT_MS}ms")
+ }
+
+ fun assertNoCallback() {
+ val cb = events.poll(NO_CALLBACK_TIMEOUT_MS)
+ assertNull(cb, "Expected no callback but got $cb")
+ }
+ }
+
+ @Test
+ public fun testCallbacks() {
+ val executor = HandlerExecutor(Handler(Looper.getMainLooper()))
+
+ // If an interface exists when the callback is registered, it is reported on registration.
+ val iface = runAsShell(MANAGE_TEST_NETWORKS) {
+ createInterface()
+ }
+ val listener = EthernetStateListener()
+ addInterfaceStateListener(executor, listener)
+ listener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
+
+ // If an interface appears, existing callbacks see it.
+ // TODO: fix the up/up/down/up callbacks and only send down/up.
+ val iface2 = runAsShell(MANAGE_TEST_NETWORKS) {
+ createInterface()
+ }
+ listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+ listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+ listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
+ listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+
+ // Removing interfaces first sends link down, then STATE_ABSENT/ROLE_NONE.
+ removeInterface(iface)
+ listener.expectCallback(iface, STATE_LINK_DOWN, ROLE_CLIENT)
+ listener.expectCallback(iface, STATE_ABSENT, ROLE_NONE)
+
+ removeInterface(iface2)
+ listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
+ listener.expectCallback(iface2, STATE_ABSENT, ROLE_NONE)
+ listener.assertNoCallback()
+ }
+
+ @Before
+ fun setUp() {
+ runAsShell(MANAGE_TEST_NETWORKS, NETWORK_SETTINGS) {
+ em.setIncludeTestInterfaces(true)
+ }
+ }
+
+ @After
+ fun tearDown() {
+ runAsShell(MANAGE_TEST_NETWORKS, NETWORK_SETTINGS) {
+ em.setIncludeTestInterfaces(false)
+ for (iface in createdIfaces) {
+ if (iface.fileDescriptor.fileDescriptor.valid()) iface.fileDescriptor.close()
+ }
+ for (listener in addedListeners) {
+ em.removeInterfaceStateListener(listener)
+ }
+ }
+ }
+
+ private fun addInterfaceStateListener(executor: Executor, listener: InterfaceStateListener) {
+ em.addInterfaceStateListener(executor, listener)
+ addedListeners.add(listener)
+ }
+
+ private fun createInterface(): TestNetworkInterface {
+ val tnm = context.getSystemService(TestNetworkManager::class.java)
+ return tnm.createTapInterface(false /* bringUp */).also { createdIfaces.add(it) }
+ }
+
+ private fun removeInterface(iface: TestNetworkInterface) {
+ iface.fileDescriptor.close()
+ createdIfaces.remove(iface)
+ }
+
+ private fun doTestGetInterfaceList() {
+ em.setIncludeTestInterfaces(true)
+
+ // Create two test interfaces and check the return list contains the interface names.
+ val iface1 = createInterface()
+ val iface2 = createInterface()
+ var ifaces = em.getInterfaceList()
+ assertTrue(ifaces.size > 0)
+ assertTrue(ifaces.contains(iface1.getInterfaceName()))
+ assertTrue(ifaces.contains(iface2.getInterfaceName()))
+
+ // Remove one existing test interface and check the return list doesn't contain the
+ // removed interface name.
+ removeInterface(iface1)
+ ifaces = em.getInterfaceList()
+ assertFalse(ifaces.contains(iface1.getInterfaceName()))
+ assertTrue(ifaces.contains(iface2.getInterfaceName()))
+
+ removeInterface(iface2)
+ }
+
+ @Test
+ public fun testGetInterfaceList() {
+ runAsShell(MANAGE_TEST_NETWORKS, NETWORK_SETTINGS) {
+ doTestGetInterfaceList()
+ }
+ }
+}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 6d802af..6eec2eb 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -160,6 +160,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
+import static org.junit.Assume.assumeFalse;
import static org.mockito.AdditionalMatchers.aryEq;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -283,6 +284,7 @@
import android.net.shared.NetworkMonitorUtils;
import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
+import android.net.wifi.WifiInfo;
import android.os.BadParcelableException;
import android.os.BatteryStatsManager;
import android.os.Binder;
@@ -15572,4 +15574,91 @@
assertNull(readHead.poll(TEST_CALLBACK_TIMEOUT_MS, it -> true));
}
+
+ @Test
+ public void testIgnoreValidationAfterRoamDisabled() throws Exception {
+ assumeFalse(SdkLevel.isAtLeastT());
+ // testIgnoreValidationAfterRoam off
+ doReturn(-1).when(mResources)
+ .getInteger(R.integer.config_validationFailureAfterRoamIgnoreTimeMillis);
+
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ NetworkCapabilities wifiNc1 = new NetworkCapabilities()
+ .addTransportType(TRANSPORT_WIFI)
+ .setTransportInfo(new WifiInfo.Builder().setBssid("AA:AA:AA:AA:AA:AA").build());
+ NetworkCapabilities wifiNc2 = new NetworkCapabilities()
+ .addTransportType(TRANSPORT_WIFI)
+ .setTransportInfo(new WifiInfo.Builder().setBssid("BB:BB:BB:BB:BB:BB").build());
+ final LinkProperties wifiLp = new LinkProperties();
+ wifiLp.setInterfaceName(WIFI_IFNAME);
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp, wifiNc1);
+ mWiFiNetworkAgent.connect(true);
+
+ // The default network will be switching to Wi-Fi Network.
+ final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
+ final NetworkRequest wifiRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI).build();
+ mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
+ wifiNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
+ registerDefaultNetworkCallbacks();
+ mDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
+
+ // Wi-Fi roaming from wifiNc1 to wifiNc2.
+ mWiFiNetworkAgent.setNetworkCapabilities(wifiNc2, true);
+ mWiFiNetworkAgent.setNetworkInvalid(false);
+ mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
+ mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ }
+
+ @Test
+ public void testIgnoreValidationAfterRoamEnabled() throws Exception {
+ assumeFalse(SdkLevel.isAtLeastT());
+ // testIgnoreValidationAfterRoam on
+ doReturn(5000).when(mResources)
+ .getInteger(R.integer.config_validationFailureAfterRoamIgnoreTimeMillis);
+
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ NetworkCapabilities wifiNc1 = new NetworkCapabilities()
+ .addTransportType(TRANSPORT_WIFI)
+ .setTransportInfo(new WifiInfo.Builder().setBssid("AA:AA:AA:AA:AA:AA").build());
+ NetworkCapabilities wifiNc2 = new NetworkCapabilities()
+ .addTransportType(TRANSPORT_WIFI)
+ .setTransportInfo(new WifiInfo.Builder().setBssid("BB:BB:BB:BB:BB:BB").build());
+ final LinkProperties wifiLp = new LinkProperties();
+ wifiLp.setInterfaceName(WIFI_IFNAME);
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp, wifiNc1);
+ mWiFiNetworkAgent.connect(true);
+
+ // The default network will be switching to Wi-Fi Network.
+ final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
+ final NetworkRequest wifiRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI).build();
+ mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
+ wifiNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
+ registerDefaultNetworkCallbacks();
+ mDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
+
+ // Wi-Fi roaming from wifiNc1 to wifiNc2.
+ mWiFiNetworkAgent.setNetworkCapabilities(wifiNc2, true);
+ mWiFiNetworkAgent.setNetworkInvalid(false);
+ mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
+
+ // Network validation failed, but the result will be ignored.
+ assertTrue(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
+ NET_CAPABILITY_VALIDATED));
+ mWiFiNetworkAgent.setNetworkValid(false);
+
+ // Behavior of after config_validationFailureAfterRoamIgnoreTimeMillis
+ ConditionVariable waitForValidationBlock = new ConditionVariable();
+ doReturn(50).when(mResources)
+ .getInteger(R.integer.config_validationFailureAfterRoamIgnoreTimeMillis);
+ // Wi-Fi roaming from wifiNc2 to wifiNc1.
+ mWiFiNetworkAgent.setNetworkCapabilities(wifiNc1, true);
+ mWiFiNetworkAgent.setNetworkInvalid(false);
+ waitForValidationBlock.block(150);
+ mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
+ mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
index 6590543..6b379e8 100644
--- a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -77,6 +77,7 @@
import android.net.UidRange;
import android.net.Uri;
import android.os.Build;
+import android.os.Process;
import android.os.SystemConfigManager;
import android.os.UserHandle;
import android.os.UserManager;
@@ -88,7 +89,10 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.CollectionUtils;
+import com.android.networkstack.apishim.ProcessShimImpl;
+import com.android.networkstack.apishim.common.ProcessShim;
import com.android.server.BpfNetMaps;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -153,6 +157,8 @@
private NetdMonitor mNetdMonitor;
private BpfMapMonitor mBpfMapMonitor;
+ private ProcessShim mProcessShim = ProcessShimImpl.newInstance();
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -197,6 +203,10 @@
return mPermissionMonitor.hasRestrictedNetworkPermission(packageInfo);
}
+ private boolean hasSdkSandbox(final int uid) {
+ return SdkLevel.isAtLeastT() && Process.isApplicationUid(uid);
+ }
+
private static PackageInfo systemPackageInfoWithPermissions(String... permissions) {
return packageInfoWithPermissions(
REQUESTED_PERMISSION_GRANTED, permissions, PARTITION_SYSTEM);
@@ -493,6 +503,11 @@
String... permissions) throws Exception {
addPackage(name, uid, permissions);
assertEquals(hasPermission, mPermissionMonitor.hasUseBackgroundNetworksPermission(uid));
+ if (hasSdkSandbox(uid)) {
+ final int sdkSandboxUid = mProcessShim.toSdkSandboxUid(uid);
+ assertEquals(hasPermission,
+ mPermissionMonitor.hasUseBackgroundNetworksPermission(sdkSandboxUid));
+ }
}
@Test
@@ -531,7 +546,7 @@
}).when(mockBpfmap).setNetPermForUids(anyInt(), any(int[].class));
}
- public void expectTrafficPerm(int permission, int... appIds) {
+ public void expectTrafficPerm(int permission, Integer... appIds) {
for (final int appId : appIds) {
if (mAppIdsTrafficPermission.get(appId, DOES_NOT_EXIST) == DOES_NOT_EXIST) {
fail("appId " + appId + " does not exist.");
@@ -540,6 +555,17 @@
fail("appId " + appId + " has wrong permission: "
+ mAppIdsTrafficPermission.get(appId));
}
+ if (hasSdkSandbox(appId)) {
+ int sdkSandboxAppId = mProcessShim.toSdkSandboxUid(appId);
+ if (mAppIdsTrafficPermission.get(sdkSandboxAppId, DOES_NOT_EXIST)
+ == DOES_NOT_EXIST) {
+ fail("SDK sandbox appId " + sdkSandboxAppId + " does not exist.");
+ }
+ if (mAppIdsTrafficPermission.get(sdkSandboxAppId) != permission) {
+ fail("SDK sandbox appId " + sdkSandboxAppId + " has wrong permission: "
+ + mAppIdsTrafficPermission.get(sdkSandboxAppId));
+ }
+ }
}
}
}
@@ -589,6 +615,17 @@
if (mUidsNetworkPermission.get(uid) != permission) {
fail("uid " + uid + " has wrong permission: " + permission);
}
+ if (hasSdkSandbox(uid)) {
+ int sdkSandboxUid = mProcessShim.toSdkSandboxUid(uid);
+ if (mUidsNetworkPermission.get(sdkSandboxUid, DOES_NOT_EXIST)
+ == DOES_NOT_EXIST) {
+ fail("SDK sandbox uid " + uid + " does not exist.");
+ }
+ if (mUidsNetworkPermission.get(sdkSandboxUid) != permission) {
+ fail("SDK sandbox uid " + uid + " has wrong permission: "
+ + permission);
+ }
+ }
}
}
}
@@ -600,6 +637,14 @@
if (mUidsNetworkPermission.get(uid, DOES_NOT_EXIST) != DOES_NOT_EXIST) {
fail("uid " + uid + " has listed permissions, expected none.");
}
+ if (hasSdkSandbox(uid)) {
+ int sdkSandboxUid = mProcessShim.toSdkSandboxUid(uid);
+ if (mUidsNetworkPermission.get(sdkSandboxUid, DOES_NOT_EXIST)
+ != DOES_NOT_EXIST) {
+ fail("SDK sandbox uid " + sdkSandboxUid
+ + " has listed permissions, expected none.");
+ }
+ }
}
}
}
@@ -785,9 +830,18 @@
// MOCK_APPID2: MOCK_PACKAGE2 does not have any permission.
// SYSTEM_APPID1: SYSTEM_PACKAGE1 has internet permission and update device stats permission
// SYSTEM_APPID2: SYSTEM_PACKAGE2 has only update device stats permission.
+ // The SDK sandbox APPIDs must have permissions mirroring the app
SparseIntArray netdPermissionsAppIds = new SparseIntArray();
netdPermissionsAppIds.put(MOCK_APPID1, PERMISSION_INTERNET);
+ if (hasSdkSandbox(MOCK_APPID1)) {
+ netdPermissionsAppIds.put(mProcessShim.toSdkSandboxUid(MOCK_APPID1),
+ PERMISSION_INTERNET);
+ }
netdPermissionsAppIds.put(MOCK_APPID2, PERMISSION_NONE);
+ if (hasSdkSandbox(MOCK_APPID2)) {
+ netdPermissionsAppIds.put(mProcessShim.toSdkSandboxUid(MOCK_APPID2),
+ PERMISSION_NONE);
+ }
netdPermissionsAppIds.put(SYSTEM_APPID1, PERMISSION_TRAFFIC_ALL);
netdPermissionsAppIds.put(SYSTEM_APPID2, PERMISSION_UPDATE_DEVICE_STATS);