Merge "Add tethering bootclasspath fragment to its sdk"
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 8adcbd9..36a2f10 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -48,6 +48,7 @@
import android.net.util.SharedLog;
import android.net.util.TetheringUtils.ForwardedStats;
import android.os.Handler;
+import android.os.SystemClock;
import android.system.ErrnoException;
import android.text.TextUtils;
import android.util.Log;
@@ -773,6 +774,12 @@
}
pw.decreaseIndent();
+ pw.println("BPF stats:");
+ pw.increaseIndent();
+ dumpBpfStats(pw);
+ pw.decreaseIndent();
+ pw.println();
+
pw.println("Forwarding rules:");
pw.increaseIndent();
dumpIpv6UpstreamRules(pw);
@@ -800,6 +807,22 @@
upstreamIfindex), stats.toString()));
}
}
+ private void dumpBpfStats(@NonNull IndentingPrintWriter pw) {
+ try (BpfMap<TetherStatsKey, TetherStatsValue> map = mDeps.getBpfStatsMap()) {
+ if (map == null) {
+ pw.println("No BPF stats map");
+ return;
+ }
+ if (map.isEmpty()) {
+ pw.println("<empty>");
+ }
+ map.forEach((k, v) -> {
+ pw.println(String.format("%s: %s", k, v));
+ });
+ } catch (ErrnoException e) {
+ pw.println("Error dumping BPF stats map: " + e);
+ }
+ }
private void dumpIpv6ForwardingRules(@NonNull IndentingPrintWriter pw) {
if (mIpv6ForwardingRules.size() == 0) {
@@ -849,7 +872,7 @@
}
}
- private String ipv4RuleToString(Tether4Key key, Tether4Value value) {
+ private String ipv4RuleToString(long now, Tether4Key key, Tether4Value value) {
final String private4, public4, dst4;
try {
private4 = InetAddress.getByAddress(key.src4).getHostAddress();
@@ -858,29 +881,43 @@
} catch (UnknownHostException impossible) {
throw new AssertionError("4-byte array not valid IPv4 address!");
}
- return String.format("[%s] %d(%s) %s:%d -> %d(%s) %s:%d -> %s:%d",
+ long ageMs = (now - value.lastUsed) / 1_000_000;
+ return String.format("[%s] %d(%s) %s:%d -> %d(%s) %s:%d -> %s:%d %dms",
key.dstMac, key.iif, getIfName(key.iif), private4, key.srcPort,
value.oif, getIfName(value.oif),
- public4, value.srcPort, dst4, key.dstPort);
+ public4, value.srcPort, dst4, key.dstPort, ageMs);
+ }
+
+ private void dumpIpv4ForwardingRuleMap(long now, BpfMap<Tether4Key, Tether4Value> map,
+ IndentingPrintWriter pw) throws ErrnoException {
+ if (map == null) {
+ pw.println("No IPv4 support");
+ return;
+ }
+ if (map.isEmpty()) {
+ pw.println("No rules");
+ return;
+ }
+ map.forEach((k, v) -> pw.println(ipv4RuleToString(now, k, v)));
}
private void dumpIpv4ForwardingRules(IndentingPrintWriter pw) {
- try (BpfMap<Tether4Key, Tether4Value> map = mDeps.getBpfUpstream4Map()) {
- if (map == null) {
- pw.println("No IPv4 support");
- return;
- }
- if (map.isEmpty()) {
- pw.println("No IPv4 rules");
- return;
- }
- pw.println("IPv4: [inDstMac] iif(iface) src -> nat -> dst");
+ final long now = SystemClock.elapsedRealtimeNanos();
+
+ try (BpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map();
+ BpfMap<Tether4Key, Tether4Value> downstreamMap = mDeps.getBpfDownstream4Map()) {
+ pw.println("IPv4 Upstream: [inDstMac] iif(iface) src -> nat -> dst");
pw.increaseIndent();
- map.forEach((k, v) -> pw.println(ipv4RuleToString(k, v)));
+ dumpIpv4ForwardingRuleMap(now, upstreamMap, pw);
+ pw.decreaseIndent();
+
+ pw.println("IPv4 Downstream: [inDstMac] iif(iface) src -> nat -> dst");
+ pw.increaseIndent();
+ dumpIpv4ForwardingRuleMap(now, downstreamMap, pw);
+ pw.decreaseIndent();
} catch (ErrnoException e) {
pw.println("Error dumping IPv4 map: " + e);
}
- pw.decreaseIndent();
}
/**
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 90d821b..3a07db3 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));
@@ -1565,6 +1569,28 @@
}
/**
+ * Compare if the given NetworkCapabilities have the same UIDs.
+ *
+ * @hide
+ */
+ public static boolean hasSameUids(@Nullable NetworkCapabilities nc1,
+ @Nullable NetworkCapabilities nc2) {
+ final Set<UidRange> uids1 = (nc1 == null) ? null : nc1.mUids;
+ final Set<UidRange> uids2 = (nc2 == null) ? null : nc2.mUids;
+ if (null == uids1) return null == uids2;
+ if (null == uids2) return false;
+ // Make a copy so it can be mutated to check that all ranges in uids2 also are in uids.
+ final Set<UidRange> uids = new ArraySet<>(uids2);
+ for (UidRange range : uids1) {
+ if (!uids.contains(range)) {
+ return false;
+ }
+ uids.remove(range);
+ }
+ return uids.isEmpty();
+ }
+
+ /**
* Tests if the set of UIDs that this network applies to is the same as the passed network.
* <p>
* This test only checks whether equal range objects are in both sets. It will
@@ -1580,19 +1606,7 @@
*/
@VisibleForTesting
public boolean equalsUids(@NonNull NetworkCapabilities nc) {
- Set<UidRange> comparedUids = nc.mUids;
- if (null == comparedUids) return null == mUids;
- if (null == mUids) return false;
- // Make a copy so it can be mutated to check that all ranges in mUids
- // also are in uids.
- final Set<UidRange> uids = new ArraySet<>(mUids);
- for (UidRange range : comparedUids) {
- if (!uids.contains(range)) {
- return false;
- }
- uids.remove(range);
- }
- return uids.isEmpty();
+ return hasSameUids(nc, this);
}
/**
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index f2fb0ba..b9076e9 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
@@ -7300,6 +7341,8 @@
mDnsManager.updateTransportsForNetwork(
nai.network.getNetId(), newNc.getTransportTypes());
}
+
+ maybeSendProxyBroadcast(nai, prevNc, newNc);
}
/** Convenience method to update the capabilities for a given network. */
@@ -7377,9 +7420,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 +
@@ -7388,6 +7435,30 @@
maybeCloseSockets(nai, ranges, exemptUids);
}
+ private boolean isProxySetOnAnyDefaultNetwork() {
+ ensureRunningOnConnectivityServiceThread();
+ for (final NetworkRequestInfo nri : mDefaultNetworkRequests) {
+ final NetworkAgentInfo nai = nri.getSatisfier();
+ if (nai != null && nai.linkProperties.getHttpProxy() != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void maybeSendProxyBroadcast(NetworkAgentInfo nai, NetworkCapabilities prevNc,
+ NetworkCapabilities newNc) {
+ // When the apps moved from/to a VPN, a proxy broadcast is needed to inform the apps that
+ // the proxy might be changed since the default network satisfied by the apps might also
+ // changed.
+ // TODO: Try to track the default network that apps use and only send a proxy broadcast when
+ // that happens to prevent false alarms.
+ if (nai.isVPN() && nai.everConnected && !NetworkCapabilities.hasSameUids(prevNc, newNc)
+ && (nai.linkProperties.getHttpProxy() != null || isProxySetOnAnyDefaultNetwork())) {
+ mProxyTracker.sendProxyBroadcast();
+ }
+ }
+
private void updateUids(NetworkAgentInfo nai, NetworkCapabilities prevNc,
NetworkCapabilities newNc) {
Set<UidRange> prevRanges = null == prevNc ? null : prevNc.getUidRanges();
@@ -7690,14 +7761,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 +9830,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 +9901,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;
}
@@ -10135,7 +10212,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/Android.bp b/tests/cts/net/Android.bp
index cd69b13..e28c33f 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -38,6 +38,7 @@
srcs: [
"src/**/*.java",
"src/**/*.kt",
+ ":ike-aes-xcbc",
],
jarjar_rules: "jarjar-rules-shared.txt",
static_libs: [
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..080f7f9
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/IpSecAlgorithmImplTest.java
@@ -0,0 +1,288 @@
+/*
+ * 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_AES_XCBC;
+import static android.net.IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305;
+import static android.net.IpSecAlgorithm.CRYPT_AES_CTR;
+import static android.net.cts.PacketUtils.AES_CTR;
+import static android.net.cts.PacketUtils.AES_CTR_BLK_SIZE;
+import static android.net.cts.PacketUtils.AES_CTR_IV_LEN;
+import static android.net.cts.PacketUtils.AES_CTR_KEY_LEN_20;
+import static android.net.cts.PacketUtils.AES_CTR_KEY_LEN_28;
+import static android.net.cts.PacketUtils.AES_CTR_KEY_LEN_36;
+import static android.net.cts.PacketUtils.AES_CTR_SALT_LEN;
+import static android.net.cts.PacketUtils.AES_XCBC;
+import static android.net.cts.PacketUtils.AES_XCBC_ICV_LEN;
+import static android.net.cts.PacketUtils.AES_XCBC_KEY_LEN;
+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.EspCipherNull;
+import android.net.cts.PacketUtils.EspCryptCipher;
+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};
+ }
+ }
+
+ private void checkAesCtr(int keyLen) throws Exception {
+ final byte[] cryptKey = getKeyBytes(keyLen);
+
+ final IpSecAlgorithm ipsecEncryptAlgo =
+ new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CTR, cryptKey);
+ final EspCipher espCipher =
+ new EspCryptCipher(
+ AES_CTR, AES_CTR_BLK_SIZE, cryptKey, AES_CTR_IV_LEN, AES_CTR_SALT_LEN);
+
+ runWithShellPermissionIdentity(new TestNetworkRunnable(new CheckCryptoImplTest(
+ ipsecEncryptAlgo, null /* ipsecAuthAlgo */, null /* ipsecAeadAlgo */,
+ espCipher, EspAuthNull.getInstance())));
+ }
+
+ @Test
+ public void testAesCtr160() throws Exception {
+ assumeTrue(hasIpSecAlgorithm(CRYPT_AES_CTR));
+
+ checkAesCtr(AES_CTR_KEY_LEN_20);
+ }
+
+ @Test
+ public void testAesCtr224() throws Exception {
+ assumeTrue(hasIpSecAlgorithm(CRYPT_AES_CTR));
+
+ checkAesCtr(AES_CTR_KEY_LEN_28);
+ }
+
+ @Test
+ public void testAesCtr288() throws Exception {
+ assumeTrue(hasIpSecAlgorithm(CRYPT_AES_CTR));
+
+ checkAesCtr(AES_CTR_KEY_LEN_36);
+ }
+
+ @Test
+ public void testAesXcbc() throws Exception {
+ assumeTrue(hasIpSecAlgorithm(AUTH_AES_XCBC));
+
+ final byte[] authKey = getKeyBytes(AES_XCBC_KEY_LEN);
+ final IpSecAlgorithm ipsecAuthAlgo =
+ new IpSecAlgorithm(IpSecAlgorithm.AUTH_AES_XCBC, authKey, AES_XCBC_ICV_LEN * 8);
+ final EspAuth espAuth = new EspAuth(AES_XCBC, authKey, AES_XCBC_ICV_LEN);
+
+ runWithShellPermissionIdentity(new TestNetworkRunnable(new CheckCryptoImplTest(
+ null /* ipsecEncryptAlgo */, ipsecAuthAlgo, null /* ipsecAeadAlgo */,
+ EspCipherNull.getInstance(), espAuth)));
+ }
+
+ @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/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index e7e1d67..5f79a3e 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -33,7 +33,7 @@
import static android.net.cts.PacketUtils.AES_CMAC_KEY_LEN;
import static android.net.cts.PacketUtils.AES_CTR_BLK_SIZE;
import static android.net.cts.PacketUtils.AES_CTR_IV_LEN;
-import static android.net.cts.PacketUtils.AES_CTR_KEY_LEN;
+import static android.net.cts.PacketUtils.AES_CTR_KEY_LEN_20;
import static android.net.cts.PacketUtils.AES_GCM_BLK_SIZE;
import static android.net.cts.PacketUtils.AES_GCM_IV_LEN;
import static android.net.cts.PacketUtils.AES_XCBC_ICV_LEN;
@@ -928,7 +928,7 @@
}
private static IpSecAlgorithm buildCryptAesCtr() throws Exception {
- return new IpSecAlgorithm(CRYPT_AES_CTR, getKeyBytes(AES_CTR_KEY_LEN));
+ return new IpSecAlgorithm(CRYPT_AES_CTR, getKeyBytes(AES_CTR_KEY_LEN_20));
}
private static IpSecAlgorithm buildAuthHmacSha512() throws Exception {
diff --git a/tests/cts/net/src/android/net/cts/PacketUtils.java b/tests/cts/net/src/android/net/cts/PacketUtils.java
index 7e622f6..0b3bad4 100644
--- a/tests/cts/net/src/android/net/cts/PacketUtils.java
+++ b/tests/cts/net/src/android/net/cts/PacketUtils.java
@@ -19,6 +19,8 @@
import static android.system.OsConstants.IPPROTO_IPV6;
import static android.system.OsConstants.IPPROTO_UDP;
+import com.android.internal.net.ipsec.ike.crypto.AesXCbcImpl;
+
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
@@ -27,6 +29,8 @@
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
import javax.crypto.Cipher;
import javax.crypto.Mac;
@@ -43,7 +47,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;
@@ -52,19 +58,25 @@
// Encryption parameters
static final int AES_CBC_IV_LEN = 16;
static final int AES_CBC_BLK_SIZE = 16;
+ static final int AES_CTR_SALT_LEN = 4;
- static final int AES_CTR_KEY_LEN = 20;
+ static final int AES_CTR_KEY_LEN_20 = 20;
+ static final int AES_CTR_KEY_LEN_28 = 28;
+ static final int AES_CTR_KEY_LEN_36 = 36;
static final int AES_CTR_BLK_SIZE = ESP_BLK_SIZE;
static final int AES_CTR_IV_LEN = 8;
// 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;
@@ -72,10 +84,24 @@
static final int AES_CMAC_KEY_LEN = 16;
static final int AES_CMAC_ICV_LEN = 12;
+ // Block counter field should be 32 bits and starts from value one as per RFC 3686
+ static final byte[] AES_CTR_INITIAL_COUNTER = new byte[] {0x00, 0x00, 0x00, 0x01};
+
// Encryption algorithms
static final String AES = "AES";
static final String AES_CBC = "AES/CBC/NoPadding";
+ static final String AES_CTR = "AES/CTR/NoPadding";
+
+ // AEAD algorithms
+ static final String CHACHA20_POLY1305 = "ChaCha20/Poly1305/NoPadding";
+
+ // Authentication algorithms
+ static final String HMAC_MD5 = "HmacMD5";
+ static final String HMAC_SHA1 = "HmacSHA1";
static final String HMAC_SHA_256 = "HmacSHA256";
+ static final String HMAC_SHA_384 = "HmacSHA384";
+ static final String HMAC_SHA_512 = "HmacSHA512";
+ static final String AES_XCBC = "AesXCbc";
public interface Payload {
byte[] getPacketBytes(IpHeader header) throws Exception;
@@ -328,8 +354,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 +367,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 +416,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 +433,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 +455,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 +493,236 @@
}
}
+ 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) {
+ this(algoName, blockSize, key, ivLen, SALT_LEN_UNUSED);
+ }
+
+ public EspCryptCipher(String algoName, int blockSize, byte[] key, int ivLen, int saltLen) {
+ super(algoName, blockSize, key, ivLen, saltLen);
+ }
+
+ @Override
+ public byte[] getCipherText(int nextHeader, byte[] payload, int spi, int seqNum)
+ throws GeneralSecurityException {
+ final IvParameterSpec ivParameterSpec;
+ final SecretKeySpec secretKeySpec;
+
+ if (AES_CBC.equals(algoName)) {
+ ivParameterSpec = new IvParameterSpec(iv);
+ secretKeySpec = new SecretKeySpec(key, algoName);
+ } else if (AES_CTR.equals(algoName)) {
+ // Provided key consists of encryption/decryption key plus 4-byte salt. Salt is used
+ // with ESP payload IV and initial block counter value to build IvParameterSpec.
+ final byte[] secretKey = Arrays.copyOfRange(key, 0, key.length - saltLen);
+ final byte[] salt = Arrays.copyOfRange(key, secretKey.length, key.length);
+ secretKeySpec = new SecretKeySpec(secretKey, algoName);
+
+ final ByteBuffer ivParameterBuffer =
+ ByteBuffer.allocate(iv.length + saltLen + AES_CTR_INITIAL_COUNTER.length);
+ ivParameterBuffer.put(salt);
+ ivParameterBuffer.put(iv);
+ ivParameterBuffer.put(AES_CTR_INITIAL_COUNTER);
+ ivParameterSpec = new IvParameterSpec(ivParameterBuffer.array());
+ } else {
+ throw new IllegalArgumentException("Invalid algorithm " + 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;
+
+ private static final Set<String> SUPPORTED_HMAC_ALGOS = new HashSet<>();
+
+ static {
+ SUPPORTED_HMAC_ALGOS.add(HMAC_MD5);
+ SUPPORTED_HMAC_ALGOS.add(HMAC_SHA1);
+ SUPPORTED_HMAC_ALGOS.add(HMAC_SHA_256);
+ SUPPORTED_HMAC_ALGOS.add(HMAC_SHA_384);
+ SUPPORTED_HMAC_ALGOS.add(HMAC_SHA_512);
+ }
+
+ public EspAuth(String algoName, byte[] key, int icvLen) {
+ this.algoName = algoName;
+ this.key = key;
+ this.icvLen = icvLen;
+ }
+
+ public byte[] getIcv(byte[] authenticatedSection) throws GeneralSecurityException {
+ if (AES_XCBC.equals(algoName)) {
+ final Cipher aesCipher = Cipher.getInstance(AES_CBC);
+ return new AesXCbcImpl().mac(key, authenticatedSection, true /* needTruncation */);
+ } else if (SUPPORTED_HMAC_ALGOS.contains(algoName)) {
+ final Mac mac = Mac.getInstance(algoName);
+ final SecretKeySpec authKey = new SecretKeySpec(key, algoName);
+ mac.init(authKey);
+
+ final ByteBuffer buffer = ByteBuffer.wrap(mac.doFinal(authenticatedSection));
+ final byte[] icv = new byte[icvLen];
+ buffer.get(icv);
+ return icv;
+ } else {
+ throw new IllegalArgumentException("Invalid algorithm: " + algoName);
+ }
+ }
+ }
+
+ 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 73a6ca5..b0d849a 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;
@@ -481,6 +484,7 @@
@Mock VpnProfileStore mVpnProfileStore;
@Mock SystemConfigManager mSystemConfigManager;
@Mock Resources mResources;
+ @Mock ProxyTracker mProxyTracker;
private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
ArgumentCaptor.forClass(ResolverParamsParcel.class);
@@ -1275,10 +1279,14 @@
return mMockNetworkAgent;
}
- public void establish(LinkProperties lp, int uid, Set<UidRange> ranges, boolean validated,
- boolean hasInternet, boolean isStrictMode) throws Exception {
+ private void setOwnerAndAdminUid(int uid) throws Exception {
mNetworkCapabilities.setOwnerUid(uid);
mNetworkCapabilities.setAdministratorUids(new int[]{uid});
+ }
+
+ public void establish(LinkProperties lp, int uid, Set<UidRange> ranges, boolean validated,
+ boolean hasInternet, boolean isStrictMode) throws Exception {
+ setOwnerAndAdminUid(uid);
registerAgent(false, ranges, lp);
connect(validated, hasInternet, isStrictMode);
waitForIdle();
@@ -1633,7 +1641,7 @@
doReturn(mNetIdManager).when(deps).makeNetIdManager();
doReturn(mNetworkStack).when(deps).getNetworkStack();
doReturn(mSystemProperties).when(deps).getSystemProperties();
- doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any());
+ doReturn(mProxyTracker).when(deps).makeProxyTracker(any(), any());
doReturn(true).when(deps).queryUserAccess(anyInt(), any(), any());
doAnswer(inv -> {
mPolicyTracker = new WrappedMultinetworkPolicyTracker(
@@ -10259,16 +10267,23 @@
@Test
public void testVpnUidRangesUpdate() throws Exception {
- LinkProperties lp = new LinkProperties();
+ // Set up a WiFi network without proxy.
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+ assertNull(mService.getProxyForNetwork(null));
+ assertNull(mCm.getDefaultProxy());
+
+ final LinkProperties lp = new LinkProperties();
lp.setInterfaceName("tun0");
lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
final UidRange vpnRange = PRIMARY_UIDRANGE;
- Set<UidRange> vpnRanges = Collections.singleton(vpnRange);
+ final Set<UidRange> vpnRanges = Collections.singleton(vpnRange);
mMockVpn.establish(lp, VPN_UID, vpnRanges);
assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID);
+ // VPN is connected but proxy is not set, so there is no need to send proxy broadcast.
+ verify(mProxyTracker, never()).sendProxyBroadcast();
- reset(mMockNetd);
// Update to new range which is old range minus APP1, i.e. only APP2
final Set<UidRange> newRanges = new HashSet<>(Arrays.asList(
new UidRange(vpnRange.start, APP1_UID - 1),
@@ -10278,6 +10293,101 @@
assertVpnUidRangesUpdated(true, newRanges, VPN_UID);
assertVpnUidRangesUpdated(false, vpnRanges, VPN_UID);
+
+ // Uid has changed but proxy is not set, so there is no need to send proxy broadcast.
+ verify(mProxyTracker, never()).sendProxyBroadcast();
+
+ final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
+ lp.setHttpProxy(testProxyInfo);
+ mMockVpn.sendLinkProperties(lp);
+ waitForIdle();
+ // Proxy is set, so send a proxy broadcast.
+ verify(mProxyTracker, times(1)).sendProxyBroadcast();
+ reset(mProxyTracker);
+
+ mMockVpn.setUids(vpnRanges);
+ waitForIdle();
+ // Uid has changed and proxy is already set, so send a proxy broadcast.
+ verify(mProxyTracker, times(1)).sendProxyBroadcast();
+ reset(mProxyTracker);
+
+ // Proxy is removed, send a proxy broadcast.
+ lp.setHttpProxy(null);
+ mMockVpn.sendLinkProperties(lp);
+ waitForIdle();
+ verify(mProxyTracker, times(1)).sendProxyBroadcast();
+ reset(mProxyTracker);
+
+ // Proxy is added in WiFi(default network), setDefaultProxy will be called.
+ final LinkProperties wifiLp = mCm.getLinkProperties(mWiFiNetworkAgent.getNetwork());
+ assertNotNull(wifiLp);
+ wifiLp.setHttpProxy(testProxyInfo);
+ mWiFiNetworkAgent.sendLinkProperties(wifiLp);
+ waitForIdle();
+ verify(mProxyTracker, times(1)).setDefaultProxy(eq(testProxyInfo));
+ reset(mProxyTracker);
+ }
+
+ @Test
+ public void testProxyBroadcastWillBeSentWhenVpnHasProxyAndConnects() throws Exception {
+ // Set up a WiFi network without proxy.
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+ assertNull(mService.getProxyForNetwork(null));
+ assertNull(mCm.getDefaultProxy());
+
+ final LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("tun0");
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
+ final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
+ lp.setHttpProxy(testProxyInfo);
+ final UidRange vpnRange = PRIMARY_UIDRANGE;
+ final Set<UidRange> vpnRanges = Collections.singleton(vpnRange);
+ mMockVpn.setOwnerAndAdminUid(VPN_UID);
+ mMockVpn.registerAgent(false, vpnRanges, lp);
+ // In any case, the proxy broadcast won't be sent before VPN goes into CONNECTED state.
+ // Otherwise, the app that calls ConnectivityManager#getDefaultProxy() when it receives the
+ // proxy broadcast will get null.
+ verify(mProxyTracker, never()).sendProxyBroadcast();
+ mMockVpn.connect(true /* validated */, true /* hasInternet */, false /* isStrictMode */);
+ waitForIdle();
+ assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID);
+ // Vpn is connected with proxy, so the proxy broadcast will be sent to inform the apps to
+ // update their proxy data.
+ verify(mProxyTracker, times(1)).sendProxyBroadcast();
+ }
+
+ @Test
+ public void testProxyBroadcastWillBeSentWhenTheProxyOfNonDefaultNetworkHasChanged()
+ throws Exception {
+ // Set up a CELLULAR network without proxy.
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ assertNull(mService.getProxyForNetwork(null));
+ assertNull(mCm.getDefaultProxy());
+ // CELLULAR network should be the default network.
+ assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // Set up a WiFi network without proxy.
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+ assertNull(mService.getProxyForNetwork(null));
+ assertNull(mCm.getDefaultProxy());
+ // WiFi network should be the default network.
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ // CELLULAR network is not the default network.
+ assertNotEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // CELLULAR network is not the system default network, but it might be a per-app default
+ // network. The proxy broadcast should be sent once its proxy has changed.
+ final LinkProperties cellularLp = new LinkProperties();
+ cellularLp.setInterfaceName(MOBILE_IFNAME);
+ final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
+ cellularLp.setHttpProxy(testProxyInfo);
+ mCellNetworkAgent.sendLinkProperties(cellularLp);
+ waitForIdle();
+ verify(mProxyTracker, times(1)).sendProxyBroadcast();
}
@Test
@@ -10621,8 +10731,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());
@@ -10650,8 +10761,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());
@@ -10676,8 +10788,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());
@@ -10699,8 +10812,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());
@@ -10965,16 +11079,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);
}
@@ -10989,6 +11111,9 @@
if (null != mProfileDefaultNetworkCallback) {
mCm.unregisterNetworkCallback(mProfileDefaultNetworkCallback);
}
+ if (null != mTestPackageDefaultNetworkCallback) {
+ mCm.unregisterNetworkCallback(mTestPackageDefaultNetworkCallback);
+ }
}
private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(
@@ -12880,6 +13005,8 @@
assertEquals(1, nris.size());
assertTrue(nri.isMultilayerRequest());
assertEquals(nri.getUids(), uidRangesForUids(uids));
+ assertEquals(DEFAULT_NETWORK_PRIORITY_MOBILE_DATA_PREFERRED,
+ nri.getDefaultNetworkPriority());
}
/**
@@ -12912,9 +13039,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(
@@ -12925,12 +13054,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));
@@ -12938,5 +13070,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();
+ });
}
}