Merge "Improve dumping IPv4 map entries."
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 90d821b..82842bc 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -817,6 +817,7 @@
final int originalOwnerUid = getOwnerUid();
final int[] originalAdministratorUids = getAdministratorUids();
final TransportInfo originalTransportInfo = getTransportInfo();
+ final Set<Integer> originalSubIds = getSubscriptionIds();
clearAll();
if (0 != (originalCapabilities & NET_CAPABILITY_NOT_RESTRICTED)) {
// If the test network is not restricted, then it is only allowed to declare some
@@ -825,6 +826,9 @@
mTransportTypes =
(originalTransportTypes & UNRESTRICTED_TEST_NETWORKS_ALLOWED_TRANSPORTS)
| (1 << TRANSPORT_TEST);
+
+ // SubIds are only allowed for Test Networks that only declare TRANSPORT_TEST.
+ setSubscriptionIds(originalSubIds);
} else {
// If the test transport is restricted, then it may declare any transport.
mTransportTypes = (originalTransportTypes | (1 << TRANSPORT_TEST));
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 15b666a..913b3f3 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -398,6 +398,32 @@
}
/**
+ * The priority value is used when issue uid ranges rules to netd. Netd will use the priority
+ * value and uid ranges to generate corresponding ip rules specific to the given preference.
+ * Thus, any device originated data traffic of the applied uids can be routed to the altered
+ * default network which has highest priority.
+ *
+ * Note: The priority value should be in 0~1000. Larger value means lower priority, see
+ * {@link NativeUidRangeConfig}.
+ */
+ // This is default priority value for those NetworkRequests which doesn't have preference to
+ // alter default network and use the global one.
+ @VisibleForTesting
+ static final int DEFAULT_NETWORK_PRIORITY_NONE = 0;
+ // Used by automotive devices to set the network preferences used to direct traffic at an
+ // application level. See {@link #setOemNetworkPreference}.
+ @VisibleForTesting
+ static final int DEFAULT_NETWORK_PRIORITY_OEM = 10;
+ // Request that a user profile is put by default on a network matching a given preference.
+ // See {@link #setProfileNetworkPreference}.
+ @VisibleForTesting
+ static final int DEFAULT_NETWORK_PRIORITY_PROFILE = 20;
+ // Set by MOBILE_DATA_PREFERRED_UIDS setting. Use mobile data in preference even when
+ // higher-priority networks are connected.
+ @VisibleForTesting
+ static final int DEFAULT_NETWORK_PRIORITY_MOBILE_DATA_PREFERRED = 30;
+
+ /**
* used internally to clear a wakelock when transitioning
* from one net to another. Clear happens when we get a new
* network - EVENT_EXPIRE_NET_TRANSITION_WAKELOCK happens
@@ -1516,7 +1542,7 @@
}
// Note that registering observer for setting do not get initial callback when registering,
- // callers might have self-initialization to update status if need.
+ // callers must fetch the initial value of the setting themselves if needed.
private void registerSettingsCallbacks() {
// Watch for global HTTP proxy changes.
mSettingsObserver.observe(
@@ -4146,8 +4172,10 @@
final NetworkAgentInfo satisfier = nri.getSatisfier();
if (null != satisfier) {
try {
+ // TODO: Passing default network priority to netd.
mNetd.networkRemoveUidRanges(satisfier.network.getNetId(),
- toUidRangeStableParcels(nri.getUids()));
+ toUidRangeStableParcels(nri.getUids())
+ /* nri.getDefaultNetworkPriority() */);
} catch (RemoteException e) {
loge("Exception setting network preference default network", e);
}
@@ -5592,6 +5620,13 @@
// maximum limit of registered callbacks per UID.
final int mAsUid;
+ // Default network priority of this request.
+ private final int mDefaultNetworkPriority;
+
+ int getDefaultNetworkPriority() {
+ return mDefaultNetworkPriority;
+ }
+
// In order to preserve the mapping of NetworkRequest-to-callback when apps register
// callbacks using a returned NetworkRequest, the original NetworkRequest needs to be
// maintained for keying off of. This is only a concern when the original nri
@@ -5621,12 +5656,13 @@
NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r,
@Nullable final PendingIntent pi, @Nullable String callingAttributionTag) {
- this(asUid, Collections.singletonList(r), r, pi, callingAttributionTag);
+ this(asUid, Collections.singletonList(r), r, pi, callingAttributionTag,
+ DEFAULT_NETWORK_PRIORITY_NONE);
}
NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r,
@NonNull final NetworkRequest requestForCallback, @Nullable final PendingIntent pi,
- @Nullable String callingAttributionTag) {
+ @Nullable String callingAttributionTag, final int defaultNetworkPriority) {
ensureAllNetworkRequestsHaveType(r);
mRequests = initializeRequests(r);
mNetworkRequestForCallback = requestForCallback;
@@ -5644,6 +5680,7 @@
*/
mCallbackFlags = NetworkCallback.FLAG_NONE;
mCallingAttributionTag = callingAttributionTag;
+ mDefaultNetworkPriority = defaultNetworkPriority;
}
NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r, @Nullable final Messenger m,
@@ -5673,6 +5710,7 @@
mPerUidCounter.incrementCountOrThrow(mUid);
mCallbackFlags = callbackFlags;
mCallingAttributionTag = callingAttributionTag;
+ mDefaultNetworkPriority = DEFAULT_NETWORK_PRIORITY_NONE;
linkDeathRecipient();
}
@@ -5712,15 +5750,18 @@
mPerUidCounter.incrementCountOrThrow(mUid);
mCallbackFlags = nri.mCallbackFlags;
mCallingAttributionTag = nri.mCallingAttributionTag;
+ mDefaultNetworkPriority = DEFAULT_NETWORK_PRIORITY_NONE;
linkDeathRecipient();
}
NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r) {
- this(asUid, Collections.singletonList(r));
+ this(asUid, Collections.singletonList(r), DEFAULT_NETWORK_PRIORITY_NONE);
}
- NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r) {
- this(asUid, r, r.get(0), null /* pi */, null /* callingAttributionTag */);
+ NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r,
+ final int defaultNetworkPriority) {
+ this(asUid, r, r.get(0), null /* pi */, null /* callingAttributionTag */,
+ defaultNetworkPriority);
}
// True if this NRI is being satisfied. It also accounts for if the nri has its satisifer
@@ -7377,9 +7418,13 @@
maybeCloseSockets(nai, ranges, exemptUids);
try {
if (add) {
- mNetd.networkAddUidRanges(nai.network.netId, ranges);
+ // TODO: Passing default network priority to netd.
+ mNetd.networkAddUidRanges(nai.network.netId, ranges
+ /* DEFAULT_NETWORK_PRIORITY_NONE */);
} else {
- mNetd.networkRemoveUidRanges(nai.network.netId, ranges);
+ // TODO: Passing default network priority to netd.
+ mNetd.networkRemoveUidRanges(nai.network.netId, ranges
+ /* DEFAULT_NETWORK_PRIORITY_NONE */);
}
} catch (Exception e) {
loge("Exception while " + (add ? "adding" : "removing") + " uid ranges " + uidRanges +
@@ -7690,14 +7735,18 @@
+ " any applications to set as the default." + nri);
}
if (null != newDefaultNetwork) {
+ // TODO: Passing default network priority to netd.
mNetd.networkAddUidRanges(
newDefaultNetwork.network.getNetId(),
- toUidRangeStableParcels(nri.getUids()));
+ toUidRangeStableParcels(nri.getUids())
+ /* nri.getDefaultNetworkPriority() */);
}
if (null != oldDefaultNetwork) {
+ // TODO: Passing default network priority to netd.
mNetd.networkRemoveUidRanges(
oldDefaultNetwork.network.getNetId(),
- toUidRangeStableParcels(nri.getUids()));
+ toUidRangeStableParcels(nri.getUids())
+ /* nri.getDefaultNetworkPriority() */);
}
} catch (RemoteException | ServiceSpecificException e) {
loge("Exception setting app default network", e);
@@ -9755,7 +9804,8 @@
nrs.add(createDefaultInternetRequestForTransport(
TYPE_NONE, NetworkRequest.Type.TRACK_DEFAULT));
setNetworkRequestUids(nrs, UidRange.fromIntRanges(pref.capabilities.getUids()));
- final NetworkRequestInfo nri = new NetworkRequestInfo(Process.myUid(), nrs);
+ final NetworkRequestInfo nri = new NetworkRequestInfo(Process.myUid(), nrs,
+ DEFAULT_NETWORK_PRIORITY_PROFILE);
result.add(nri);
}
return result;
@@ -9825,7 +9875,8 @@
ranges.add(new UidRange(uid, uid));
}
setNetworkRequestUids(requests, ranges);
- nris.add(new NetworkRequestInfo(Process.myUid(), requests));
+ nris.add(new NetworkRequestInfo(Process.myUid(), requests,
+ DEFAULT_NETWORK_PRIORITY_MOBILE_DATA_PREFERRED));
return nris;
}
@@ -10069,7 +10120,7 @@
private SparseArray<Set<Integer>> createUidsFromOemNetworkPreferences(
@NonNull final OemNetworkPreferences preference) {
- final SparseArray<Set<Integer>> uids = new SparseArray<>();
+ final SparseArray<Set<Integer>> prefToUids = new SparseArray<>();
final PackageManager pm = mContext.getPackageManager();
final List<UserHandle> users =
mContext.getSystemService(UserManager.class).getUserHandles(true);
@@ -10077,29 +10128,29 @@
if (VDBG || DDBG) {
log("No users currently available for setting the OEM network preference.");
}
- return uids;
+ return prefToUids;
}
for (final Map.Entry<String, Integer> entry :
preference.getNetworkPreferences().entrySet()) {
@OemNetworkPreferences.OemNetworkPreference final int pref = entry.getValue();
- try {
- final int uid = pm.getApplicationInfo(entry.getKey(), 0).uid;
- if (!uids.contains(pref)) {
- uids.put(pref, new ArraySet<>());
+ // Add the rules for all users as this policy is device wide.
+ for (final UserHandle user : users) {
+ try {
+ final int uid = pm.getApplicationInfoAsUser(entry.getKey(), 0, user).uid;
+ if (!prefToUids.contains(pref)) {
+ prefToUids.put(pref, new ArraySet<>());
+ }
+ prefToUids.get(pref).add(uid);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Although this may seem like an error scenario, it is ok that uninstalled
+ // packages are sent on a network preference as the system will watch for
+ // package installations associated with this network preference and update
+ // accordingly. This is done to minimize race conditions on app install.
+ continue;
}
- for (final UserHandle ui : users) {
- // Add the rules for all users as this policy is device wide.
- uids.get(pref).add(ui.getUid(uid));
- }
- } catch (PackageManager.NameNotFoundException e) {
- // Although this may seem like an error scenario, it is ok that uninstalled
- // packages are sent on a network preference as the system will watch for
- // package installations associated with this network preference and update
- // accordingly. This is done so as to minimize race conditions on app install.
- continue;
}
}
- return uids;
+ return prefToUids;
}
private NetworkRequestInfo createNriFromOemNetworkPreferences(
@@ -10135,7 +10186,8 @@
ranges.add(new UidRange(uid, uid));
}
setNetworkRequestUids(requests, ranges);
- return new NetworkRequestInfo(Process.myUid(), requests);
+ return new NetworkRequestInfo(
+ Process.myUid(), requests, DEFAULT_NETWORK_PRIORITY_OEM);
}
private NetworkRequest createUnmeteredNetworkRequest() {
diff --git a/service/src/com/android/server/TestNetworkService.java b/service/src/com/android/server/TestNetworkService.java
index 09873f4..fffd2be 100644
--- a/service/src/com/android/server/TestNetworkService.java
+++ b/service/src/com/android/server/TestNetworkService.java
@@ -48,7 +48,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.NetworkStackConstants;
-import com.android.net.module.util.PermissionUtils;
import java.io.UncheckedIOException;
import java.net.Inet4Address;
@@ -123,6 +122,8 @@
addr.getPrefixLength());
}
+ NetdUtils.setInterfaceUp(mNetd, iface);
+
return new TestNetworkInterface(tunIntf, iface);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -324,14 +325,6 @@
}
try {
- final long token = Binder.clearCallingIdentity();
- try {
- PermissionUtils.enforceNetworkStackPermission(mContext);
- NetdUtils.setInterfaceUp(mNetd, iface);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
-
// Synchronize all accesses to mTestNetworkTracker to prevent the case where:
// 1. TestNetworkAgent successfully binds to death of binder
// 2. Before it is added to the mTestNetworkTracker, binder dies, binderDied() is called
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 1afbfb0..7d30056 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -106,6 +106,8 @@
private static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
+ private static final String EMPTY_STRING = "";
+
protected static final int TYPE_COMPONENT_ACTIVTIY = 0;
protected static final int TYPE_COMPONENT_FOREGROUND_SERVICE = 1;
@@ -206,6 +208,8 @@
final String resultData = getResultData();
if (resultData == null) {
Log.e(TAG, "Received null data from ordered intent");
+ // Offer an empty string so that the code waiting for the result can return.
+ result.offer(EMPTY_STRING);
return;
}
result.offer(resultData);
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 0075b54..1d84a1b 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -151,6 +151,7 @@
import com.android.networkstack.apishim.common.ConnectivityManagerShim;
import com.android.testutils.CompatUtil;
import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRuleKt;
import com.android.testutils.RecorderCallback.CallbackEntry;
import com.android.testutils.SkipPresubmit;
@@ -660,6 +661,31 @@
.build();
}
+ @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ public void testIsPrivateDnsBroken() throws InterruptedException {
+ final String invalidPrivateDnsServer = "invalidhostname.example.com";
+ final String goodPrivateDnsServer = "dns.google";
+ mCtsNetUtils.storePrivateDnsSetting();
+ final TestableNetworkCallback cb = new TestableNetworkCallback();
+ mCm.registerNetworkCallback(makeWifiNetworkRequest(), cb);
+ try {
+ // Verifying the good private DNS sever
+ mCtsNetUtils.setPrivateDnsStrictMode(goodPrivateDnsServer);
+ final Network networkForPrivateDns = mCtsNetUtils.ensureWifiConnected();
+ cb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED, NETWORK_CALLBACK_TIMEOUT_MS,
+ entry -> (!((CallbackEntry.CapabilitiesChanged) entry).getCaps()
+ .isPrivateDnsBroken()) && networkForPrivateDns.equals(entry.getNetwork()));
+
+ // Verifying the broken private DNS sever
+ mCtsNetUtils.setPrivateDnsStrictMode(invalidPrivateDnsServer);
+ cb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED, NETWORK_CALLBACK_TIMEOUT_MS,
+ entry -> (((CallbackEntry.CapabilitiesChanged) entry).getCaps()
+ .isPrivateDnsBroken()) && networkForPrivateDns.equals(entry.getNetwork()));
+ } finally {
+ mCtsNetUtils.restorePrivateDnsSetting();
+ }
+ }
+
/**
* Exercises both registerNetworkCallback and unregisterNetworkCallback. This checks to
* see if we get a callback for the TRANSPORT_WIFI transport type being available.
diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
index c6d8d65..6e9f0cd 100644
--- a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
+++ b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
@@ -39,13 +39,11 @@
import android.net.ConnectivityManager;
import android.net.Ikev2VpnProfile;
import android.net.IpSecAlgorithm;
-import android.net.LinkAddress;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.ProxyInfo;
import android.net.TestNetworkInterface;
-import android.net.TestNetworkManager;
import android.net.VpnManager;
import android.net.cts.util.CtsNetUtils;
import android.os.Build;
@@ -439,41 +437,37 @@
assertEquals(vpnNetwork, cb.lastLostNetwork);
}
- private void doTestStartStopVpnProfile(boolean testIpv6) throws Exception {
- // Non-final; these variables ensure we clean up properly after our test if we have
- // allocated test network resources
- final TestNetworkManager tnm = sContext.getSystemService(TestNetworkManager.class);
- TestNetworkInterface testIface = null;
- TestNetworkCallback tunNetworkCallback = null;
+ private class VerifyStartStopVpnProfileTest implements TestNetworkRunnable.Test {
+ private final boolean mTestIpv6Only;
- try {
- // Build underlying test network
- testIface = tnm.createTunInterface(
- new LinkAddress[] {
- new LinkAddress(LOCAL_OUTER_4, IP4_PREFIX_LEN),
- new LinkAddress(LOCAL_OUTER_6, IP6_PREFIX_LEN)});
+ /**
+ * Constructs the test
+ *
+ * @param testIpv6Only if true, builds a IPv6-only test; otherwise builds a IPv4-only test
+ */
+ VerifyStartStopVpnProfileTest(boolean testIpv6Only) {
+ mTestIpv6Only = testIpv6Only;
+ }
- // Hold on to this callback to ensure network does not get reaped.
- tunNetworkCallback = mCtsNetUtils.setupAndGetTestNetwork(
- testIface.getInterfaceName());
+ @Override
+ public void runTest(TestNetworkInterface testIface, TestNetworkCallback tunNetworkCallback)
+ throws Exception {
final IkeTunUtils tunUtils = new IkeTunUtils(testIface.getFileDescriptor());
- checkStartStopVpnProfileBuildsNetworks(tunUtils, testIpv6);
- } finally {
- // Make sure to stop the VPN profile. This is safe to call multiple times.
+ checkStartStopVpnProfileBuildsNetworks(tunUtils, mTestIpv6Only);
+ }
+
+ @Override
+ public void cleanupTest() {
sVpnMgr.stopProvisionedVpnProfile();
+ }
- if (testIface != null) {
- testIface.getFileDescriptor().close();
- }
-
- if (tunNetworkCallback != null) {
- sCM.unregisterNetworkCallback(tunNetworkCallback);
- }
-
- final Network testNetwork = tunNetworkCallback.currentNetwork;
- if (testNetwork != null) {
- tnm.teardownTestNetwork(testNetwork);
+ @Override
+ public InetAddress[] getTestNetworkAddresses() {
+ if (mTestIpv6Only) {
+ return new InetAddress[] {LOCAL_OUTER_6};
+ } else {
+ return new InetAddress[] {LOCAL_OUTER_4};
}
}
}
@@ -483,9 +477,8 @@
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
// Requires shell permission to update appops.
- runWithShellPermissionIdentity(() -> {
- doTestStartStopVpnProfile(false);
- });
+ runWithShellPermissionIdentity(
+ new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(false)));
}
@Test
@@ -493,9 +486,8 @@
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
// Requires shell permission to update appops.
- runWithShellPermissionIdentity(() -> {
- doTestStartStopVpnProfile(true);
- });
+ runWithShellPermissionIdentity(
+ new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(true)));
}
private static class CertificateAndKey {
diff --git a/tests/cts/net/src/android/net/cts/IpSecAlgorithmImplTest.java b/tests/cts/net/src/android/net/cts/IpSecAlgorithmImplTest.java
new file mode 100644
index 0000000..3b110a4
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/IpSecAlgorithmImplTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2020 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 static android.net.IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305;
+import static android.net.cts.PacketUtils.CHACHA20_POLY1305;
+import static android.net.cts.PacketUtils.CHACHA20_POLY1305_BLK_SIZE;
+import static android.net.cts.PacketUtils.CHACHA20_POLY1305_ICV_LEN;
+import static android.net.cts.PacketUtils.CHACHA20_POLY1305_IV_LEN;
+import static android.net.cts.PacketUtils.CHACHA20_POLY1305_KEY_LEN;
+import static android.net.cts.PacketUtils.CHACHA20_POLY1305_SALT_LEN;
+import static android.net.cts.PacketUtils.ESP_HDRLEN;
+import static android.net.cts.PacketUtils.IP6_HDRLEN;
+import static android.net.cts.PacketUtils.getIpHeader;
+import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
+
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.net.IpSecAlgorithm;
+import android.net.IpSecManager;
+import android.net.IpSecTransform;
+import android.net.Network;
+import android.net.TestNetworkInterface;
+import android.net.cts.PacketUtils.BytePayload;
+import android.net.cts.PacketUtils.EspAeadCipher;
+import android.net.cts.PacketUtils.EspAuth;
+import android.net.cts.PacketUtils.EspAuthNull;
+import android.net.cts.PacketUtils.EspCipher;
+import android.net.cts.PacketUtils.EspHeader;
+import android.net.cts.PacketUtils.IpHeader;
+import android.net.cts.PacketUtils.UdpHeader;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Socket cannot bind in instant app mode")
+public class IpSecAlgorithmImplTest extends IpSecBaseTest {
+ private static final InetAddress LOCAL_ADDRESS =
+ InetAddress.parseNumericAddress("2001:db8:1::1");
+ private static final InetAddress REMOTE_ADDRESS =
+ InetAddress.parseNumericAddress("2001:db8:1::2");
+
+ private static final int REMOTE_PORT = 12345;
+ private static final IpSecManager IPSEC_MANAGER =
+ InstrumentationRegistry.getContext().getSystemService(IpSecManager.class);
+
+ private static class CheckCryptoImplTest implements TestNetworkRunnable.Test {
+ private final IpSecAlgorithm mIpsecEncryptAlgo;
+ private final IpSecAlgorithm mIpsecAuthAlgo;
+ private final IpSecAlgorithm mIpsecAeadAlgo;
+ private final EspCipher mEspCipher;
+ private final EspAuth mEspAuth;
+
+ public CheckCryptoImplTest(
+ IpSecAlgorithm ipsecEncryptAlgo,
+ IpSecAlgorithm ipsecAuthAlgo,
+ IpSecAlgorithm ipsecAeadAlgo,
+ EspCipher espCipher,
+ EspAuth espAuth) {
+ mIpsecEncryptAlgo = ipsecEncryptAlgo;
+ mIpsecAuthAlgo = ipsecAuthAlgo;
+ mIpsecAeadAlgo = ipsecAeadAlgo;
+ mEspCipher = espCipher;
+ mEspAuth = espAuth;
+ }
+
+ private static byte[] buildTransportModeEspPayload(
+ int srcPort, int dstPort, int spi, EspCipher espCipher, EspAuth espAuth)
+ throws Exception {
+ final UdpHeader udpPayload =
+ new UdpHeader(srcPort, dstPort, new BytePayload(TEST_DATA));
+ final IpHeader preEspIpHeader =
+ getIpHeader(
+ udpPayload.getProtocolId(), LOCAL_ADDRESS, REMOTE_ADDRESS, udpPayload);
+
+ final PacketUtils.EspHeader espPayload =
+ new EspHeader(
+ udpPayload.getProtocolId(),
+ spi,
+ 1 /* sequence number */,
+ udpPayload.getPacketBytes(preEspIpHeader),
+ espCipher,
+ espAuth);
+ return espPayload.getPacketBytes(preEspIpHeader);
+ }
+
+ @Override
+ public void runTest(TestNetworkInterface testIface, TestNetworkCallback tunNetworkCallback)
+ throws Exception {
+ final TunUtils tunUtils = new TunUtils(testIface.getFileDescriptor());
+ tunNetworkCallback.waitForAvailable();
+ final Network testNetwork = tunNetworkCallback.currentNetwork;
+
+ final IpSecTransform.Builder transformBuilder =
+ new IpSecTransform.Builder(InstrumentationRegistry.getContext());
+ if (mIpsecAeadAlgo != null) {
+ transformBuilder.setAuthenticatedEncryption(mIpsecAeadAlgo);
+ } else {
+ if (mIpsecEncryptAlgo != null) {
+ transformBuilder.setEncryption(mIpsecEncryptAlgo);
+ }
+ if (mIpsecAuthAlgo != null) {
+ transformBuilder.setAuthentication(mIpsecAuthAlgo);
+ }
+ }
+
+ try (final IpSecManager.SecurityParameterIndex outSpi =
+ IPSEC_MANAGER.allocateSecurityParameterIndex(REMOTE_ADDRESS);
+ final IpSecManager.SecurityParameterIndex inSpi =
+ IPSEC_MANAGER.allocateSecurityParameterIndex(LOCAL_ADDRESS);
+ final IpSecTransform outTransform =
+ transformBuilder.buildTransportModeTransform(LOCAL_ADDRESS, outSpi);
+ final IpSecTransform inTransform =
+ transformBuilder.buildTransportModeTransform(REMOTE_ADDRESS, inSpi);
+ // Bind localSocket to a random available port.
+ final DatagramSocket localSocket = new DatagramSocket(0)) {
+ IPSEC_MANAGER.applyTransportModeTransform(
+ localSocket, IpSecManager.DIRECTION_IN, inTransform);
+ IPSEC_MANAGER.applyTransportModeTransform(
+ localSocket, IpSecManager.DIRECTION_OUT, outTransform);
+
+ // Send ESP packet
+ final DatagramPacket outPacket =
+ new DatagramPacket(
+ TEST_DATA, 0, TEST_DATA.length, REMOTE_ADDRESS, REMOTE_PORT);
+ testNetwork.bindSocket(localSocket);
+ localSocket.send(outPacket);
+ final byte[] outEspPacket =
+ tunUtils.awaitEspPacket(outSpi.getSpi(), false /* useEncap */);
+
+ // Remove transform for good hygiene
+ IPSEC_MANAGER.removeTransportModeTransforms(localSocket);
+
+ // Get the kernel-generated ESP payload
+ final byte[] outEspPayload = new byte[outEspPacket.length - IP6_HDRLEN];
+ System.arraycopy(outEspPacket, IP6_HDRLEN, outEspPayload, 0, outEspPayload.length);
+
+ // Get the IV of the kernel-generated ESP payload
+ final byte[] iv =
+ Arrays.copyOfRange(
+ outEspPayload, ESP_HDRLEN, ESP_HDRLEN + mEspCipher.ivLen);
+
+ // Build ESP payload using the kernel-generated IV and the user space crypto
+ // implementations
+ mEspCipher.updateIv(iv);
+ final byte[] expectedEspPayload =
+ buildTransportModeEspPayload(
+ localSocket.getLocalPort(),
+ REMOTE_PORT,
+ outSpi.getSpi(),
+ mEspCipher,
+ mEspAuth);
+
+ // Compare user-space-generated and kernel-generated ESP payload
+ assertArrayEquals(expectedEspPayload, outEspPayload);
+ }
+ }
+
+ @Override
+ public void cleanupTest() {
+ // Do nothing
+ }
+
+ @Override
+ public InetAddress[] getTestNetworkAddresses() {
+ return new InetAddress[] {LOCAL_ADDRESS};
+ }
+ }
+
+ @Test
+ public void testChaCha20Poly1305() throws Exception {
+ assumeTrue(hasIpSecAlgorithm(AUTH_CRYPT_CHACHA20_POLY1305));
+
+ final byte[] cryptKey = getKeyBytes(CHACHA20_POLY1305_KEY_LEN);
+ final IpSecAlgorithm ipsecAeadAlgo =
+ new IpSecAlgorithm(
+ IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305,
+ cryptKey,
+ CHACHA20_POLY1305_ICV_LEN * 8);
+ final EspAeadCipher espAead =
+ new EspAeadCipher(
+ CHACHA20_POLY1305,
+ CHACHA20_POLY1305_BLK_SIZE,
+ cryptKey,
+ CHACHA20_POLY1305_IV_LEN,
+ CHACHA20_POLY1305_ICV_LEN,
+ CHACHA20_POLY1305_SALT_LEN);
+
+ runWithShellPermissionIdentity(
+ new TestNetworkRunnable(
+ new CheckCryptoImplTest(
+ null /* ipsecEncryptAlgo */,
+ null /* ipsecAuthAlgo */,
+ ipsecAeadAlgo,
+ espAead,
+ EspAuthNull.getInstance())));
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/PacketUtils.java b/tests/cts/net/src/android/net/cts/PacketUtils.java
index 7e622f6..27c9f3b 100644
--- a/tests/cts/net/src/android/net/cts/PacketUtils.java
+++ b/tests/cts/net/src/android/net/cts/PacketUtils.java
@@ -43,7 +43,9 @@
static final int UDP_HDRLEN = 8;
static final int TCP_HDRLEN = 20;
static final int TCP_HDRLEN_WITH_TIMESTAMP_OPT = TCP_HDRLEN + 12;
+ static final int ESP_HDRLEN = 8;
static final int ESP_BLK_SIZE = 4; // ESP has to be 4-byte aligned
+ static final int ESP_TRAILER_LEN = 2;
// Not defined in OsConstants
static final int IPPROTO_IPV4 = 4;
@@ -60,11 +62,14 @@
// AEAD parameters
static final int AES_GCM_IV_LEN = 8;
static final int AES_GCM_BLK_SIZE = 4;
+ static final int CHACHA20_POLY1305_KEY_LEN = 36;
static final int CHACHA20_POLY1305_BLK_SIZE = ESP_BLK_SIZE;
static final int CHACHA20_POLY1305_IV_LEN = 8;
+ static final int CHACHA20_POLY1305_SALT_LEN = 4;
static final int CHACHA20_POLY1305_ICV_LEN = 16;
// Authentication parameters
+ static final int HMAC_SHA256_ICV_LEN = 16;
static final int HMAC_SHA512_KEY_LEN = 64;
static final int HMAC_SHA512_ICV_LEN = 32;
static final int AES_XCBC_KEY_LEN = 16;
@@ -75,6 +80,11 @@
// Encryption algorithms
static final String AES = "AES";
static final String AES_CBC = "AES/CBC/NoPadding";
+
+ // AEAD algorithms
+ static final String CHACHA20_POLY1305 = "ChaCha20/Poly1305/NoPadding";
+
+ // Authentication algorithms
static final String HMAC_SHA_256 = "HmacSHA256";
public interface Payload {
@@ -328,8 +338,9 @@
public final int nextHeader;
public final int spi;
public final int seqNum;
- public final byte[] key;
public final byte[] payload;
+ public final EspCipher cipher;
+ public final EspAuth auth;
/**
* Generic constructor for ESP headers.
@@ -340,11 +351,48 @@
* calculated using the pre-encryption IP header
*/
public EspHeader(int nextHeader, int spi, int seqNum, byte[] key, byte[] payload) {
+ this(nextHeader, spi, seqNum, payload, getDefaultCipher(key), getDefaultAuth(key));
+ }
+
+ /**
+ * Generic constructor for ESP headers that allows configuring encryption and authentication
+ * algortihms.
+ *
+ * <p>For Tunnel mode, payload will be a full IP header + attached payloads
+ *
+ * <p>For Transport mode, payload will be only the attached payloads, but with the checksum
+ * calculated using the pre-encryption IP header
+ */
+ public EspHeader(
+ int nextHeader,
+ int spi,
+ int seqNum,
+ byte[] payload,
+ EspCipher cipher,
+ EspAuth auth) {
this.nextHeader = nextHeader;
this.spi = spi;
this.seqNum = seqNum;
- this.key = key;
this.payload = payload;
+ this.cipher = cipher;
+ this.auth = auth;
+
+ if (cipher instanceof EspCipherNull && auth instanceof EspAuthNull) {
+ throw new IllegalArgumentException("No algorithm is provided");
+ }
+
+ if (cipher instanceof EspAeadCipher && !(auth instanceof EspAuthNull)) {
+ throw new IllegalArgumentException(
+ "AEAD is provided with an authentication" + " algorithm.");
+ }
+ }
+
+ private static EspCipher getDefaultCipher(byte[] key) {
+ return new EspCryptCipher(AES_CBC, AES_CBC_BLK_SIZE, key, AES_CBC_IV_LEN);
+ }
+
+ private static EspAuth getDefaultAuth(byte[] key) {
+ return new EspAuth(HMAC_SHA_256, key, HMAC_SHA256_ICV_LEN);
}
public int getProtocolId() {
@@ -352,9 +400,10 @@
}
public short length() {
- // ALWAYS uses AES-CBC, HMAC-SHA256 (128b trunc len)
- return (short)
- calculateEspPacketSize(payload.length, AES_CBC_IV_LEN, AES_CBC_BLK_SIZE, 128);
+ final int icvLen =
+ cipher instanceof EspAeadCipher ? ((EspAeadCipher) cipher).icvLen : auth.icvLen;
+ return calculateEspPacketSize(
+ payload.length, cipher.ivLen, cipher.blockSize, icvLen * 8);
}
public byte[] getPacketBytes(IpHeader header) throws Exception {
@@ -368,58 +417,12 @@
ByteBuffer espPayloadBuffer = ByteBuffer.allocate(DATA_BUFFER_LEN);
espPayloadBuffer.putInt(spi);
espPayloadBuffer.putInt(seqNum);
- espPayloadBuffer.put(getCiphertext(key));
- espPayloadBuffer.put(getIcv(getByteArrayFromBuffer(espPayloadBuffer)), 0, 16);
+ espPayloadBuffer.put(cipher.getCipherText(nextHeader, payload, spi, seqNum));
+ espPayloadBuffer.put(auth.getIcv(getByteArrayFromBuffer(espPayloadBuffer)));
+
resultBuffer.put(getByteArrayFromBuffer(espPayloadBuffer));
}
-
- private byte[] getIcv(byte[] authenticatedSection) throws GeneralSecurityException {
- Mac sha256HMAC = Mac.getInstance(HMAC_SHA_256);
- SecretKeySpec authKey = new SecretKeySpec(key, HMAC_SHA_256);
- sha256HMAC.init(authKey);
-
- return sha256HMAC.doFinal(authenticatedSection);
- }
-
- /**
- * Encrypts and builds ciphertext block. Includes the IV, Padding and Next-Header blocks
- *
- * <p>The ciphertext does NOT include the SPI/Sequence numbers, or the ICV.
- */
- private byte[] getCiphertext(byte[] key) throws GeneralSecurityException {
- int paddedLen = calculateEspEncryptedLength(payload.length, AES_CBC_BLK_SIZE);
- ByteBuffer paddedPayload = ByteBuffer.allocate(paddedLen);
- paddedPayload.put(payload);
-
- // Add padding - consecutive integers from 0x01
- int pad = 1;
- while (paddedPayload.position() < paddedPayload.limit()) {
- paddedPayload.put((byte) pad++);
- }
-
- paddedPayload.position(paddedPayload.limit() - 2);
- paddedPayload.put((byte) (paddedLen - 2 - payload.length)); // Pad length
- paddedPayload.put((byte) nextHeader);
-
- // Generate Initialization Vector
- byte[] iv = new byte[AES_CBC_IV_LEN];
- new SecureRandom().nextBytes(iv);
- IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
- SecretKeySpec secretKeySpec = new SecretKeySpec(key, AES);
-
- // Encrypt payload
- Cipher cipher = Cipher.getInstance(AES_CBC);
- cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
- byte[] encrypted = cipher.doFinal(getByteArrayFromBuffer(paddedPayload));
-
- // Build ciphertext
- ByteBuffer cipherText = ByteBuffer.allocate(AES_CBC_IV_LEN + encrypted.length);
- cipherText.put(iv);
- cipherText.put(encrypted);
-
- return getByteArrayFromBuffer(cipherText);
- }
}
private static int addAndWrapForChecksum(int currentChecksum, int value) {
@@ -436,15 +439,14 @@
return (short) ((~val) & 0xffff);
}
- public static int calculateEspPacketSize(
+ public static short calculateEspPacketSize(
int payloadLen, int cryptIvLength, int cryptBlockSize, int authTruncLen) {
- final int ESP_HDRLEN = 4 + 4; // SPI + Seq#
final int ICV_LEN = authTruncLen / 8; // Auth trailer; based on truncation length
- payloadLen += cryptIvLength; // Initialization Vector
// Align to block size of encryption algorithm
payloadLen = calculateEspEncryptedLength(payloadLen, cryptBlockSize);
- return payloadLen + ESP_HDRLEN + ICV_LEN;
+ payloadLen += cryptIvLength; // Initialization Vector
+ return (short) (payloadLen + ESP_HDRLEN + ICV_LEN);
}
private static int calculateEspEncryptedLength(int payloadLen, int cryptBlockSize) {
@@ -475,6 +477,195 @@
}
}
+ public abstract static class EspCipher {
+ protected static final int SALT_LEN_UNUSED = 0;
+
+ public final String algoName;
+ public final int blockSize;
+ public final byte[] key;
+ public final int ivLen;
+ public final int saltLen;
+ protected byte[] iv;
+
+ public EspCipher(String algoName, int blockSize, byte[] key, int ivLen, int saltLen) {
+ this.algoName = algoName;
+ this.blockSize = blockSize;
+ this.key = key;
+ this.ivLen = ivLen;
+ this.saltLen = saltLen;
+ this.iv = getIv(ivLen);
+ }
+
+ public void updateIv(byte[] iv) {
+ this.iv = iv;
+ }
+
+ public static byte[] getPaddedPayload(int nextHeader, byte[] payload, int blockSize) {
+ final int paddedLen = calculateEspEncryptedLength(payload.length, blockSize);
+ final ByteBuffer paddedPayload = ByteBuffer.allocate(paddedLen);
+ paddedPayload.put(payload);
+
+ // Add padding - consecutive integers from 0x01
+ byte pad = 1;
+ while (paddedPayload.position() < paddedPayload.limit() - ESP_TRAILER_LEN) {
+ paddedPayload.put((byte) pad++);
+ }
+
+ // Add padding length and next header
+ paddedPayload.put((byte) (paddedLen - ESP_TRAILER_LEN - payload.length));
+ paddedPayload.put((byte) nextHeader);
+
+ return getByteArrayFromBuffer(paddedPayload);
+ }
+
+ private static byte[] getIv(int ivLen) {
+ final byte[] iv = new byte[ivLen];
+ new SecureRandom().nextBytes(iv);
+ return iv;
+ }
+
+ public abstract byte[] getCipherText(int nextHeader, byte[] payload, int spi, int seqNum)
+ throws GeneralSecurityException;
+ }
+
+ public static class EspCipherNull extends EspCipher {
+ private static final String CRYPT_NULL = "CRYPT_NULL";
+ private static final int IV_LEN_UNUSED = 0;
+ private static final byte[] KEY_UNUSED = new byte[0];
+
+ private static final EspCipherNull INSTANCE = new EspCipherNull();
+
+ private EspCipherNull() {
+ super(CRYPT_NULL, ESP_BLK_SIZE, KEY_UNUSED, IV_LEN_UNUSED, SALT_LEN_UNUSED);
+ }
+
+ public static EspCipherNull getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public byte[] getCipherText(int nextHeader, byte[] payload, int spi, int seqNum)
+ throws GeneralSecurityException {
+ return getPaddedPayload(nextHeader, payload, blockSize);
+ }
+ }
+
+ public static class EspCryptCipher extends EspCipher {
+ public EspCryptCipher(String algoName, int blockSize, byte[] key, int ivLen) {
+ super(algoName, blockSize, key, ivLen, SALT_LEN_UNUSED);
+ }
+
+ @Override
+ public byte[] getCipherText(int nextHeader, byte[] payload, int spi, int seqNum)
+ throws GeneralSecurityException {
+ final IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
+ final SecretKeySpec secretKeySpec = new SecretKeySpec(key, algoName);
+
+ // Encrypt payload
+ final Cipher cipher = Cipher.getInstance(algoName);
+ cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
+ final byte[] encrypted =
+ cipher.doFinal(getPaddedPayload(nextHeader, payload, blockSize));
+
+ // Build ciphertext
+ final ByteBuffer cipherText = ByteBuffer.allocate(iv.length + encrypted.length);
+ cipherText.put(iv);
+ cipherText.put(encrypted);
+
+ return getByteArrayFromBuffer(cipherText);
+ }
+ }
+
+ public static class EspAeadCipher extends EspCipher {
+ public final int icvLen;
+
+ public EspAeadCipher(
+ String algoName, int blockSize, byte[] key, int ivLen, int icvLen, int saltLen) {
+ super(algoName, blockSize, key, ivLen, saltLen);
+ this.icvLen = icvLen;
+ }
+
+ @Override
+ public byte[] getCipherText(int nextHeader, byte[] payload, int spi, int seqNum)
+ throws GeneralSecurityException {
+ // Provided key consists of encryption/decryption key plus salt. Salt is used
+ // with ESP payload IV to build IvParameterSpec.
+ final byte[] secretKey = Arrays.copyOfRange(key, 0, key.length - saltLen);
+ final byte[] salt = Arrays.copyOfRange(key, secretKey.length, key.length);
+
+ final SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, algoName);
+
+ final ByteBuffer ivParameterBuffer = ByteBuffer.allocate(saltLen + iv.length);
+ ivParameterBuffer.put(salt);
+ ivParameterBuffer.put(iv);
+ final IvParameterSpec ivParameterSpec = new IvParameterSpec(ivParameterBuffer.array());
+
+ final ByteBuffer aadBuffer = ByteBuffer.allocate(ESP_HDRLEN);
+ aadBuffer.putInt(spi);
+ aadBuffer.putInt(seqNum);
+
+ // Encrypt payload
+ final Cipher cipher = Cipher.getInstance(algoName);
+ cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
+ cipher.updateAAD(aadBuffer.array());
+ final byte[] encryptedTextAndIcv =
+ cipher.doFinal(getPaddedPayload(nextHeader, payload, blockSize));
+
+ // Build ciphertext
+ final ByteBuffer cipherText =
+ ByteBuffer.allocate(iv.length + encryptedTextAndIcv.length);
+ cipherText.put(iv);
+ cipherText.put(encryptedTextAndIcv);
+
+ return getByteArrayFromBuffer(cipherText);
+ }
+ }
+
+ public static class EspAuth {
+ public final String algoName;
+ public final byte[] key;
+ public final int icvLen;
+
+ public EspAuth(String algoName, byte[] key, int icvLen) {
+ this.algoName = algoName;
+ this.key = key;
+ this.icvLen = icvLen;
+ }
+
+ public byte[] getIcv(byte[] authenticatedSection) throws GeneralSecurityException {
+ final Mac mac = Mac.getInstance(algoName);
+ final SecretKeySpec authKey = new SecretKeySpec(key, HMAC_SHA_256);
+ mac.init(authKey);
+
+ final ByteBuffer buffer = ByteBuffer.wrap(mac.doFinal(authenticatedSection));
+ final byte[] icv = new byte[icvLen];
+ buffer.get(icv);
+ return icv;
+ }
+ }
+
+ public static class EspAuthNull extends EspAuth {
+ private static final String AUTH_NULL = "AUTH_NULL";
+ private static final int ICV_LEN_UNUSED = 0;
+ private static final byte[] KEY_UNUSED = new byte[0];
+ private static final byte[] ICV_EMPTY = new byte[0];
+
+ private static final EspAuthNull INSTANCE = new EspAuthNull();
+
+ private EspAuthNull() {
+ super(AUTH_NULL, KEY_UNUSED, ICV_LEN_UNUSED);
+ }
+
+ public static EspAuthNull getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public byte[] getIcv(byte[] authenticatedSection) throws GeneralSecurityException {
+ return ICV_EMPTY;
+ }
+ }
+
/*
* Debug printing
*/
diff --git a/tests/cts/net/src/android/net/cts/TestNetworkRunnable.java b/tests/cts/net/src/android/net/cts/TestNetworkRunnable.java
new file mode 100644
index 0000000..0eb5644
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/TestNetworkRunnable.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2020 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 static android.Manifest.permission.MANAGE_TEST_NETWORKS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
+
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkAddress;
+import android.net.Network;
+import android.net.TestNetworkInterface;
+import android.net.TestNetworkManager;
+import android.net.cts.util.CtsNetUtils;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ThrowingRunnable;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+
+/** This class supports running a test with a test network. */
+public class TestNetworkRunnable implements ThrowingRunnable {
+ private static final int IP4_PREFIX_LEN = 32;
+ private static final int IP6_PREFIX_LEN = 128;
+
+ private static final InetAddress DEFAULT_ADDRESS_4 =
+ InetAddress.parseNumericAddress("192.2.0.2");
+ private static final InetAddress DEFAULT_ADDRESS_6 =
+ InetAddress.parseNumericAddress("2001:db8:1::2");
+
+ private static final Context sContext = InstrumentationRegistry.getContext();
+ private static final ConnectivityManager sCm =
+ sContext.getSystemService(ConnectivityManager.class);
+
+ private final Test mTest;
+
+ public TestNetworkRunnable(Test test) {
+ mTest = test;
+ }
+
+ private void runTest() throws Exception {
+ final TestNetworkManager tnm = sContext.getSystemService(TestNetworkManager.class);
+
+ // Non-final; these variables ensure we clean up properly after our test if we
+ // have allocated test network resources
+ TestNetworkInterface testIface = null;
+ TestNetworkCallback tunNetworkCallback = null;
+
+ final CtsNetUtils ctsNetUtils = new CtsNetUtils(sContext);
+ final InetAddress[] addresses = mTest.getTestNetworkAddresses();
+ final LinkAddress[] linkAddresses = new LinkAddress[addresses.length];
+ for (int i = 0; i < addresses.length; i++) {
+ InetAddress address = addresses[i];
+ if (address instanceof Inet4Address) {
+ linkAddresses[i] = new LinkAddress(address, IP4_PREFIX_LEN);
+ } else {
+ linkAddresses[i] = new LinkAddress(address, IP6_PREFIX_LEN);
+ }
+ }
+
+ try {
+ // Build underlying test network
+ testIface = tnm.createTunInterface(linkAddresses);
+
+ // Hold on to this callback to ensure network does not get reaped.
+ tunNetworkCallback = ctsNetUtils.setupAndGetTestNetwork(testIface.getInterfaceName());
+
+ mTest.runTest(testIface, tunNetworkCallback);
+ } finally {
+ try {
+ mTest.cleanupTest();
+ } catch (Exception e) {
+ // No action
+ }
+
+ if (testIface != null) {
+ testIface.getFileDescriptor().close();
+ }
+
+ if (tunNetworkCallback != null) {
+ sCm.unregisterNetworkCallback(tunNetworkCallback);
+ }
+
+ final Network testNetwork = tunNetworkCallback.currentNetwork;
+ if (testNetwork != null) {
+ tnm.teardownTestNetwork(testNetwork);
+ }
+ }
+ }
+
+ @Override
+ public void run() throws Exception {
+ if (sContext.checkSelfPermission(MANAGE_TEST_NETWORKS) == PERMISSION_GRANTED) {
+ runTest();
+ } else {
+ runWithShellPermissionIdentity(this::runTest, MANAGE_TEST_NETWORKS);
+ }
+ }
+
+ /** Interface for test caller to configure the test that will be run with a test network */
+ public interface Test {
+ /** Runs the test with a test network */
+ void runTest(TestNetworkInterface testIface, TestNetworkCallback tunNetworkCallback)
+ throws Exception;
+
+ /** Cleans up when the test is finished or interrupted */
+ void cleanupTest();
+
+ /** Returns the IP addresses that will be used by the test network */
+ default InetAddress[] getTestNetworkAddresses() {
+ return new InetAddress[] {DEFAULT_ADDRESS_4, DEFAULT_ADDRESS_6};
+ }
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/TunUtils.java b/tests/cts/net/src/android/net/cts/TunUtils.java
index 7887385..d8e39b4 100644
--- a/tests/cts/net/src/android/net/cts/TunUtils.java
+++ b/tests/cts/net/src/android/net/cts/TunUtils.java
@@ -147,6 +147,10 @@
return espPkt; // We've found the packet we're looking for.
}
+ public byte[] awaitEspPacket(int spi, boolean useEncap) throws Exception {
+ return awaitPacket((pkt) -> isEsp(pkt, spi, useEncap));
+ }
+
private static boolean isSpiEqual(byte[] pkt, int espOffset, int spi) {
// Check SPI byte by byte.
return pkt[espOffset] == (byte) ((spi >>> 24) & 0xff)
diff --git a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
index 899295a..6bd2bd5 100644
--- a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
+++ b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
@@ -24,8 +24,8 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -52,6 +52,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class NetworkStatsManagerTest {
+ private static final String TEST_SUBSCRIBER_ID = "subid";
private @Mock INetworkStatsService mService;
private @Mock INetworkStatsSession mStatsSession;
@@ -69,7 +70,6 @@
@Test
public void testQueryDetails() throws RemoteException {
- final String subscriberId = "subid";
final long startTime = 1;
final long endTime = 100;
final int uid1 = 10001;
@@ -113,7 +113,7 @@
.then((InvocationOnMock inv) -> {
NetworkTemplate template = inv.getArgument(0);
assertEquals(MATCH_MOBILE_ALL, template.getMatchRule());
- assertEquals(subscriberId, template.getSubscriberId());
+ assertEquals(TEST_SUBSCRIBER_ID, template.getSubscriberId());
return history1;
});
@@ -124,13 +124,13 @@
.then((InvocationOnMock inv) -> {
NetworkTemplate template = inv.getArgument(0);
assertEquals(MATCH_MOBILE_ALL, template.getMatchRule());
- assertEquals(subscriberId, template.getSubscriberId());
+ assertEquals(TEST_SUBSCRIBER_ID, template.getSubscriberId());
return history2;
});
NetworkStats stats = mManager.queryDetails(
- ConnectivityManager.TYPE_MOBILE, subscriberId, startTime, endTime);
+ ConnectivityManager.TYPE_MOBILE, TEST_SUBSCRIBER_ID, startTime, endTime);
NetworkStats.Bucket bucket = new NetworkStats.Bucket();
@@ -166,35 +166,30 @@
assertFalse(stats.hasNextBucket());
}
- @Test
- public void testQueryDetails_NoSubscriberId() throws RemoteException {
+ private void runQueryDetailsAndCheckTemplate(int networkType, String subscriberId,
+ NetworkTemplate expectedTemplate) throws RemoteException {
final long startTime = 1;
final long endTime = 100;
final int uid1 = 10001;
final int uid2 = 10002;
+ reset(mStatsSession);
when(mService.openSessionForUsageStats(anyInt(), anyString())).thenReturn(mStatsSession);
when(mStatsSession.getRelevantUids()).thenReturn(new int[] { uid1, uid2 });
-
- NetworkStats stats = mManager.queryDetails(
- ConnectivityManager.TYPE_MOBILE, null, startTime, endTime);
-
when(mStatsSession.getHistoryIntervalForUid(any(NetworkTemplate.class),
anyInt(), anyInt(), anyInt(), anyInt(), anyLong(), anyLong()))
.thenReturn(new NetworkStatsHistory(10, 0));
+ NetworkStats stats = mManager.queryDetails(
+ networkType, subscriberId, startTime, endTime);
verify(mStatsSession, times(1)).getHistoryIntervalForUid(
- argThat((NetworkTemplate t) ->
- // No subscriberId: MATCH_MOBILE_WILDCARD template
- t.getMatchRule() == NetworkTemplate.MATCH_MOBILE_WILDCARD),
+ eq(expectedTemplate),
eq(uid1), eq(android.net.NetworkStats.SET_ALL),
eq(android.net.NetworkStats.TAG_NONE),
eq(NetworkStatsHistory.FIELD_ALL), eq(startTime), eq(endTime));
verify(mStatsSession, times(1)).getHistoryIntervalForUid(
- argThat((NetworkTemplate t) ->
- // No subscriberId: MATCH_MOBILE_WILDCARD template
- t.getMatchRule() == NetworkTemplate.MATCH_MOBILE_WILDCARD),
+ eq(expectedTemplate),
eq(uid2), eq(android.net.NetworkStats.SET_ALL),
eq(android.net.NetworkStats.TAG_NONE),
eq(NetworkStatsHistory.FIELD_ALL), eq(startTime), eq(endTime));
@@ -202,6 +197,25 @@
assertFalse(stats.hasNextBucket());
}
+ @Test
+ public void testNetworkTemplateWhenRunningQueryDetails_NoSubscriberId() throws RemoteException {
+ runQueryDetailsAndCheckTemplate(ConnectivityManager.TYPE_MOBILE,
+ null /* subscriberId */, NetworkTemplate.buildTemplateMobileWildcard());
+ runQueryDetailsAndCheckTemplate(ConnectivityManager.TYPE_WIFI,
+ "" /* subscriberId */, NetworkTemplate.buildTemplateWifiWildcard());
+ runQueryDetailsAndCheckTemplate(ConnectivityManager.TYPE_WIFI,
+ null /* subscriberId */, NetworkTemplate.buildTemplateWifiWildcard());
+ }
+
+ @Test
+ public void testNetworkTemplateWhenRunningQueryDetails_MergedCarrierWifi()
+ throws RemoteException {
+ runQueryDetailsAndCheckTemplate(ConnectivityManager.TYPE_WIFI,
+ TEST_SUBSCRIBER_ID,
+ NetworkTemplate.buildTemplateWifi(NetworkTemplate.WIFI_NETWORKID_ALL,
+ TEST_SUBSCRIBER_ID));
+ }
+
private void assertBucketMatches(Entry expected, NetworkStats.Bucket actual) {
assertEquals(expected.uid, actual.getUid());
assertEquals(expected.rxBytes, actual.getRxBytes());
diff --git a/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java b/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java
index fc739fb..bc6dbf2 100644
--- a/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java
+++ b/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java
@@ -24,7 +24,9 @@
import static org.junit.Assert.fail;
import android.net.util.KeepalivePacketDataUtil;
+import android.util.Log;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -38,8 +40,19 @@
private static final byte[] IPV4_KEEPALIVE_SRC_ADDR = {10, 0, 0, 1};
private static final byte[] IPV4_KEEPALIVE_DST_ADDR = {10, 0, 0, 5};
+ private Log.TerribleFailureHandler mOriginalHandler;
+
@Before
- public void setUp() {}
+ public void setUp() {
+ // Terrible failures are logged when using deprecated methods on newer platforms
+ mOriginalHandler = Log.setWtfHandler((tag, what, sys) ->
+ Log.e(tag, "Terrible failure in test", what));
+ }
+
+ @After
+ public void tearDown() {
+ Log.setWtfHandler(mOriginalHandler);
+ }
@Test
public void testFromTcpKeepaliveStableParcelable() throws Exception {
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index a9612de..83d2e1a 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -118,6 +118,8 @@
import static android.os.Process.INVALID_UID;
import static android.system.OsConstants.IPPROTO_TCP;
+import static com.android.server.ConnectivityService.DEFAULT_NETWORK_PRIORITY_MOBILE_DATA_PREFERRED;
+import static com.android.server.ConnectivityService.DEFAULT_NETWORK_PRIORITY_OEM;
import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType;
import static com.android.testutils.ConcurrentUtils.await;
import static com.android.testutils.ConcurrentUtils.durationOf;
@@ -456,6 +458,7 @@
private TestNetworkCallback mDefaultNetworkCallback;
private TestNetworkCallback mSystemDefaultNetworkCallback;
private TestNetworkCallback mProfileDefaultNetworkCallback;
+ private TestNetworkCallback mTestPackageDefaultNetworkCallback;
// State variables required to emulate NetworkPolicyManagerService behaviour.
private int mBlockedReasons = BLOCKED_REASON_NONE;
@@ -1523,6 +1526,8 @@
}
private static final int PRIMARY_USER = 0;
+ private static final int SECONDARY_USER = 10;
+ private static final int TERTIARY_USER = 11;
private static final UidRange PRIMARY_UIDRANGE =
UidRange.createForUser(UserHandle.of(PRIMARY_USER));
private static final int APP1_UID = UserHandle.getUid(PRIMARY_USER, 10100);
@@ -1531,8 +1536,8 @@
private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER, "",
UserInfo.FLAG_PRIMARY);
private static final UserHandle PRIMARY_USER_HANDLE = new UserHandle(PRIMARY_USER);
- private static final int SECONDARY_USER = 10;
private static final UserHandle SECONDARY_USER_HANDLE = new UserHandle(SECONDARY_USER);
+ private static final UserHandle TERTIARY_USER_HANDLE = new UserHandle(TERTIARY_USER);
private static final int RESTRICTED_USER = 1;
private static final UserInfo RESTRICTED_USER_INFO = new UserInfo(RESTRICTED_USER, "",
@@ -10548,29 +10553,30 @@
fail("TOO_MANY_REQUESTS never thrown");
}
- private UidRange createUidRange(int userId) {
- return UidRange.createForUser(UserHandle.of(userId));
+ private void mockGetApplicationInfo(@NonNull final String packageName, final int uid) {
+ mockGetApplicationInfo(packageName, uid, PRIMARY_USER_HANDLE);
}
- private void mockGetApplicationInfo(@NonNull final String packageName, @NonNull final int uid) {
+ private void mockGetApplicationInfo(@NonNull final String packageName, final int uid,
+ @NonNull final UserHandle user) {
final ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.uid = uid;
try {
- when(mPackageManager.getApplicationInfo(eq(packageName), anyInt()))
+ when(mPackageManager.getApplicationInfoAsUser(eq(packageName), anyInt(), eq(user)))
.thenReturn(applicationInfo);
} catch (Exception e) {
fail(e.getMessage());
}
}
- private void mockGetApplicationInfoThrowsNameNotFound(@NonNull final String packageName)
+ private void mockGetApplicationInfoThrowsNameNotFound(@NonNull final String packageName,
+ @NonNull final UserHandle user)
throws Exception {
- when(mPackageManager.getApplicationInfo(eq(packageName), anyInt()))
+ when(mPackageManager.getApplicationInfoAsUser(eq(packageName), anyInt(), eq(user)))
.thenThrow(new PackageManager.NameNotFoundException(packageName));
}
- private void mockHasSystemFeature(@NonNull final String featureName,
- @NonNull final boolean hasFeature) {
+ private void mockHasSystemFeature(@NonNull final String featureName, final boolean hasFeature) {
when(mPackageManager.hasSystemFeature(eq(featureName)))
.thenReturn(hasFeature);
}
@@ -10618,8 +10624,9 @@
mService.new OemNetworkRequestFactory()
.createNrisFromOemNetworkPreferences(
createDefaultOemNetworkPreferences(prefToTest));
-
- final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+ final NetworkRequestInfo nri = nris.iterator().next();
+ assertEquals(DEFAULT_NETWORK_PRIORITY_OEM, nri.getDefaultNetworkPriority());
+ final List<NetworkRequest> mRequests = nri.mRequests;
assertEquals(expectedNumOfNris, nris.size());
assertEquals(expectedNumOfRequests, mRequests.size());
assertTrue(mRequests.get(0).isListen());
@@ -10647,8 +10654,9 @@
mService.new OemNetworkRequestFactory()
.createNrisFromOemNetworkPreferences(
createDefaultOemNetworkPreferences(prefToTest));
-
- final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+ final NetworkRequestInfo nri = nris.iterator().next();
+ assertEquals(DEFAULT_NETWORK_PRIORITY_OEM, nri.getDefaultNetworkPriority());
+ final List<NetworkRequest> mRequests = nri.mRequests;
assertEquals(expectedNumOfNris, nris.size());
assertEquals(expectedNumOfRequests, mRequests.size());
assertTrue(mRequests.get(0).isListen());
@@ -10673,8 +10681,9 @@
mService.new OemNetworkRequestFactory()
.createNrisFromOemNetworkPreferences(
createDefaultOemNetworkPreferences(prefToTest));
-
- final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+ final NetworkRequestInfo nri = nris.iterator().next();
+ assertEquals(DEFAULT_NETWORK_PRIORITY_OEM, nri.getDefaultNetworkPriority());
+ final List<NetworkRequest> mRequests = nri.mRequests;
assertEquals(expectedNumOfNris, nris.size());
assertEquals(expectedNumOfRequests, mRequests.size());
assertTrue(mRequests.get(0).isRequest());
@@ -10696,8 +10705,9 @@
mService.new OemNetworkRequestFactory()
.createNrisFromOemNetworkPreferences(
createDefaultOemNetworkPreferences(prefToTest));
-
- final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+ final NetworkRequestInfo nri = nris.iterator().next();
+ assertEquals(DEFAULT_NETWORK_PRIORITY_OEM, nri.getDefaultNetworkPriority());
+ final List<NetworkRequest> mRequests = nri.mRequests;
assertEquals(expectedNumOfNris, nris.size());
assertEquals(expectedNumOfRequests, mRequests.size());
assertTrue(mRequests.get(0).isRequest());
@@ -10764,16 +10774,18 @@
}
@Test
- public void testOemNetworkRequestFactoryMultipleUsersCorrectlySetsUids()
+ public void testOemNetworkRequestFactoryMultipleUsersSetsUids()
throws Exception {
// Arrange users
- final int secondUser = 10;
- final UserHandle secondUserHandle = new UserHandle(secondUser);
+ final int secondUserTestPackageUid = UserHandle.getUid(SECONDARY_USER, TEST_PACKAGE_UID);
+ final int thirdUserTestPackageUid = UserHandle.getUid(TERTIARY_USER, TEST_PACKAGE_UID);
when(mUserManager.getUserHandles(anyBoolean())).thenReturn(
- Arrays.asList(PRIMARY_USER_HANDLE, secondUserHandle));
+ Arrays.asList(PRIMARY_USER_HANDLE, SECONDARY_USER_HANDLE, TERTIARY_USER_HANDLE));
- // Arrange PackageManager mocks
- mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+ // Arrange PackageManager mocks testing for users who have and don't have a package.
+ mockGetApplicationInfoThrowsNameNotFound(TEST_PACKAGE_NAME, PRIMARY_USER_HANDLE);
+ mockGetApplicationInfo(TEST_PACKAGE_NAME, secondUserTestPackageUid, SECONDARY_USER_HANDLE);
+ mockGetApplicationInfo(TEST_PACKAGE_NAME, thirdUserTestPackageUid, TERTIARY_USER_HANDLE);
// Build OemNetworkPreferences object
final int testOemPref = OEM_NETWORK_PREFERENCE_OEM_PAID;
@@ -10787,8 +10799,8 @@
mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(
pref));
- // UIDs for all users and all managed packages should be present.
- // Two users each with two packages.
+ // UIDs for users with installed packages should be present.
+ // Three users exist, but only two have the test package installed.
final int expectedUidSize = 2;
final List<Range<Integer>> uids =
new ArrayList<>(nris.get(0).mRequests.get(0).networkCapabilities.getUids());
@@ -10796,11 +10808,10 @@
// Sort by uid to access nris by index
uids.sort(Comparator.comparingInt(uid -> uid.getLower()));
- final int secondUserTestPackageUid = UserHandle.getUid(secondUser, TEST_PACKAGE_UID);
- assertEquals(TEST_PACKAGE_UID, (int) uids.get(0).getLower());
- assertEquals(TEST_PACKAGE_UID, (int) uids.get(0).getUpper());
- assertEquals(secondUserTestPackageUid, (int) uids.get(1).getLower());
- assertEquals(secondUserTestPackageUid, (int) uids.get(1).getUpper());
+ assertEquals(secondUserTestPackageUid, (int) uids.get(0).getLower());
+ assertEquals(secondUserTestPackageUid, (int) uids.get(0).getUpper());
+ assertEquals(thirdUserTestPackageUid, (int) uids.get(1).getLower());
+ assertEquals(thirdUserTestPackageUid, (int) uids.get(1).getUpper());
}
@Test
@@ -10961,16 +10972,24 @@
}
private void registerDefaultNetworkCallbacks() {
+ if (mSystemDefaultNetworkCallback != null || mDefaultNetworkCallback != null
+ || mProfileDefaultNetworkCallback != null
+ || mTestPackageDefaultNetworkCallback != null) {
+ throw new IllegalStateException("Default network callbacks already registered");
+ }
+
// Using Manifest.permission.NETWORK_SETTINGS for registerSystemDefaultNetworkCallback()
mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
mSystemDefaultNetworkCallback = new TestNetworkCallback();
mDefaultNetworkCallback = new TestNetworkCallback();
mProfileDefaultNetworkCallback = new TestNetworkCallback();
+ mTestPackageDefaultNetworkCallback = new TestNetworkCallback();
mCm.registerSystemDefaultNetworkCallback(mSystemDefaultNetworkCallback,
new Handler(ConnectivityThread.getInstanceLooper()));
mCm.registerDefaultNetworkCallback(mDefaultNetworkCallback);
registerDefaultNetworkCallbackAsUid(mProfileDefaultNetworkCallback,
TEST_WORK_PROFILE_APP_UID);
+ registerDefaultNetworkCallbackAsUid(mTestPackageDefaultNetworkCallback, TEST_PACKAGE_UID);
// TODO: test using ConnectivityManager#registerDefaultNetworkCallbackAsUid as well.
mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED);
}
@@ -10985,6 +11004,9 @@
if (null != mProfileDefaultNetworkCallback) {
mCm.unregisterNetworkCallback(mProfileDefaultNetworkCallback);
}
+ if (null != mTestPackageDefaultNetworkCallback) {
+ mCm.unregisterNetworkCallback(mTestPackageDefaultNetworkCallback);
+ }
}
private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(
@@ -11601,6 +11623,7 @@
final UidRangeParcel[] uidRanges =
toUidRangeStableParcels(
uidRangesForUids(TEST_PACKAGE_UID, secondUserTestPackageUid));
+ mockGetApplicationInfo(TEST_PACKAGE_NAME, secondUserTestPackageUid, secondUserHandle);
setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME);
// Verify the starting state. No networks should be connected.
@@ -11643,6 +11666,7 @@
final UidRangeParcel[] uidRangesBothUsers =
toUidRangeStableParcels(
uidRangesForUids(TEST_PACKAGE_UID, secondUserTestPackageUid));
+ mockGetApplicationInfo(TEST_PACKAGE_NAME, secondUserTestPackageUid, secondUserHandle);
setupSetOemNetworkPreferenceForPreferenceTest(
networkPref, uidRangesSingleUser, TEST_PACKAGE_NAME);
@@ -11699,7 +11723,7 @@
final UidRangeParcel[] uidRangesSinglePackage =
toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID));
mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
- mockGetApplicationInfoThrowsNameNotFound(packageToInstall);
+ mockGetApplicationInfoThrowsNameNotFound(packageToInstall, PRIMARY_USER_HANDLE);
setOemNetworkPreference(networkPref, TEST_PACKAGE_NAME, packageToInstall);
grantUsingBackgroundNetworksPermissionForUid(Binder.getCallingUid(), packageToInstall);
@@ -11733,7 +11757,7 @@
false /* shouldDestroyNetwork */);
// Set the system to no longer recognize the package to be installed
- mockGetApplicationInfoThrowsNameNotFound(packageToInstall);
+ mockGetApplicationInfoThrowsNameNotFound(packageToInstall, PRIMARY_USER_HANDLE);
// Send a broadcast indicating a package was removed.
final Intent removedIntent = new Intent(ACTION_PACKAGE_REMOVED);
@@ -12874,6 +12898,8 @@
assertEquals(1, nris.size());
assertTrue(nri.isMultilayerRequest());
assertEquals(nri.getUids(), uidRangesForUids(uids));
+ assertEquals(DEFAULT_NETWORK_PRIORITY_MOBILE_DATA_PREFERRED,
+ nri.getDefaultNetworkPriority());
}
/**
@@ -12906,9 +12932,11 @@
@Test
public void testMobileDataPreferredUidsChanged() throws Exception {
final InOrder inorder = inOrder(mMockNetd);
+ registerDefaultNetworkCallbacks();
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
- waitForIdle();
+ mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
final int cellNetId = mCellNetworkAgent.getNetwork().netId;
inorder.verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(
@@ -12919,12 +12947,15 @@
inorder.verify(mMockNetd, never()).networkAddUidRanges(anyInt(), any());
inorder.verify(mMockNetd, never()).networkRemoveUidRanges(anyInt(), any());
+ // Set MOBILE_DATA_PREFERRED_UIDS setting and verify that net id and uid ranges send to netd
final Set<Integer> uids1 = Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID));
final UidRangeParcel[] uidRanges1 = toUidRangeStableParcels(uidRangesForUids(uids1));
setAndUpdateMobileDataPreferredUids(uids1);
inorder.verify(mMockNetd, times(1)).networkAddUidRanges(cellNetId, uidRanges1);
inorder.verify(mMockNetd, never()).networkRemoveUidRanges(anyInt(), any());
+ // Set MOBILE_DATA_PREFERRED_UIDS setting again and verify that old rules are removed and
+ // new rules are added.
final Set<Integer> uids2 = Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID),
PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2),
SECONDARY_USER_HANDLE.getUid(TEST_PACKAGE_UID));
@@ -12932,5 +12963,170 @@
setAndUpdateMobileDataPreferredUids(uids2);
inorder.verify(mMockNetd, times(1)).networkRemoveUidRanges(cellNetId, uidRanges1);
inorder.verify(mMockNetd, times(1)).networkAddUidRanges(cellNetId, uidRanges2);
+
+ // Clear MOBILE_DATA_PREFERRED_UIDS setting again and verify that old rules are removed and
+ // new rules are not added.
+ final Set<Integer> uids3 = Set.of();
+ final UidRangeParcel[] uidRanges3 = toUidRangeStableParcels(uidRangesForUids(uids3));
+ setAndUpdateMobileDataPreferredUids(uids3);
+ inorder.verify(mMockNetd, times(1)).networkRemoveUidRanges(cellNetId, uidRanges2);
+ inorder.verify(mMockNetd, never()).networkAddUidRanges(anyInt(), any());
+ }
+
+ /**
+ * Make sure mobile data preferred uids feature behaves as expected when the mobile network
+ * goes up and down while the uids is set. Make sure they behave as expected whether
+ * there is a general default network or not.
+ */
+ @Test
+ public void testMobileDataPreferenceForMobileNetworkUpDown() throws Exception {
+ final InOrder inorder = inOrder(mMockNetd);
+ // File a request for cell to ensure it doesn't go down.
+ final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
+ final NetworkRequest cellRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR).build();
+ mCm.requestNetwork(cellRequest, cellNetworkCallback);
+ cellNetworkCallback.assertNoCallback();
+
+ registerDefaultNetworkCallbacks();
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+ mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
+ mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
+
+ final int wifiNetId = mWiFiNetworkAgent.getNetwork().netId;
+ inorder.verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(
+ wifiNetId, INetd.PERMISSION_NONE));
+
+ // Initial mobile data preferred uids status.
+ setAndUpdateMobileDataPreferredUids(Set.of());
+ inorder.verify(mMockNetd, never()).networkAddUidRanges(anyInt(), any());
+ inorder.verify(mMockNetd, never()).networkRemoveUidRanges(anyInt(), any());
+
+ // Set MOBILE_DATA_PREFERRED_UIDS setting and verify that wifi net id and uid ranges send to
+ // netd.
+ final Set<Integer> uids = Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID));
+ final UidRangeParcel[] uidRanges = toUidRangeStableParcels(uidRangesForUids(uids));
+ setAndUpdateMobileDataPreferredUids(uids);
+ inorder.verify(mMockNetd, times(1)).networkAddUidRanges(wifiNetId, uidRanges);
+ inorder.verify(mMockNetd, never()).networkRemoveUidRanges(anyInt(), any());
+
+ // Cellular network connected. mTestPackageDefaultNetworkCallback should receive
+ // callback with cellular network and net id and uid ranges should be updated to netd.
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ mDefaultNetworkCallback.assertNoCallback();
+ mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
+
+ final int cellNetId = mCellNetworkAgent.getNetwork().netId;
+ inorder.verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(
+ cellNetId, INetd.PERMISSION_NONE));
+ inorder.verify(mMockNetd, times(1)).networkAddUidRanges(cellNetId, uidRanges);
+ inorder.verify(mMockNetd, times(1)).networkRemoveUidRanges(wifiNetId, uidRanges);
+
+ // Cellular network disconnected. mTestPackageDefaultNetworkCallback should receive
+ // callback with wifi network from fallback request.
+ mCellNetworkAgent.disconnect();
+ mDefaultNetworkCallback.assertNoCallback();
+ cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ mTestPackageDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ mTestPackageDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
+ inorder.verify(mMockNetd, times(1)).networkAddUidRanges(wifiNetId, uidRanges);
+ inorder.verify(mMockNetd, never()).networkRemoveUidRanges(anyInt(), any());
+ inorder.verify(mMockNetd).networkDestroy(cellNetId);
+
+ // Cellular network comes back. mTestPackageDefaultNetworkCallback should receive
+ // callback with cellular network.
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ mDefaultNetworkCallback.assertNoCallback();
+ mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
+
+ final int cellNetId2 = mCellNetworkAgent.getNetwork().netId;
+ inorder.verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(
+ cellNetId2, INetd.PERMISSION_NONE));
+ inorder.verify(mMockNetd, times(1)).networkAddUidRanges(cellNetId2, uidRanges);
+ inorder.verify(mMockNetd, times(1)).networkRemoveUidRanges(wifiNetId, uidRanges);
+
+ // Wifi network disconnected. mTestPackageDefaultNetworkCallback should not receive
+ // any callback.
+ mWiFiNetworkAgent.disconnect();
+ mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+ mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ mTestPackageDefaultNetworkCallback.assertNoCallback();
+ assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
+ waitForIdle();
+ inorder.verify(mMockNetd, never()).networkAddUidRanges(anyInt(), any());
+ inorder.verify(mMockNetd, never()).networkRemoveUidRanges(anyInt(), any());
+ inorder.verify(mMockNetd).networkDestroy(wifiNetId);
+
+ mCm.unregisterNetworkCallback(cellNetworkCallback);
+ }
+
+ @Test
+ public void testSetMobileDataPreferredUids_noIssueToFactory() throws Exception {
+ // First set mobile data preferred uid to create a multi-layer requests: 1. listen for
+ // cellular, 2. track the default network for fallback.
+ setAndUpdateMobileDataPreferredUids(
+ Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)));
+
+ final HandlerThread handlerThread = new HandlerThread("MockFactory");
+ handlerThread.start();
+ NetworkCapabilities internetFilter = new NetworkCapabilities()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+ final MockNetworkFactory internetFactory = new MockNetworkFactory(handlerThread.getLooper(),
+ mServiceContext, "internetFactory", internetFilter, mCsHandlerThread);
+ internetFactory.setScoreFilter(40);
+
+ try {
+ internetFactory.register();
+ // Default internet request only. The first request is listen for cellular network,
+ // which is never sent to factories (it's a LISTEN, not requestable). The second
+ // fallback request is TRACK_DEFAULT which is also not sent to factories.
+ internetFactory.expectRequestAdds(1);
+ internetFactory.assertRequestCountEquals(1);
+
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+
+ // The internet factory however is outscored, and should lose its requests.
+ internetFactory.expectRequestRemove();
+ internetFactory.assertRequestCountEquals(0);
+
+ mCellNetworkAgent.disconnect();
+ // The network satisfying the default internet request has disconnected, so the
+ // internetFactory sees the default request again.
+ internetFactory.expectRequestAdds(1);
+ internetFactory.assertRequestCountEquals(1);
+ } finally {
+ internetFactory.terminate();
+ handlerThread.quitSafely();
+ }
+ }
+
+ /**
+ * Validate request counts are counted accurately on MOBILE_DATA_PREFERRED_UIDS change
+ * on set/replace.
+ */
+ @Test
+ public void testMobileDataPreferredUidsChangedCountsRequestsCorrectlyOnSet() throws Exception {
+ ConnectivitySettingsManager.setMobileDataPreferredUids(mServiceContext,
+ Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)));
+ testRequestCountLimits(() -> {
+ // Set initially to test the limit prior to having existing requests.
+ mService.updateMobileDataPreferredUids();
+ waitForIdle();
+
+ // re-set so as to test the limit as part of replacing existing requests.
+ mService.updateMobileDataPreferredUids();
+ waitForIdle();
+ });
}
}
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index a90fa68..20be5f4 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -57,6 +57,7 @@
public class NsdServiceTest {
static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
+ private static final long CLEANUP_DELAY_MS = 500;
long mTimeoutMs = 100; // non-final so that tests can adjust the value.
@@ -86,20 +87,27 @@
}
@Test
- public void testClientsCanConnectAndDisconnect() {
+ public void testNoDaemonStartedWhenClientsConnect() {
when(mSettings.isEnabled()).thenReturn(true);
NsdService service = makeService();
+ // Creating an NsdManager will not cause any cmds executed, which means
+ // no daemon is started.
NsdManager client1 = connectClient(service);
- verify(mDaemon, timeout(100).times(1)).start();
+ verify(mDaemon, never()).execute(any());
+ // Creating another NsdManager will not cause any cmds executed.
NsdManager client2 = connectClient(service);
+ verify(mDaemon, never()).execute(any());
client1.disconnect();
- client2.disconnect();
+ // Still 1 client remains, daemon shouldn't be stopped.
+ verify(mDaemon, never()).maybeStop();
- verify(mDaemon, timeout(mTimeoutMs).times(1)).stop();
+ client2.disconnect();
+ // All clients are disconnected, stop the daemon after CLEANUP_DELAY_MS.
+ verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
client1.disconnect();
client2.disconnect();
@@ -113,7 +121,8 @@
NsdService service = makeService();
NsdManager client = connectClient(service);
- verify(mDaemon, timeout(100).times(1)).start();
+ verify(mDaemon, never()).maybeStart();
+ verify(mDaemon, never()).execute(any());
NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
request.setPort(2201);
@@ -121,21 +130,24 @@
// Client registration request
NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
client.registerService(request, PROTOCOL, listener1);
+ verify(mDaemon, timeout(mTimeoutMs).times(1)).maybeStart();
verifyDaemonCommand("register 2 a_name a_type 2201");
// Client discovery request
NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
client.discoverServices("a_type", PROTOCOL, listener2);
+ verify(mDaemon, timeout(mTimeoutMs).times(1)).maybeStart();
verifyDaemonCommand("discover 3 a_type");
// Client resolve request
NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
client.resolveService(request, listener3);
+ verify(mDaemon, timeout(mTimeoutMs).times(1)).maybeStart();
verifyDaemonCommand("resolve 4 a_name a_type local.");
- // Client disconnects
+ // Client disconnects, stop the daemon after CLEANUP_DELAY_MS.
client.disconnect();
- verify(mDaemon, timeout(mTimeoutMs).times(1)).stop();
+ verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
// checks that request are cleaned
verifyDaemonCommands("stop-register 2", "stop-discover 3", "stop-resolve 4");
@@ -143,12 +155,38 @@
client.disconnect();
}
+ @Test
+ public void testCleanupDelayNoRequestActive() {
+ when(mSettings.isEnabled()).thenReturn(true);
+ when(mDaemon.execute(any())).thenReturn(true);
+
+ NsdService service = makeService();
+ NsdManager client = connectClient(service);
+
+ NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
+ request.setPort(2201);
+ NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
+ client.registerService(request, PROTOCOL, listener1);
+ verify(mDaemon, timeout(mTimeoutMs).times(1)).maybeStart();
+ verifyDaemonCommand("register 2 a_name a_type 2201");
+
+ client.unregisterService(listener1);
+ verifyDaemonCommand("stop-register 2");
+
+ verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
+ reset(mDaemon);
+ client.disconnect();
+ // Client disconnects, after CLEANUP_DELAY_MS, maybeStop the daemon.
+ verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
+ }
+
NsdService makeService() {
DaemonConnectionSupplier supplier = (callback) -> {
mDaemonCallback = callback;
return mDaemon;
};
- NsdService service = new NsdService(mContext, mSettings, mHandler, supplier);
+ NsdService service = new NsdService(mContext, mSettings,
+ mHandler, supplier, CLEANUP_DELAY_MS);
verify(mDaemon, never()).execute(any(String.class));
return service;
}
@@ -157,6 +195,13 @@
return new NsdManager(mContext, service);
}
+ void verifyDelayMaybeStopDaemon(long cleanupDelayMs) {
+ // Stop daemon shouldn't be called immediately.
+ verify(mDaemon, timeout(mTimeoutMs).times(0)).maybeStop();
+ // Clean up the daemon after CLEANUP_DELAY_MS.
+ verify(mDaemon, timeout(cleanupDelayMs + mTimeoutMs)).maybeStop();
+ }
+
void verifyDaemonCommands(String... wants) {
verifyDaemonCommand(String.join(" ", wants), wants.length);
}