Merge "Ensure SpiUnavailableException was thrown because of duplicate SPI"
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 64365cc..225bd58 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -65,7 +65,7 @@
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.NetworkStackConstants;
-import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.U32;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
import com.android.net.module.util.netlink.ConntrackMessage;
@@ -1177,22 +1177,13 @@
}
}
- /**
- * Simple struct that only contains a u32. Must be public because Struct needs access to it.
- * TODO: make this a public inner class of Struct so anyone can use it as, e.g., Struct.U32?
- */
- public static class U32Struct extends Struct {
- @Struct.Field(order = 0, type = Struct.Type.U32)
- public long val;
- }
-
private void dumpCounters(@NonNull IndentingPrintWriter pw) {
if (!mDeps.isAtLeastS()) {
pw.println("No counter support");
return;
}
- try (BpfMap<U32Struct, U32Struct> map = new BpfMap<>(TETHER_ERROR_MAP_PATH,
- BpfMap.BPF_F_RDONLY, U32Struct.class, U32Struct.class)) {
+ try (BpfMap<U32, U32> map = new BpfMap<>(TETHER_ERROR_MAP_PATH, BpfMap.BPF_F_RDONLY,
+ U32.class, U32.class)) {
map.forEach((k, v) -> {
String counterName;
@@ -1496,7 +1487,8 @@
}
@NonNull
- private byte[] toIpv4MappedAddressBytes(Inet4Address ia4) {
+ @VisibleForTesting
+ static byte[] toIpv4MappedAddressBytes(Inet4Address ia4) {
final byte[] addr4 = ia4.getAddress();
final byte[] addr6 = new byte[16];
addr6[10] = (byte) 0xff;
diff --git a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
index 4ca36df..844efde 100644
--- a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
+++ b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
@@ -16,6 +16,7 @@
package com.android.networkstack.tethering;
+import static android.content.pm.PackageManager.GET_ACTIVITIES;
import static android.net.TetheringConstants.EXTRA_ADD_TETHER_TYPE;
import static android.net.TetheringConstants.EXTRA_PROVISION_CALLBACK;
import static android.net.TetheringConstants.EXTRA_RUN_PROVISION;
@@ -32,6 +33,9 @@
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
+import static com.android.networkstack.apishim.ConstantsShim.ACTION_TETHER_UNSUPPORTED_CARRIER_UI;
+import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL;
+
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -39,6 +43,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.net.util.SharedLog;
import android.os.Bundle;
import android.os.Handler;
@@ -52,6 +57,7 @@
import android.util.SparseIntArray;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
import java.io.PrintWriter;
import java.util.BitSet;
@@ -74,6 +80,13 @@
protected static final String ACTION_PROVISIONING_ALARM =
"com.android.networkstack.tethering.PROVISIONING_RECHECK_ALARM";
+ // Indicate tether provisioning is not required by carrier.
+ private static final int TETHERING_PROVISIONING_REQUIRED = 1000;
+ // Indicate tether provisioning is required by carrier.
+ private static final int TETHERING_PROVISIONING_NOT_REQUIRED = 1001;
+ // Indicate tethering is not supported by carrier.
+ private static final int TETHERING_PROVISIONING_CARRIER_UNSUPPORT = 1002;
+
private final ComponentName mSilentProvisioningService;
private static final int MS_PER_HOUR = 60 * 60 * 1000;
private static final int DUMP_TIMEOUT = 10_000;
@@ -96,7 +109,7 @@
private boolean mLastCellularUpstreamPermitted = true;
private boolean mUsingCellularAsUpstream = false;
private boolean mNeedReRunProvisioningUi = false;
- private OnUiEntitlementFailedListener mListener;
+ private OnTetherProvisioningFailedListener mListener;
private TetheringConfigurationFetcher mFetcher;
public EntitlementManager(Context ctx, Handler h, SharedLog log,
@@ -115,18 +128,20 @@
mContext.getResources().getString(R.string.config_wifi_tether_enable));
}
- public void setOnUiEntitlementFailedListener(final OnUiEntitlementFailedListener listener) {
+ public void setOnTetherProvisioningFailedListener(
+ final OnTetherProvisioningFailedListener listener) {
mListener = listener;
}
/** Callback fired when UI entitlement failed. */
- public interface OnUiEntitlementFailedListener {
+ public interface OnTetherProvisioningFailedListener {
/**
* Ui entitlement check fails in |downstream|.
*
* @param downstream tethering type from TetheringManager.TETHERING_{@code *}.
+ * @param reason Failed reason.
*/
- void onUiEntitlementFailed(int downstream);
+ void onTetherProvisioningFailed(int downstream, String reason);
}
public void setTetheringConfigurationFetcher(final TetheringConfigurationFetcher fetcher) {
@@ -153,6 +168,9 @@
}
private boolean isCellularUpstreamPermitted(final TetheringConfiguration config) {
+ // If #getTetherProvisioningCondition return TETHERING_PROVISIONING_CARRIER_UNSUPPORT,
+ // that means cellular upstream is not supported and entitlement check result is empty
+ // because entitlement check should not be run.
if (!isTetherProvisioningRequired(config)) return true;
// If provisioning is required and EntitlementManager doesn't know any downstreams, cellular
@@ -199,11 +217,7 @@
// If upstream is not cellular, provisioning app would not be launched
// till upstream change to cellular.
if (mUsingCellularAsUpstream) {
- if (showProvisioningUi) {
- runUiTetherProvisioning(downstreamType, config);
- } else {
- runSilentTetherProvisioning(downstreamType, config);
- }
+ runTetheringProvisioning(showProvisioningUi, downstreamType, config);
mNeedReRunProvisioningUi = false;
} else {
mNeedReRunProvisioningUi |= showProvisioningUi;
@@ -262,18 +276,51 @@
// the change and get the new correct value.
for (int downstream = mCurrentDownstreams.nextSetBit(0); downstream >= 0;
downstream = mCurrentDownstreams.nextSetBit(downstream + 1)) {
+ // If tethering provisioning is required but entitlement check result is empty,
+ // this means tethering may need to run entitlement check or carrier network
+ // is not supported.
if (mCurrentEntitlementResults.indexOfKey(downstream) < 0) {
- if (mNeedReRunProvisioningUi) {
- mNeedReRunProvisioningUi = false;
- runUiTetherProvisioning(downstream, config);
- } else {
- runSilentTetherProvisioning(downstream, config);
- }
+ runTetheringProvisioning(mNeedReRunProvisioningUi, downstream, config);
+ mNeedReRunProvisioningUi = false;
}
}
}
/**
+ * Tether provisioning has these conditions to control provisioning behavior.
+ * 1st priority : Uses system property to disable any provisioning behavior.
+ * 2nd priority : Uses {@code CarrierConfigManager#KEY_CARRIER_SUPPORTS_TETHERING_BOOL} to
+ * decide current carrier support cellular upstream tethering or not.
+ * If value is true, it means check follow up condition to know whether
+ * provisioning is required.
+ * If value is false, it means tethering could not use cellular as upstream.
+ * 3rd priority : Uses {@code CarrierConfigManager#KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL} to
+ * decide current carrier require the provisioning.
+ * 4th priority : Checks whether provisioning is required from RRO configuration.
+ *
+ * @param config
+ * @return integer {@see #TETHERING_PROVISIONING_NOT_REQUIRED,
+ * #TETHERING_PROVISIONING_REQUIRED,
+ * #TETHERING_PROVISIONING_CARRIER_UNSUPPORT}
+ */
+ private int getTetherProvisioningCondition(final TetheringConfiguration config) {
+ if (SystemProperties.getBoolean(DISABLE_PROVISIONING_SYSPROP_KEY, false)) {
+ return TETHERING_PROVISIONING_NOT_REQUIRED;
+ }
+ // TODO: Find a way to avoid get carrier config twice.
+ if (carrierConfigAffirmsCarrierNotSupport(config)) {
+ // To block tethering, behave as if running provisioning check and failed.
+ return TETHERING_PROVISIONING_CARRIER_UNSUPPORT;
+ }
+
+ if (carrierConfigAffirmsEntitlementCheckNotRequired(config)) {
+ return TETHERING_PROVISIONING_NOT_REQUIRED;
+ }
+ return (config.provisioningApp.length == 2)
+ ? TETHERING_PROVISIONING_REQUIRED : TETHERING_PROVISIONING_NOT_REQUIRED;
+ }
+
+ /**
* Check if the device requires a provisioning check in order to enable tethering.
*
* @param config an object that encapsulates the various tethering configuration elements.
@@ -281,14 +328,26 @@
*/
@VisibleForTesting
protected boolean isTetherProvisioningRequired(final TetheringConfiguration config) {
- if (SystemProperties.getBoolean(DISABLE_PROVISIONING_SYSPROP_KEY, false)
- || config.provisioningApp.length == 0) {
+ return getTetherProvisioningCondition(config) != TETHERING_PROVISIONING_NOT_REQUIRED;
+ }
+
+ /**
+ * Confirms the need of tethering provisioning but no entitlement package exists.
+ */
+ public boolean isProvisioningNeededButUnavailable() {
+ final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
+ return getTetherProvisioningCondition(config) == TETHERING_PROVISIONING_REQUIRED
+ && !doesEntitlementPackageExist(config);
+ }
+
+ private boolean doesEntitlementPackageExist(final TetheringConfiguration config) {
+ final PackageManager pm = mContext.getPackageManager();
+ try {
+ pm.getPackageInfo(config.provisioningApp[0], GET_ACTIVITIES);
+ } catch (PackageManager.NameNotFoundException e) {
return false;
}
- if (carrierConfigAffirmsEntitlementCheckNotRequired(config)) {
- return false;
- }
- return (config.provisioningApp.length == 2);
+ return true;
}
/**
@@ -310,9 +369,7 @@
mEntitlementCacheValue.clear();
mCurrentEntitlementResults.clear();
- // TODO: refine provisioning check to isTetherProvisioningRequired() ??
- if (!config.hasMobileHotspotProvisionApp()
- || carrierConfigAffirmsEntitlementCheckNotRequired(config)) {
+ if (!isTetherProvisioningRequired(config)) {
evaluateCellularPermission(config);
return;
}
@@ -327,8 +384,8 @@
* @param config an object that encapsulates the various tethering configuration elements.
* */
public PersistableBundle getCarrierConfig(final TetheringConfiguration config) {
- final CarrierConfigManager configManager = (CarrierConfigManager) mContext
- .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ final CarrierConfigManager configManager = mContext
+ .getSystemService(CarrierConfigManager.class);
if (configManager == null) return null;
final PersistableBundle carrierConfig = configManager.getConfigForSubId(
@@ -346,6 +403,7 @@
//
// TODO: find a better way to express this, or alter the checking process
// entirely so that this is more intuitive.
+ // TODO: Find a way to avoid using getCarrierConfig everytime.
private boolean carrierConfigAffirmsEntitlementCheckNotRequired(
final TetheringConfiguration config) {
// Check carrier config for entitlement checks
@@ -358,16 +416,29 @@
return !isEntitlementCheckRequired;
}
+ private boolean carrierConfigAffirmsCarrierNotSupport(final TetheringConfiguration config) {
+ if (!SdkLevel.isAtLeastT()) {
+ return false;
+ }
+ // Check carrier config for entitlement checks
+ final PersistableBundle carrierConfig = getCarrierConfig(config);
+ if (carrierConfig == null) return false;
+
+ // A CarrierConfigManager was found and it has a config.
+ final boolean mIsCarrierSupport = carrierConfig.getBoolean(
+ KEY_CARRIER_SUPPORTS_TETHERING_BOOL, true);
+ return !mIsCarrierSupport;
+ }
+
/**
* Run no UI tethering provisioning check.
* @param type tethering type from TetheringManager.TETHERING_{@code *}
* @param subId default data subscription ID.
*/
@VisibleForTesting
- protected Intent runSilentTetherProvisioning(int type, final TetheringConfiguration config) {
+ protected Intent runSilentTetherProvisioning(
+ int type, final TetheringConfiguration config, ResultReceiver receiver) {
if (DBG) mLog.i("runSilentTetherProvisioning: " + type);
- // For silent provisioning, settings would stop tethering when entitlement fail.
- ResultReceiver receiver = buildProxyReceiver(type, false/* notifyFail */, null);
Intent intent = new Intent();
intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);
@@ -383,11 +454,6 @@
return intent;
}
- private void runUiTetherProvisioning(int type, final TetheringConfiguration config) {
- ResultReceiver receiver = buildProxyReceiver(type, true/* notifyFail */, null);
- runUiTetherProvisioning(type, config, receiver);
- }
-
/**
* Run the UI-enabled tethering provisioning check.
* @param type tethering type from TetheringManager.TETHERING_{@code *}
@@ -411,6 +477,35 @@
return intent;
}
+ private void runTetheringProvisioning(
+ boolean showProvisioningUi, int downstreamType, final TetheringConfiguration config) {
+ if (carrierConfigAffirmsCarrierNotSupport(config)) {
+ mListener.onTetherProvisioningFailed(downstreamType, "Carrier does not support.");
+ if (showProvisioningUi) {
+ showCarrierUnsupportedDialog();
+ }
+ return;
+ }
+
+ ResultReceiver receiver =
+ buildProxyReceiver(downstreamType, showProvisioningUi/* notifyFail */, null);
+ if (showProvisioningUi) {
+ runUiTetherProvisioning(downstreamType, config, receiver);
+ } else {
+ runSilentTetherProvisioning(downstreamType, config, receiver);
+ }
+ }
+
+ private void showCarrierUnsupportedDialog() {
+ // This is only used when carrierConfigAffirmsCarrierNotSupport() is true.
+ if (!SdkLevel.isAtLeastT()) {
+ return;
+ }
+ Intent intent = new Intent(ACTION_TETHER_UNSUPPORTED_CARRIER_UI);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ }
+
@VisibleForTesting
PendingIntent createRecheckAlarmIntent() {
final Intent intent = new Intent(ACTION_PROVISIONING_ALARM);
@@ -576,7 +671,8 @@
int updatedCacheValue = updateEntitlementCacheValue(type, resultCode);
addDownstreamMapping(type, updatedCacheValue);
if (updatedCacheValue == TETHER_ERROR_PROVISIONING_FAILED && notifyFail) {
- mListener.onUiEntitlementFailed(type);
+ mListener.onTetherProvisioningFailed(
+ type, "Tethering provisioning failed.");
}
if (receiver != null) receiver.send(updatedCacheValue, null);
}
@@ -632,9 +728,14 @@
}
final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
- if (!isTetherProvisioningRequired(config)) {
- receiver.send(TETHER_ERROR_NO_ERROR, null);
- return;
+
+ switch (getTetherProvisioningCondition(config)) {
+ case TETHERING_PROVISIONING_NOT_REQUIRED:
+ receiver.send(TETHER_ERROR_NO_ERROR, null);
+ return;
+ case TETHERING_PROVISIONING_CARRIER_UNSUPPORT:
+ receiver.send(TETHER_ERROR_PROVISIONING_FAILED, null);
+ return;
}
final int cacheValue = mEntitlementCacheValue.get(
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index bb9b6fb..0b607bd 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -18,7 +18,6 @@
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.NETWORK_STACK;
-import static android.content.pm.PackageManager.GET_ACTIVITIES;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.hardware.usb.UsbManager.USB_CONFIGURED;
import static android.hardware.usb.UsbManager.USB_CONNECTED;
@@ -319,8 +318,8 @@
mEntitlementMgr = mDeps.getEntitlementManager(mContext, mHandler, mLog,
() -> mTetherMainSM.sendMessage(
TetherMainSM.EVENT_UPSTREAM_PERMISSION_CHANGED));
- mEntitlementMgr.setOnUiEntitlementFailedListener((int downstream) -> {
- mLog.log("OBSERVED UiEnitlementFailed");
+ mEntitlementMgr.setOnTetherProvisioningFailedListener((downstream, reason) -> {
+ mLog.log("OBSERVED OnTetherProvisioningFailed : " + reason);
stopTethering(downstream);
});
mEntitlementMgr.setTetheringConfigurationFetcher(() -> {
@@ -995,30 +994,11 @@
return tetherState.lastError;
}
- private boolean isProvisioningNeededButUnavailable() {
- return isTetherProvisioningRequired() && !doesEntitlementPackageExist();
- }
-
boolean isTetherProvisioningRequired() {
final TetheringConfiguration cfg = mConfig;
return mEntitlementMgr.isTetherProvisioningRequired(cfg);
}
- private boolean doesEntitlementPackageExist() {
- // provisioningApp must contain package and class name.
- if (mConfig.provisioningApp.length != 2) {
- return false;
- }
-
- final PackageManager pm = mContext.getPackageManager();
- try {
- pm.getPackageInfo(mConfig.provisioningApp[0], GET_ACTIVITIES);
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- return true;
- }
-
private int getRequestedState(int type) {
final TetheringRequestParcel request = mActiveTetheringRequests.get(type);
@@ -2476,7 +2456,7 @@
&& !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
return tetherEnabledInSettings && hasAnySupportedDownstream()
- && !isProvisioningNeededButUnavailable();
+ && !mEntitlementMgr.isProvisioningNeededButUnavailable();
}
private void dumpBpf(IndentingPrintWriter pw) {
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index c614046..179fc8a 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -47,6 +47,7 @@
import static com.android.networkstack.tethering.BpfCoordinator.StatsType;
import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_IFACE;
import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_UID;
+import static com.android.networkstack.tethering.BpfCoordinator.toIpv4MappedAddressBytes;
import static com.android.networkstack.tethering.BpfUtils.DOWNSTREAM;
import static com.android.networkstack.tethering.BpfUtils.UPSTREAM;
import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
@@ -129,6 +130,7 @@
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.LinkedHashMap;
@RunWith(AndroidJUnit4.class)
@@ -138,22 +140,225 @@
public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
private static final int TEST_NET_ID = 24;
+ private static final int TEST_NET_ID2 = 25;
+ private static final int INVALID_IFINDEX = 0;
private static final int UPSTREAM_IFINDEX = 1001;
- private static final int DOWNSTREAM_IFINDEX = 1002;
+ private static final int UPSTREAM_IFINDEX2 = 1002;
+ private static final int DOWNSTREAM_IFINDEX = 1003;
+ private static final int DOWNSTREAM_IFINDEX2 = 1004;
private static final String UPSTREAM_IFACE = "rmnet0";
+ private static final String UPSTREAM_IFACE2 = "wlan0";
private static final MacAddress DOWNSTREAM_MAC = MacAddress.fromString("12:34:56:78:90:ab");
+ private static final MacAddress DOWNSTREAM_MAC2 = MacAddress.fromString("ab:90:78:56:34:12");
+
private static final MacAddress MAC_A = MacAddress.fromString("00:00:00:00:00:0a");
private static final MacAddress MAC_B = MacAddress.fromString("11:22:33:00:00:0b");
private static final InetAddress NEIGH_A = InetAddresses.parseNumericAddress("2001:db8::1");
private static final InetAddress NEIGH_B = InetAddresses.parseNumericAddress("2001:db8::2");
+ private static final Inet4Address REMOTE_ADDR =
+ (Inet4Address) InetAddresses.parseNumericAddress("140.112.8.116");
+ private static final Inet4Address PUBLIC_ADDR =
+ (Inet4Address) InetAddresses.parseNumericAddress("1.0.0.1");
+ private static final Inet4Address PUBLIC_ADDR2 =
+ (Inet4Address) InetAddresses.parseNumericAddress("1.0.0.2");
+ private static final Inet4Address PRIVATE_ADDR =
+ (Inet4Address) InetAddresses.parseNumericAddress("192.168.80.12");
+ private static final Inet4Address PRIVATE_ADDR2 =
+ (Inet4Address) InetAddresses.parseNumericAddress("192.168.90.12");
+
+ // Generally, public port and private port are the same in the NAT conntrack message.
+ // TODO: consider using different private port and public port for testing.
+ private static final short REMOTE_PORT = (short) 443;
+ private static final short PUBLIC_PORT = (short) 62449;
+ private static final short PUBLIC_PORT2 = (short) 62450;
+ private static final short PRIVATE_PORT = (short) 62449;
+ private static final short PRIVATE_PORT2 = (short) 62450;
+
private static final InterfaceParams UPSTREAM_IFACE_PARAMS = new InterfaceParams(
UPSTREAM_IFACE, UPSTREAM_IFINDEX, null /* macAddr, rawip */,
NetworkStackConstants.ETHER_MTU);
+ private static final InterfaceParams UPSTREAM_IFACE_PARAMS2 = new InterfaceParams(
+ UPSTREAM_IFACE2, UPSTREAM_IFINDEX2, MacAddress.fromString("44:55:66:00:00:0c"),
+ NetworkStackConstants.ETHER_MTU);
+
+ private static final HashMap<Integer, UpstreamInformation> UPSTREAM_INFORMATIONS =
+ new HashMap<Integer, UpstreamInformation>() {{
+ put(UPSTREAM_IFINDEX, new UpstreamInformation(UPSTREAM_IFACE_PARAMS,
+ PUBLIC_ADDR, NetworkCapabilities.TRANSPORT_CELLULAR, TEST_NET_ID));
+ put(UPSTREAM_IFINDEX2, new UpstreamInformation(UPSTREAM_IFACE_PARAMS2,
+ PUBLIC_ADDR2, NetworkCapabilities.TRANSPORT_WIFI, TEST_NET_ID2));
+ }};
+
+ private static final ClientInfo CLIENT_INFO_A = new ClientInfo(DOWNSTREAM_IFINDEX,
+ DOWNSTREAM_MAC, PRIVATE_ADDR, MAC_A);
+ private static final ClientInfo CLIENT_INFO_B = new ClientInfo(DOWNSTREAM_IFINDEX2,
+ DOWNSTREAM_MAC2, PRIVATE_ADDR2, MAC_B);
+
+ private static class UpstreamInformation {
+ public final InterfaceParams interfaceParams;
+ public final Inet4Address address;
+ public final int transportType;
+ public final int netId;
+
+ UpstreamInformation(final InterfaceParams interfaceParams,
+ final Inet4Address address, int transportType, int netId) {
+ this.interfaceParams = interfaceParams;
+ this.address = address;
+ this.transportType = transportType;
+ this.netId = netId;
+ }
+ }
+
+ private static class TestUpstream4Key {
+ public static class Builder {
+ private long mIif = DOWNSTREAM_IFINDEX;
+ private MacAddress mDstMac = DOWNSTREAM_MAC;
+ private short mL4proto = (short) IPPROTO_TCP;
+ private byte[] mSrc4 = PRIVATE_ADDR.getAddress();
+ private byte[] mDst4 = REMOTE_ADDR.getAddress();
+ private int mSrcPort = PRIVATE_PORT;
+ private int mDstPort = REMOTE_PORT;
+
+ Builder() {}
+
+ public Builder setProto(int proto) {
+ if (proto != IPPROTO_TCP && proto != IPPROTO_UDP) {
+ fail("Not support protocol " + proto);
+ }
+ mL4proto = (short) proto;
+ return this;
+ }
+
+ public Tether4Key build() {
+ return new Tether4Key(mIif, mDstMac, mL4proto, mSrc4, mDst4, mSrcPort, mDstPort);
+ }
+ }
+ }
+
+ private static class TestDownstream4Key {
+ public static class Builder {
+ private long mIif = UPSTREAM_IFINDEX;
+ private MacAddress mDstMac = MacAddress.ALL_ZEROS_ADDRESS /* dstMac (rawip) */;
+ private short mL4proto = (short) IPPROTO_TCP;
+ private byte[] mSrc4 = REMOTE_ADDR.getAddress();
+ private byte[] mDst4 = PUBLIC_ADDR.getAddress();
+ private int mSrcPort = REMOTE_PORT;
+ private int mDstPort = PUBLIC_PORT;
+
+ Builder() {}
+
+ public Builder setProto(int proto) {
+ if (proto != IPPROTO_TCP && proto != IPPROTO_UDP) {
+ fail("Not support protocol " + proto);
+ }
+ mL4proto = (short) proto;
+ return this;
+ }
+
+ public Tether4Key build() {
+ return new Tether4Key(mIif, mDstMac, mL4proto, mSrc4, mDst4, mSrcPort, mDstPort);
+ }
+ }
+ }
+
+ private static class TestUpstream4Value {
+ public static class Builder {
+ private long mOif = UPSTREAM_IFINDEX;
+ private MacAddress mEthDstMac = MacAddress.ALL_ZEROS_ADDRESS /* dstMac (rawip) */;
+ private MacAddress mEthSrcMac = MacAddress.ALL_ZEROS_ADDRESS /* dstMac (rawip) */;
+ private int mEthProto = ETH_P_IP;
+ private short mPmtu = NetworkStackConstants.ETHER_MTU;
+ private byte[] mSrc46 = toIpv4MappedAddressBytes(PUBLIC_ADDR);
+ private byte[] mDst46 = toIpv4MappedAddressBytes(REMOTE_ADDR);
+ private int mSrcPort = PUBLIC_PORT;
+ private int mDstPort = REMOTE_PORT;
+ private long mLastUsed = 0;
+
+ Builder() {}
+
+ public Tether4Value build() {
+ return new Tether4Value(mOif, mEthDstMac, mEthSrcMac, mEthProto, mPmtu,
+ mSrc46, mDst46, mSrcPort, mDstPort, mLastUsed);
+ }
+ }
+ }
+
+ private static class TestDownstream4Value {
+ public static class Builder {
+ private long mOif = DOWNSTREAM_IFINDEX;
+ private MacAddress mEthDstMac = MAC_A /* client mac */;
+ private MacAddress mEthSrcMac = DOWNSTREAM_MAC;
+ private int mEthProto = ETH_P_IP;
+ private short mPmtu = NetworkStackConstants.ETHER_MTU;
+ private byte[] mSrc46 = toIpv4MappedAddressBytes(REMOTE_ADDR);
+ private byte[] mDst46 = toIpv4MappedAddressBytes(PRIVATE_ADDR);
+ private int mSrcPort = REMOTE_PORT;
+ private int mDstPort = PRIVATE_PORT;
+ private long mLastUsed = 0;
+
+ Builder() {}
+
+ public Tether4Value build() {
+ return new Tether4Value(mOif, mEthDstMac, mEthSrcMac, mEthProto, mPmtu,
+ mSrc46, mDst46, mSrcPort, mDstPort, mLastUsed);
+ }
+ }
+ }
+
+ private static class TestConntrackEvent {
+ public static class Builder {
+ private short mMsgType = IPCTNL_MSG_CT_NEW;
+ private short mProto = (short) IPPROTO_TCP;
+ private Inet4Address mPrivateAddr = PRIVATE_ADDR;
+ private Inet4Address mPublicAddr = PUBLIC_ADDR;
+ private Inet4Address mRemoteAddr = REMOTE_ADDR;
+ private short mPrivatePort = PRIVATE_PORT;
+ private short mPublicPort = PUBLIC_PORT;
+ private short mRemotePort = REMOTE_PORT;
+
+ Builder() {}
+
+ public Builder setMsgType(short msgType) {
+ if (msgType != IPCTNL_MSG_CT_NEW && msgType != IPCTNL_MSG_CT_DELETE) {
+ fail("Not support message type " + msgType);
+ }
+ mMsgType = (short) msgType;
+ return this;
+ }
+
+ public Builder setProto(int proto) {
+ if (proto != IPPROTO_TCP && proto != IPPROTO_UDP) {
+ fail("Not support protocol " + proto);
+ }
+ mProto = (short) proto;
+ return this;
+ }
+
+ public Builder setRemotePort(int remotePort) {
+ mRemotePort = (short) remotePort;
+ return this;
+ }
+
+ public ConntrackEvent build() {
+ final int status = (mMsgType == IPCTNL_MSG_CT_NEW) ? ESTABLISHED_MASK : DYING_MASK;
+ final int timeoutSec = (mMsgType == IPCTNL_MSG_CT_NEW) ? 100 /* nonzero, new */
+ : 0 /* unused, delete */;
+ return new ConntrackEvent(
+ (short) (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8 | mMsgType),
+ new Tuple(new TupleIpv4(mPrivateAddr, mRemoteAddr),
+ new TupleProto((byte) mProto, mPrivatePort, mRemotePort)),
+ new Tuple(new TupleIpv4(mRemoteAddr, mPublicAddr),
+ new TupleProto((byte) mProto, mRemotePort, mPublicPort)),
+ status,
+ timeoutSec);
+ }
+ }
+ }
@Mock private NetworkStatsManager mStatsManager;
@Mock private INetd mNetd;
@@ -161,8 +366,6 @@
@Mock private IpServer mIpServer2;
@Mock private TetheringConfiguration mTetherConfig;
@Mock private ConntrackMonitor mConntrackMonitor;
- @Mock private BpfMap<Tether4Key, Tether4Value> mBpfDownstream4Map;
- @Mock private BpfMap<Tether4Key, Tether4Value> mBpfUpstream4Map;
@Mock private BpfMap<TetherDownstream6Key, Tether6Value> mBpfDownstream6Map;
@Mock private BpfMap<TetherUpstream6Key, Tether6Value> mBpfUpstream6Map;
@Mock private BpfMap<TetherDevKey, TetherDevValue> mBpfDevMap;
@@ -179,6 +382,10 @@
private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
ArgumentCaptor.forClass(ArrayList.class);
private final TestLooper mTestLooper = new TestLooper();
+ private final BpfMap<Tether4Key, Tether4Value> mBpfDownstream4Map =
+ spy(new TestBpfMap<>(Tether4Key.class, Tether4Value.class));
+ private final BpfMap<Tether4Key, Tether4Value> mBpfUpstream4Map =
+ spy(new TestBpfMap<>(Tether4Key.class, Tether4Value.class));
private final TestBpfMap<TetherStatsKey, TetherStatsValue> mBpfStatsMap =
spy(new TestBpfMap<>(TetherStatsKey.class, TetherStatsValue.class));
private final TestBpfMap<TetherLimitKey, TetherLimitValue> mBpfLimitMap =
@@ -1244,140 +1451,67 @@
// | Sever +---------+ Upstream | Downstream +---------+ Client |
// +------------+ +------------+------------+ +------------+
// remote ip public ip private ip
- // 140.112.8.116:443 100.81.179.1:62449 192.168.80.12:62449
+ // 140.112.8.116:443 1.0.0.1:62449 192.168.80.12:62449
//
- private static final Inet4Address REMOTE_ADDR =
- (Inet4Address) InetAddresses.parseNumericAddress("140.112.8.116");
- private static final Inet4Address PUBLIC_ADDR =
- (Inet4Address) InetAddresses.parseNumericAddress("100.81.179.1");
- private static final Inet4Address PRIVATE_ADDR =
- (Inet4Address) InetAddresses.parseNumericAddress("192.168.80.12");
- // IPv4-mapped IPv6 addresses
- // Remote addrress ::ffff:140.112.8.116
- // Public addrress ::ffff:100.81.179.1
- // Private addrress ::ffff:192.168.80.12
- private static final byte[] REMOTE_ADDR_V4MAPPED_BYTES = new byte[] {
- (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
- (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff,
- (byte) 0x8c, (byte) 0x70, (byte) 0x08, (byte) 0x74 };
- private static final byte[] PUBLIC_ADDR_V4MAPPED_BYTES = new byte[] {
- (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
- (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff,
- (byte) 0x64, (byte) 0x51, (byte) 0xb3, (byte) 0x01 };
- private static final byte[] PRIVATE_ADDR_V4MAPPED_BYTES = new byte[] {
- (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
- (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff,
- (byte) 0xc0, (byte) 0xa8, (byte) 0x50, (byte) 0x0c };
-
- // Generally, public port and private port are the same in the NAT conntrack message.
- // TODO: consider using different private port and public port for testing.
- private static final short REMOTE_PORT = (short) 443;
- private static final short PUBLIC_PORT = (short) 62449;
- private static final short PRIVATE_PORT = (short) 62449;
-
- @NonNull
- private Tether4Key makeUpstream4Key(int proto) {
- if (proto != IPPROTO_TCP && proto != IPPROTO_UDP) {
- fail("Not support protocol " + proto);
- }
- return new Tether4Key(DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC, (short) proto,
- PRIVATE_ADDR.getAddress(), REMOTE_ADDR.getAddress(), PRIVATE_PORT, REMOTE_PORT);
- }
-
- @NonNull
- private Tether4Key makeDownstream4Key(int proto) {
- if (proto != IPPROTO_TCP && proto != IPPROTO_UDP) {
- fail("Not support protocol " + proto);
- }
- return new Tether4Key(UPSTREAM_IFINDEX,
- MacAddress.ALL_ZEROS_ADDRESS /* dstMac (rawip) */, (short) proto,
- REMOTE_ADDR.getAddress(), PUBLIC_ADDR.getAddress(), REMOTE_PORT, PUBLIC_PORT);
- }
-
- @NonNull
- private Tether4Value makeUpstream4Value() {
- return new Tether4Value(UPSTREAM_IFINDEX,
- MacAddress.ALL_ZEROS_ADDRESS /* ethDstMac (rawip) */,
- MacAddress.ALL_ZEROS_ADDRESS /* ethSrcMac (rawip) */, ETH_P_IP,
- NetworkStackConstants.ETHER_MTU, PUBLIC_ADDR_V4MAPPED_BYTES,
- REMOTE_ADDR_V4MAPPED_BYTES, PUBLIC_PORT, REMOTE_PORT, 0 /* lastUsed */);
- }
-
- @NonNull
- private Tether4Value makeDownstream4Value() {
- return new Tether4Value(DOWNSTREAM_IFINDEX, MAC_A /* client mac */, DOWNSTREAM_MAC,
- ETH_P_IP, NetworkStackConstants.ETHER_MTU, REMOTE_ADDR_V4MAPPED_BYTES,
- PRIVATE_ADDR_V4MAPPED_BYTES, REMOTE_PORT, PRIVATE_PORT, 0 /* lastUsed */);
- }
-
- @NonNull
- private Tether4Key makeDownstream4Key() {
- return makeDownstream4Key(IPPROTO_TCP);
- }
-
- @NonNull
- private ConntrackEvent makeTestConntrackEvent(short msgType, int proto, short remotePort) {
- if (msgType != IPCTNL_MSG_CT_NEW && msgType != IPCTNL_MSG_CT_DELETE) {
- fail("Not support message type " + msgType);
- }
- if (proto != IPPROTO_TCP && proto != IPPROTO_UDP) {
- fail("Not support protocol " + proto);
+ // Setup upstream interface to BpfCoordinator.
+ //
+ // @param coordinator BpfCoordinator instance.
+ // @param upstreamIfindex upstream interface index. can be the following values.
+ // INVALID_IFINDEX: no upstream interface
+ // UPSTREAM_IFINDEX: CELLULAR (raw ip interface)
+ // UPSTREAM_IFINDEX2: WIFI (ethernet interface)
+ private void setUpstreamInformationTo(final BpfCoordinator coordinator,
+ @Nullable Integer upstreamIfindex) {
+ if (upstreamIfindex == INVALID_IFINDEX) {
+ coordinator.updateUpstreamNetworkState(null);
+ return;
}
- final int status = (msgType == IPCTNL_MSG_CT_NEW) ? ESTABLISHED_MASK : DYING_MASK;
- final int timeoutSec = (msgType == IPCTNL_MSG_CT_NEW) ? 100 /* nonzero, new */
- : 0 /* unused, delete */;
- return new ConntrackEvent(
- (short) (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8 | msgType),
- new Tuple(new TupleIpv4(PRIVATE_ADDR, REMOTE_ADDR),
- new TupleProto((byte) proto, PRIVATE_PORT, remotePort)),
- new Tuple(new TupleIpv4(REMOTE_ADDR, PUBLIC_ADDR),
- new TupleProto((byte) proto, remotePort, PUBLIC_PORT)),
- status,
- timeoutSec);
- }
-
- @NonNull
- private ConntrackEvent makeTestConntrackEvent(short msgType, int proto) {
- return makeTestConntrackEvent(msgType, proto, REMOTE_PORT);
- }
-
- private void setUpstreamInformationTo(final BpfCoordinator coordinator) {
- final LinkProperties lp = new LinkProperties();
- lp.setInterfaceName(UPSTREAM_IFACE);
- lp.addLinkAddress(new LinkAddress(PUBLIC_ADDR, 32 /* prefix length */));
- final NetworkCapabilities capabilities = new NetworkCapabilities()
- .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
- coordinator.updateUpstreamNetworkState(new UpstreamNetworkState(lp, capabilities,
- new Network(TEST_NET_ID)));
- }
-
- private void setDownstreamAndClientInformationTo(final BpfCoordinator coordinator) {
- final ClientInfo clientInfo = new ClientInfo(DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC,
- PRIVATE_ADDR, MAC_A /* client mac */);
- coordinator.tetherOffloadClientAdd(mIpServer, clientInfo);
- }
-
- private void initBpfCoordinatorForRule4(final BpfCoordinator coordinator) throws Exception {
- // Needed because addUpstreamIfindexToMap only updates upstream information when polling
- // was started.
- coordinator.startPolling();
-
- // Needed because two reasons: (1) BpfConntrackEventConsumer#accept only performs cleanup
- // when both upstream and downstream rules are removed. (2) tetherOffloadRuleRemove of
- // api31.BpfCoordinatorShimImpl only decreases the count while the entry is deleted.
- // In the other words, deleteEntry returns true.
- doReturn(true).when(mBpfUpstream4Map).deleteEntry(any());
- doReturn(true).when(mBpfDownstream4Map).deleteEntry(any());
+ final UpstreamInformation upstreamInfo = UPSTREAM_INFORMATIONS.get(upstreamIfindex);
+ if (upstreamInfo == null) {
+ fail("Not support upstream interface index " + upstreamIfindex);
+ }
// Needed because BpfCoordinator#addUpstreamIfindexToMap queries interface parameter for
// interface index.
- doReturn(UPSTREAM_IFACE_PARAMS).when(mDeps).getInterfaceParams(UPSTREAM_IFACE);
+ doReturn(upstreamInfo.interfaceParams).when(mDeps).getInterfaceParams(
+ upstreamInfo.interfaceParams.name);
+ coordinator.addUpstreamNameToLookupTable(upstreamInfo.interfaceParams.index,
+ upstreamInfo.interfaceParams.name);
- coordinator.addUpstreamNameToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE);
- setUpstreamInformationTo(coordinator);
- setDownstreamAndClientInformationTo(coordinator);
+ final LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName(upstreamInfo.interfaceParams.name);
+ lp.addLinkAddress(new LinkAddress(upstreamInfo.address, 32 /* prefix length */));
+ final NetworkCapabilities capabilities = new NetworkCapabilities()
+ .addTransportType(upstreamInfo.transportType);
+ coordinator.updateUpstreamNetworkState(new UpstreamNetworkState(lp, capabilities,
+ new Network(upstreamInfo.netId)));
+ }
+
+ // Setup downstream interface and its client information to BpfCoordinator.
+ //
+ // @param coordinator BpfCoordinator instance.
+ // @param downstreamIfindex downstream interface index. can be the following values.
+ // DOWNSTREAM_IFINDEX: a client information which uses MAC_A is added.
+ // DOWNSTREAM_IFINDEX2: a client information which uses MAC_B is added.
+ // TODO: refactor this function once the client switches between each downstream interface.
+ private void addDownstreamAndClientInformationTo(final BpfCoordinator coordinator,
+ int downstreamIfindex) {
+ if (downstreamIfindex != DOWNSTREAM_IFINDEX && downstreamIfindex != DOWNSTREAM_IFINDEX2) {
+ fail("Not support downstream interface index " + downstreamIfindex);
+ }
+
+ if (downstreamIfindex == DOWNSTREAM_IFINDEX) {
+ coordinator.tetherOffloadClientAdd(mIpServer, CLIENT_INFO_A);
+ } else {
+ coordinator.tetherOffloadClientAdd(mIpServer2, CLIENT_INFO_B);
+ }
+ }
+
+ private void initBpfCoordinatorForRule4(final BpfCoordinator coordinator) throws Exception {
+ setUpstreamInformationTo(coordinator, UPSTREAM_IFINDEX);
+ addDownstreamAndClientInformationTo(coordinator, DOWNSTREAM_IFINDEX);
}
// TODO: Test the IPv4 and IPv6 exist concurrently.
@@ -1401,18 +1535,25 @@
// because the protocol is not an element of the value. Consider using different address
// or port to make them different for better testing.
// TODO: Make the values of {TCP, UDP} rules different.
- final Tether4Key expectedUpstream4KeyTcp = makeUpstream4Key(IPPROTO_TCP);
- final Tether4Key expectedDownstream4KeyTcp = makeDownstream4Key(IPPROTO_TCP);
- final Tether4Value expectedUpstream4ValueTcp = makeUpstream4Value();
- final Tether4Value expectedDownstream4ValueTcp = makeDownstream4Value();
+ final Tether4Key expectedUpstream4KeyTcp = new TestUpstream4Key.Builder()
+ .setProto(IPPROTO_TCP).build();
+ final Tether4Key expectedDownstream4KeyTcp = new TestDownstream4Key.Builder()
+ .setProto(IPPROTO_TCP).build();
+ final Tether4Value expectedUpstream4ValueTcp = new TestUpstream4Value.Builder().build();
+ final Tether4Value expectedDownstream4ValueTcp = new TestDownstream4Value.Builder().build();
- final Tether4Key expectedUpstream4KeyUdp = makeUpstream4Key(IPPROTO_UDP);
- final Tether4Key expectedDownstream4KeyUdp = makeDownstream4Key(IPPROTO_UDP);
- final Tether4Value expectedUpstream4ValueUdp = makeUpstream4Value();
- final Tether4Value expectedDownstream4ValueUdp = makeDownstream4Value();
+ final Tether4Key expectedUpstream4KeyUdp = new TestUpstream4Key.Builder()
+ .setProto(IPPROTO_UDP).build();
+ final Tether4Key expectedDownstream4KeyUdp = new TestDownstream4Key.Builder()
+ .setProto(IPPROTO_UDP).build();
+ final Tether4Value expectedUpstream4ValueUdp = new TestUpstream4Value.Builder().build();
+ final Tether4Value expectedDownstream4ValueUdp = new TestDownstream4Value.Builder().build();
// [1] Adding the first rule on current upstream immediately sends the quota.
- mConsumer.accept(makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, IPPROTO_TCP));
+ mConsumer.accept(new TestConntrackEvent.Builder()
+ .setMsgType(IPCTNL_MSG_CT_NEW)
+ .setProto(IPPROTO_TCP)
+ .build());
verifyTetherOffloadSetInterfaceQuota(inOrder, UPSTREAM_IFINDEX, limit, true /* isInit */);
inOrder.verify(mBpfUpstream4Map)
.insertEntry(eq(expectedUpstream4KeyTcp), eq(expectedUpstream4ValueTcp));
@@ -1421,7 +1562,10 @@
inOrder.verifyNoMoreInteractions();
// [2] Adding the second rule on current upstream does not send the quota.
- mConsumer.accept(makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, IPPROTO_UDP));
+ mConsumer.accept(new TestConntrackEvent.Builder()
+ .setMsgType(IPCTNL_MSG_CT_NEW)
+ .setProto(IPPROTO_UDP)
+ .build());
verifyNeverTetherOffloadSetInterfaceQuota(inOrder);
inOrder.verify(mBpfUpstream4Map)
.insertEntry(eq(expectedUpstream4KeyUdp), eq(expectedUpstream4ValueUdp));
@@ -1430,7 +1574,10 @@
inOrder.verifyNoMoreInteractions();
// [3] Removing the second rule on current upstream does not send the quota.
- mConsumer.accept(makeTestConntrackEvent(IPCTNL_MSG_CT_DELETE, IPPROTO_UDP));
+ mConsumer.accept(new TestConntrackEvent.Builder()
+ .setMsgType(IPCTNL_MSG_CT_DELETE)
+ .setProto(IPPROTO_UDP)
+ .build());
verifyNeverTetherOffloadSetInterfaceQuota(inOrder);
inOrder.verify(mBpfUpstream4Map).deleteEntry(eq(expectedUpstream4KeyUdp));
inOrder.verify(mBpfDownstream4Map).deleteEntry(eq(expectedDownstream4KeyUdp));
@@ -1439,7 +1586,10 @@
// [4] Removing the last rule on current upstream immediately sends the cleanup stuff.
updateStatsEntryForTetherOffloadGetAndClearStats(
buildTestTetherStatsParcel(UPSTREAM_IFINDEX, 0, 0, 0, 0));
- mConsumer.accept(makeTestConntrackEvent(IPCTNL_MSG_CT_DELETE, IPPROTO_TCP));
+ mConsumer.accept(new TestConntrackEvent.Builder()
+ .setMsgType(IPCTNL_MSG_CT_DELETE)
+ .setProto(IPPROTO_TCP)
+ .build());
inOrder.verify(mBpfUpstream4Map).deleteEntry(eq(expectedUpstream4KeyTcp));
inOrder.verify(mBpfDownstream4Map).deleteEntry(eq(expectedDownstream4KeyTcp));
verifyTetherOffloadGetAndClearStats(inOrder, UPSTREAM_IFINDEX);
@@ -1472,14 +1622,20 @@
final BpfCoordinator coordinator = makeBpfCoordinator();
initBpfCoordinatorForRule4(coordinator);
- mConsumer.accept(makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, IPPROTO_TCP));
+ mConsumer.accept(new TestConntrackEvent.Builder()
+ .setMsgType(IPCTNL_MSG_CT_NEW)
+ .setProto(IPPROTO_TCP)
+ .build());
verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(UPSTREAM_IFINDEX)),
eq(new TetherDevValue(UPSTREAM_IFINDEX)));
verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(DOWNSTREAM_IFINDEX)),
eq(new TetherDevValue(DOWNSTREAM_IFINDEX)));
clearInvocations(mBpfDevMap);
- mConsumer.accept(makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, IPPROTO_UDP));
+ mConsumer.accept(new TestConntrackEvent.Builder()
+ .setMsgType(IPCTNL_MSG_CT_NEW)
+ .setProto(IPPROTO_UDP)
+ .build());
verify(mBpfDevMap, never()).updateEntry(any(), any());
}
@@ -1559,10 +1715,10 @@
new TestBpfMap<>(Tether4Key.class, Tether4Value.class);
doReturn(bpfUpstream4Map).when(mDeps).getBpfUpstream4Map();
- final Tether4Key tcpKey = makeUpstream4Key(IPPROTO_TCP);
- final Tether4Key udpKey = makeUpstream4Key(IPPROTO_UDP);
- final Tether4Value tcpValue = makeUpstream4Value();
- final Tether4Value udpValue = makeUpstream4Value();
+ final Tether4Key tcpKey = new TestUpstream4Key.Builder().setProto(IPPROTO_TCP).build();
+ final Tether4Key udpKey = new TestUpstream4Key.Builder().setProto(IPPROTO_UDP).build();
+ final Tether4Value tcpValue = new TestUpstream4Value.Builder().build();
+ final Tether4Value udpValue = new TestUpstream4Value.Builder().build();
checkRefreshConntrackTimeout(bpfUpstream4Map, tcpKey, tcpValue, udpKey, udpValue);
}
@@ -1575,10 +1731,10 @@
new TestBpfMap<>(Tether4Key.class, Tether4Value.class);
doReturn(bpfDownstream4Map).when(mDeps).getBpfDownstream4Map();
- final Tether4Key tcpKey = makeDownstream4Key(IPPROTO_TCP);
- final Tether4Key udpKey = makeDownstream4Key(IPPROTO_UDP);
- final Tether4Value tcpValue = makeDownstream4Value();
- final Tether4Value udpValue = makeDownstream4Value();
+ final Tether4Key tcpKey = new TestDownstream4Key.Builder().setProto(IPPROTO_TCP).build();
+ final Tether4Key udpKey = new TestDownstream4Key.Builder().setProto(IPPROTO_UDP).build();
+ final Tether4Value tcpValue = new TestDownstream4Value.Builder().build();
+ final Tether4Value udpValue = new TestDownstream4Value.Builder().build();
checkRefreshConntrackTimeout(bpfDownstream4Map, tcpKey, tcpValue, udpKey, udpValue);
}
@@ -1592,26 +1748,46 @@
final short offloadedPort = 42;
assertFalse(CollectionUtils.contains(NON_OFFLOADED_UPSTREAM_IPV4_TCP_PORTS,
offloadedPort));
- mConsumer.accept(makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, IPPROTO_TCP, offloadedPort));
+ mConsumer.accept(new TestConntrackEvent.Builder()
+ .setMsgType(IPCTNL_MSG_CT_NEW)
+ .setProto(IPPROTO_TCP)
+ .setRemotePort(offloadedPort)
+ .build());
verify(mBpfUpstream4Map).insertEntry(any(), any());
verify(mBpfDownstream4Map).insertEntry(any(), any());
clearInvocations(mBpfUpstream4Map, mBpfDownstream4Map);
for (final short port : NON_OFFLOADED_UPSTREAM_IPV4_TCP_PORTS) {
- mConsumer.accept(makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, IPPROTO_TCP, port));
+ mConsumer.accept(new TestConntrackEvent.Builder()
+ .setMsgType(IPCTNL_MSG_CT_NEW)
+ .setProto(IPPROTO_TCP)
+ .setRemotePort(port)
+ .build());
verify(mBpfUpstream4Map, never()).insertEntry(any(), any());
verify(mBpfDownstream4Map, never()).insertEntry(any(), any());
- mConsumer.accept(makeTestConntrackEvent(IPCTNL_MSG_CT_DELETE, IPPROTO_TCP, port));
+ mConsumer.accept(new TestConntrackEvent.Builder()
+ .setMsgType(IPCTNL_MSG_CT_DELETE)
+ .setProto(IPPROTO_TCP)
+ .setRemotePort(port)
+ .build());
verify(mBpfUpstream4Map, never()).deleteEntry(any());
verify(mBpfDownstream4Map, never()).deleteEntry(any());
- mConsumer.accept(makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, IPPROTO_UDP, port));
+ mConsumer.accept(new TestConntrackEvent.Builder()
+ .setMsgType(IPCTNL_MSG_CT_NEW)
+ .setProto(IPPROTO_UDP)
+ .setRemotePort(port)
+ .build());
verify(mBpfUpstream4Map).insertEntry(any(), any());
verify(mBpfDownstream4Map).insertEntry(any(), any());
clearInvocations(mBpfUpstream4Map, mBpfDownstream4Map);
- mConsumer.accept(makeTestConntrackEvent(IPCTNL_MSG_CT_DELETE, IPPROTO_UDP, port));
+ mConsumer.accept(new TestConntrackEvent.Builder()
+ .setMsgType(IPCTNL_MSG_CT_DELETE)
+ .setProto(IPPROTO_UDP)
+ .setRemotePort(port)
+ .build());
verify(mBpfUpstream4Map).deleteEntry(any());
verify(mBpfDownstream4Map).deleteEntry(any());
clearInvocations(mBpfUpstream4Map, mBpfDownstream4Map);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
index 46ce82c..690ff71 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
@@ -37,6 +37,9 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL;
+import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -47,6 +50,7 @@
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -77,9 +81,12 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.testutils.DevSdkIgnoreRule;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
@@ -96,6 +103,7 @@
private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app";
private static final String PROVISIONING_APP_RESPONSE = "app_response";
private static final String TEST_PACKAGE_NAME = "com.android.tethering.test";
+ private static final String FAILED_TETHERING_REASON = "Tethering provisioning failed.";
private static final int RECHECK_TIMER_HOURS = 24;
@Mock private CarrierConfigManager mCarrierConfigManager;
@@ -103,10 +111,14 @@
@Mock private Resources mResources;
@Mock private SharedLog mLog;
@Mock private PackageManager mPm;
- @Mock private EntitlementManager.OnUiEntitlementFailedListener mEntitlementFailedListener;
+ @Mock private EntitlementManager
+ .OnTetherProvisioningFailedListener mTetherProvisioningFailedListener;
@Mock private AlarmManager mAlarmManager;
@Mock private PendingIntent mAlarmIntent;
+ @Rule
+ public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
// Like so many Android system APIs, these cannot be mocked because it is marked final.
// We have to use the real versions.
private final PersistableBundle mCarrierConfig = new PersistableBundle();
@@ -179,8 +191,8 @@
@Override
protected Intent runSilentTetherProvisioning(int type,
- final TetheringConfiguration config) {
- Intent intent = super.runSilentTetherProvisioning(type, config);
+ final TetheringConfiguration config, final ResultReceiver receiver) {
+ Intent intent = super.runSilentTetherProvisioning(type, config, receiver);
assertSilentTetherProvisioning(type, config, intent);
silentProvisionCount++;
addDownstreamMapping(type, fakeEntitlementResult);
@@ -245,7 +257,7 @@
mPermissionChangeCallback = spy(() -> { });
mEnMgr = new WrappedEntitlementManager(mMockContext, new Handler(mLooper.getLooper()), mLog,
mPermissionChangeCallback);
- mEnMgr.setOnUiEntitlementFailedListener(mEntitlementFailedListener);
+ mEnMgr.setOnTetherProvisioningFailedListener(mTetherProvisioningFailedListener);
mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
mEnMgr.setTetheringConfigurationFetcher(() -> {
return mConfig;
@@ -268,14 +280,23 @@
when(mResources.getInteger(R.integer.config_mobile_hotspot_provision_check_period))
.thenReturn(RECHECK_TIMER_HOURS);
// Act like the CarrierConfigManager is present and ready unless told otherwise.
- when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
- .thenReturn(mCarrierConfigManager);
+ mockService(Context.CARRIER_CONFIG_SERVICE,
+ CarrierConfigManager.class, mCarrierConfigManager);
when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mCarrierConfig);
mCarrierConfig.putBoolean(CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, true);
mCarrierConfig.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
}
+ private void setupCarrierConfig(boolean carrierSupported) {
+ mCarrierConfig.putBoolean(KEY_CARRIER_SUPPORTS_TETHERING_BOOL, carrierSupported);
+ }
+
+ private <T> void mockService(String serviceName, Class<T> serviceClass, T service) {
+ when(mMockContext.getSystemServiceName(serviceClass)).thenReturn(serviceName);
+ when(mMockContext.getSystemService(serviceName)).thenReturn(service);
+ }
+
@Test
public void canRequireProvisioning() {
setupForRequiredProvisioning();
@@ -285,8 +306,7 @@
@Test
public void toleratesCarrierConfigManagerMissing() {
setupForRequiredProvisioning();
- when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
- .thenReturn(null);
+ mockService(Context.CARRIER_CONFIG_SERVICE, CarrierConfigManager.class, null);
mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
// Couldn't get the CarrierConfigManager, but still had a declared provisioning app.
// Therefore provisioning still be required.
@@ -613,14 +633,16 @@
@Test
public void testCallStopTetheringWhenUiProvisioningFail() {
setupForRequiredProvisioning();
- verify(mEntitlementFailedListener, times(0)).onUiEntitlementFailed(TETHERING_WIFI);
+ verify(mTetherProvisioningFailedListener, times(0))
+ .onTetherProvisioningFailed(TETHERING_WIFI, FAILED_TETHERING_REASON);
mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
mEnMgr.notifyUpstream(true);
mLooper.dispatchAll();
mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
mLooper.dispatchAll();
assertEquals(1, mEnMgr.uiProvisionCount);
- verify(mEntitlementFailedListener, times(1)).onUiEntitlementFailed(TETHERING_WIFI);
+ verify(mTetherProvisioningFailedListener, times(1))
+ .onTetherProvisioningFailed(TETHERING_WIFI, FAILED_TETHERING_REASON);
}
@Test
@@ -644,7 +666,8 @@
// When second downstream is down, exempted downstream can use cellular upstream.
assertEquals(1, mEnMgr.uiProvisionCount);
- verify(mEntitlementFailedListener).onUiEntitlementFailed(TETHERING_USB);
+ verify(mTetherProvisioningFailedListener).onTetherProvisioningFailed(TETHERING_USB,
+ FAILED_TETHERING_REASON);
mEnMgr.stopProvisioningIfNeeded(TETHERING_USB);
assertTrue(mEnMgr.isCellularUpstreamPermitted());
@@ -678,4 +701,85 @@
verify(mAlarmManager).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), anyLong(),
eq(mAlarmIntent));
}
+
+ @Test
+ @IgnoreUpTo(SC_V2)
+ public void requestLatestTetheringEntitlementResult_carrierDoesNotSupport_noProvisionCount()
+ throws Exception {
+ setupForRequiredProvisioning();
+ setupCarrierConfig(false);
+ mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+ ResultReceiver receiver = new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ assertEquals(TETHER_ERROR_PROVISIONING_FAILED, resultCode);
+ }
+ };
+ mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
+ mLooper.dispatchAll();
+ assertEquals(0, mEnMgr.uiProvisionCount);
+ mEnMgr.reset();
+ }
+
+ @Test
+ @IgnoreUpTo(SC_V2)
+ public void reevaluateSimCardProvisioning_carrierUnsupportAndSimswitch() {
+ setupForRequiredProvisioning();
+
+ // Start a tethering with cellular data without provisioning.
+ mEnMgr.notifyUpstream(true);
+ mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, false);
+ mLooper.dispatchAll();
+
+ // Tear down mobile, then switch SIM.
+ mEnMgr.notifyUpstream(false);
+ mLooper.dispatchAll();
+ setupCarrierConfig(false);
+ mEnMgr.reevaluateSimCardProvisioning(mConfig);
+
+ // Turn on upstream.
+ mEnMgr.notifyUpstream(true);
+ mLooper.dispatchAll();
+
+ verify(mTetherProvisioningFailedListener)
+ .onTetherProvisioningFailed(TETHERING_WIFI, "Carrier does not support.");
+ }
+
+ @Test
+ @IgnoreUpTo(SC_V2)
+ public void startProvisioningIfNeeded_carrierUnsupport()
+ throws Exception {
+ setupForRequiredProvisioning();
+ setupCarrierConfig(false);
+ mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
+ verify(mTetherProvisioningFailedListener, never())
+ .onTetherProvisioningFailed(TETHERING_WIFI, "Carrier does not support.");
+
+ mEnMgr.notifyUpstream(true);
+ mLooper.dispatchAll();
+ verify(mTetherProvisioningFailedListener)
+ .onTetherProvisioningFailed(TETHERING_WIFI, "Carrier does not support.");
+ mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI);
+ reset(mTetherProvisioningFailedListener);
+
+ mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
+ mLooper.dispatchAll();
+ verify(mTetherProvisioningFailedListener)
+ .onTetherProvisioningFailed(TETHERING_WIFI, "Carrier does not support.");
+ }
+
+ @Test
+ public void isTetherProvisioningRequired_carrierUnSupport() {
+ setupForRequiredProvisioning();
+ setupCarrierConfig(false);
+ when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app))
+ .thenReturn(new String[0]);
+ mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+
+ if (SdkLevel.isAtLeastT()) {
+ assertTrue(mEnMgr.isTetherProvisioningRequired(mConfig));
+ } else {
+ assertFalse(mEnMgr.isTetherProvisioningRequired(mConfig));
+ }
+ }
}
diff --git a/framework-t/api/module-lib-current.txt b/framework-t/api/module-lib-current.txt
index 59ca730..16308ac 100644
--- a/framework-t/api/module-lib-current.txt
+++ b/framework-t/api/module-lib-current.txt
@@ -4,6 +4,7 @@
public class NetworkStatsManager {
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void forceUpdate();
method public static int getCollapsedRatType(int);
+ method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void noteUidForeground(int, boolean);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void notifyNetworkStatus(@NonNull java.util.List<android.net.Network>, @NonNull java.util.List<android.net.NetworkStateSnapshot>, @Nullable String, @NonNull java.util.List<android.net.UnderlyingNetworkInfo>);
method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForDevice(@NonNull android.net.NetworkTemplate, long, long);
method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTagState(@NonNull android.net.NetworkTemplate, long, long, int, int, int) throws java.lang.SecurityException;
@@ -15,7 +16,6 @@
method public void setPollForce(boolean);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setPollOnOpen(boolean);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setStatsProviderWarningAndLimitAsync(@NonNull String, long, long);
- method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setUidForeground(int, boolean);
field public static final int NETWORK_TYPE_5G_NSA = -2; // 0xfffffffe
}
@@ -90,7 +90,7 @@
method @NonNull public android.net.NetworkStatsCollection build();
}
- public static class NetworkStatsCollection.Key {
+ public static final class NetworkStatsCollection.Key {
ctor public NetworkStatsCollection.Key(@NonNull java.util.Set<android.net.NetworkIdentity>, int, int, int);
}
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index 751a1e2..7f50237 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -6,14 +6,18 @@
}
public class ConnectivityManager {
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void addUidToMeteredNetworkAllowList(int);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void addUidToMeteredNetworkDenyList(int);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void factoryReset();
method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List<android.net.NetworkStateSnapshot> getAllNetworkStateSnapshots();
method @Nullable public android.net.ProxyInfo getGlobalProxy();
method @NonNull public static android.util.Range<java.lang.Integer> getIpSecNetIdRange();
- method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.LinkProperties redactLinkPropertiesForPackage(@NonNull android.net.LinkProperties, int, @NonNull String);
- method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.NetworkCapabilities redactNetworkCapabilitiesForPackage(@NonNull android.net.NetworkCapabilities, int, @NonNull String);
+ method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.LinkProperties getRedactedLinkPropertiesForPackage(@NonNull android.net.LinkProperties, int, @NonNull String);
+ method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.NetworkCapabilities getRedactedNetworkCapabilitiesForPackage(@NonNull android.net.NetworkCapabilities, int, @NonNull String);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackForUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void removeUidFromMeteredNetworkAllowList(int);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void removeUidFromMeteredNetworkDenyList(int);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void replaceFirewallChain(int, @NonNull int[]);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
method @Deprecated public boolean requestRouteToHostAddress(int, java.net.InetAddress);
@@ -30,8 +34,6 @@
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void startCaptivePortalApp(@NonNull android.net.Network);
method public void systemReady();
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void updateFirewallRule(int, int, boolean);
- method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void updateMeteredNetworkAllowList(int, boolean);
- method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void updateMeteredNetworkDenyList(int, boolean);
field public static final String ACTION_CLEAR_DNS_CACHE = "android.net.action.CLEAR_DNS_CACHE";
field public static final String ACTION_PROMPT_LOST_VALIDATION = "android.net.action.PROMPT_LOST_VALIDATION";
field public static final String ACTION_PROMPT_PARTIAL_CONNECTIVITY = "android.net.action.PROMPT_PARTIAL_CONNECTIVITY";
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index 764cffa..bdefed1 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -236,6 +236,7 @@
public abstract class NetworkAgent {
ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, int, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider);
ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkScore, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider);
+ method public void destroyAndAwaitReplacement(@IntRange(from=0, to=0x1388) int);
method @Nullable public android.net.Network getNetwork();
method public void markConnected();
method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData);
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 8651f34..a798f6e 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -1643,10 +1643,10 @@
android.Manifest.permission.NETWORK_SETTINGS})
@SystemApi(client = MODULE_LIBRARIES)
@Nullable
- public LinkProperties redactLinkPropertiesForPackage(@NonNull LinkProperties lp, int uid,
+ public LinkProperties getRedactedLinkPropertiesForPackage(@NonNull LinkProperties lp, int uid,
@NonNull String packageName) {
try {
- return mService.redactLinkPropertiesForPackage(
+ return mService.getRedactedLinkPropertiesForPackage(
lp, uid, packageName, getAttributionTag());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1683,9 +1683,11 @@
* Redact {@link NetworkCapabilities} for a given package.
*
* Returns an instance of {@link NetworkCapabilities} that is appropriately redacted to send
- * to the given package, considering its permissions. Calling this method will blame the UID for
- * retrieving the device location if the passed capabilities contain location-sensitive
- * information.
+ * to the given package, considering its permissions. If the passed capabilities contain
+ * location-sensitive information, they will be redacted to the correct degree for the location
+ * permissions of the app (COARSE or FINE), and will blame the UID accordingly for retrieving
+ * that level of location. If the UID holds no location permission, the returned object will
+ * contain no location-sensitive information and the UID is not blamed.
*
* @param nc A {@link NetworkCapabilities} instance which will be redacted.
* @param uid The target uid.
@@ -1700,11 +1702,11 @@
android.Manifest.permission.NETWORK_SETTINGS})
@SystemApi(client = MODULE_LIBRARIES)
@Nullable
- public NetworkCapabilities redactNetworkCapabilitiesForPackage(
+ public NetworkCapabilities getRedactedNetworkCapabilitiesForPackage(
@NonNull NetworkCapabilities nc,
int uid, @NonNull String packageName) {
try {
- return mService.redactNetworkCapabilitiesForPackage(nc, uid, packageName,
+ return mService.getRedactedNetworkCapabilitiesForPackage(nc, uid, packageName,
getAttributionTag());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -5706,8 +5708,8 @@
}
/**
- * Sets whether the specified UID is allowed to use data on metered networks even when
- * background data is restricted.
+ * Adds the specified UID to the list of UIds that are allowed to use data on metered networks
+ * even when background data is restricted. The deny list takes precedence over the allow list.
*
* @param uid uid of target app
* @throws IllegalStateException if updating allow list failed.
@@ -5719,17 +5721,40 @@
android.Manifest.permission.NETWORK_STACK,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
})
- public void updateMeteredNetworkAllowList(final int uid, final boolean add) {
+ public void addUidToMeteredNetworkAllowList(final int uid) {
try {
- mService.updateMeteredNetworkAllowList(uid, add);
+ mService.updateMeteredNetworkAllowList(uid, true /* add */);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Sets whether the specified UID is prevented from using background data on metered networks.
- * Takes precedence over {@link #updateMeteredNetworkAllowList}.
+ * Removes the specified UID from the list of UIDs that are allowed to use background data on
+ * metered networks when background data is restricted. The deny list takes precedence over
+ * the allow list.
+ *
+ * @param uid uid of target app
+ * @throws IllegalStateException if updating allow list failed.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+ })
+ public void removeUidFromMeteredNetworkAllowList(final int uid) {
+ try {
+ mService.updateMeteredNetworkAllowList(uid, false /* remove */);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Adds the specified UID to the list of UIDs that are not allowed to use background data on
+ * metered networks. Takes precedence over {@link #addUidToMeteredNetworkAllowList}.
*
* @param uid uid of target app
* @throws IllegalStateException if updating deny list failed.
@@ -5741,9 +5766,32 @@
android.Manifest.permission.NETWORK_STACK,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
})
- public void updateMeteredNetworkDenyList(final int uid, final boolean add) {
+ public void addUidToMeteredNetworkDenyList(final int uid) {
try {
- mService.updateMeteredNetworkDenyList(uid, add);
+ mService.updateMeteredNetworkDenyList(uid, true /* add */);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes the specified UID from the list of UIds that can use use background data on metered
+ * networks if background data is not restricted. The deny list takes precedence over the
+ * allow list.
+ *
+ * @param uid uid of target app
+ * @throws IllegalStateException if updating deny list failed.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+ })
+ public void removeUidFromMeteredNetworkDenyList(final int uid) {
+ try {
+ mService.updateMeteredNetworkDenyList(uid, false /* remove */);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index 1e1f653..0988bf3 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -76,13 +76,13 @@
LinkProperties getActiveLinkProperties();
LinkProperties getLinkPropertiesForType(int networkType);
LinkProperties getLinkProperties(in Network network);
- LinkProperties redactLinkPropertiesForPackage(in LinkProperties lp, int uid, String packageName,
- String callingAttributionTag);
+ LinkProperties getRedactedLinkPropertiesForPackage(in LinkProperties lp, int uid,
+ String packageName, String callingAttributionTag);
NetworkCapabilities getNetworkCapabilities(in Network network, String callingPackageName,
String callingAttributionTag);
- NetworkCapabilities redactNetworkCapabilitiesForPackage(in NetworkCapabilities nc, int uid,
+ NetworkCapabilities getRedactedNetworkCapabilitiesForPackage(in NetworkCapabilities nc, int uid,
String callingPackageName, String callingAttributionTag);
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
diff --git a/framework/src/android/net/INetworkAgentRegistry.aidl b/framework/src/android/net/INetworkAgentRegistry.aidl
index 08536ca..2b22a5c 100644
--- a/framework/src/android/net/INetworkAgentRegistry.aidl
+++ b/framework/src/android/net/INetworkAgentRegistry.aidl
@@ -47,4 +47,5 @@
void sendAddDscpPolicy(in DscpPolicy policy);
void sendRemoveDscpPolicy(int policyId);
void sendRemoveAllDscpPolicies();
+ void sendDestroyAndAwaitReplacement(int timeoutMillis);
}
diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java
index 945e670..fdc9081 100644
--- a/framework/src/android/net/NetworkAgent.java
+++ b/framework/src/android/net/NetworkAgent.java
@@ -434,6 +434,14 @@
*/
public static final int CMD_DSCP_POLICY_STATUS = BASE + 28;
+ /**
+ * Sent by the NetworkAgent to ConnectivityService to notify that this network is expected to be
+ * replaced within the specified time by a similar network.
+ * arg1 = timeout in milliseconds
+ * @hide
+ */
+ public static final int EVENT_DESTROY_AND_AWAIT_REPLACEMENT = BASE + 29;
+
private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType,
config.legacyTypeName, config.legacySubTypeName);
@@ -943,6 +951,45 @@
}
/**
+ * Indicates that this agent will likely soon be replaced by another agent for a very similar
+ * network (e.g., same Wi-Fi SSID).
+ *
+ * If the network is not currently satisfying any {@link NetworkRequest}s, it will be torn down.
+ * If it is satisfying requests, then the native network corresponding to the agent will be
+ * destroyed immediately, but the agent will remain registered and will continue to satisfy
+ * requests until {@link #unregister} is called, the network is replaced by an equivalent or
+ * better network, or the specified timeout expires. During this time:
+ *
+ * <ul>
+ * <li>The agent may not send any further updates, for example by calling methods
+ * such as {@link #sendNetworkCapabilities}, {@link #sendLinkProperties},
+ * {@link #sendNetworkScore(NetworkScore)} and so on. Any such updates will be ignored.
+ * <li>The network will remain connected and continue to satisfy any requests that it would
+ * otherwise satisfy (including, possibly, the default request).
+ * <li>The validation state of the network will not change, and calls to
+ * {@link ConnectivityManager#reportNetworkConnectivity(Network, boolean)} will be ignored.
+ * </ul>
+ *
+ * Once this method is called, it is not possible to restore the agent to a functioning state.
+ * If a replacement network becomes available, then a new agent must be registered. When that
+ * replacement network is fully capable of replacing this network (including, possibly, being
+ * validated), this agent will no longer be needed and will be torn down. Otherwise, this agent
+ * can be disconnected by calling {@link #unregister}. If {@link #unregister} is not called,
+ * this agent will automatically be unregistered when the specified timeout expires. Any
+ * teardown delay previously set using{@link #setTeardownDelayMillis} is ignored.
+ *
+ * <p>This method has no effect if {@link #markConnected} has not yet been called.
+ * <p>This method may only be called once.
+ *
+ * @param timeoutMillis the timeout after which this network will be unregistered even if
+ * {@link #unregister} was not called.
+ */
+ public void destroyAndAwaitReplacement(
+ @IntRange(from = 0, to = MAX_TEARDOWN_DELAY_MS) int timeoutMillis) {
+ queueOrSendMessage(reg -> reg.sendDestroyAndAwaitReplacement(timeoutMillis));
+ }
+
+ /**
* Change the legacy subtype of this network agent.
*
* This is only for backward compatibility and should not be used by non-legacy network agents,
diff --git a/framework/src/android/net/QosSession.java b/framework/src/android/net/QosSession.java
index 93f2ff2..25f3965 100644
--- a/framework/src/android/net/QosSession.java
+++ b/framework/src/android/net/QosSession.java
@@ -58,12 +58,12 @@
}
/**
- * Gets the session id that is unique within that type.
+ * Gets the {@link QosSession} identifier which is set by the actor providing the QoS.
* <p/>
- * Note: The session id is set by the actor providing the qos. It can be either manufactured by
- * the actor, but also may have a particular meaning within that type. For example, using the
- * bearer id as the session id for {@link android.telephony.data.EpsBearerQosSessionAttributes}
- * is a straight forward way to keep the sessions unique from one another within that type.
+ * Note: It can be either manufactured by the actor, but also may have a particular meaning
+ * within that type. For example, using the bearer id as the session id for
+ * {@link android.telephony.data.EpsBearerQosSessionAttributes} is a straight forward way to
+ * keep the sessions unique from one another within that type.
*
* @return the id of the session
*/
diff --git a/nearby/Android.bp b/nearby/Android.bp
index baa0740..fb4e3cd 100644
--- a/nearby/Android.bp
+++ b/nearby/Android.bp
@@ -30,8 +30,8 @@
java_library {
- name: "service-nearby",
- srcs: [],
+ name: "service-nearby-pre-jarjar",
+ srcs: ["service-src/**/*.java"],
sdk_version: "module_current",
min_sdk_version: "30",
apex_available: ["com.android.tethering"],
diff --git a/nearby/service-src/com/android/server/nearby/NearbyService.java b/nearby/service-src/com/android/server/nearby/NearbyService.java
new file mode 100644
index 0000000..88752cc
--- /dev/null
+++ b/nearby/service-src/com/android/server/nearby/NearbyService.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby;
+
+import android.content.Context;
+import android.os.Binder;
+
+/**
+ * Stub NearbyService class, used until NearbyService code is available in all branches.
+ *
+ * This can be published as an empty service in branches that use it.
+ */
+public final class NearbyService extends Binder {
+ public NearbyService(Context ctx) {
+ throw new UnsupportedOperationException("This is a stub service");
+ }
+
+ /** Called by the service initializer on each boot phase */
+ public void onBootPhase(int phase) {
+ // Do nothing
+ }
+}
diff --git a/service-t/Android.bp b/service-t/Android.bp
index 159a870..3146a93 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -38,6 +38,7 @@
"framework-connectivity-t-pre-jarjar",
"framework-tethering.stubs.module_lib",
"service-connectivity-pre-jarjar",
+ "service-nearby-pre-jarjar",
"unsupportedappusage",
],
static_libs: [
diff --git a/service-t/src/com/android/server/ConnectivityServiceInitializer.java b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
index 67757af..25fe5e9 100644
--- a/service-t/src/com/android/server/ConnectivityServiceInitializer.java
+++ b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
@@ -20,16 +20,19 @@
import android.util.Log;
import com.android.modules.utils.build.SdkLevel;
+import com.android.networkstack.apishim.ConstantsShim;
+import com.android.server.nearby.NearbyService;
/**
* Connectivity service initializer for core networking. This is called by system server to create
- * a new instance of ConnectivityService.
+ * a new instance of connectivity services.
*/
public final class ConnectivityServiceInitializer extends SystemService {
private static final String TAG = ConnectivityServiceInitializer.class.getSimpleName();
private final ConnectivityService mConnectivity;
private final IpSecService mIpSecService;
private final NsdService mNsdService;
+ private final NearbyService mNearbyService;
public ConnectivityServiceInitializer(Context context) {
super(context);
@@ -38,6 +41,7 @@
mConnectivity = new ConnectivityService(context);
mIpSecService = createIpSecService(context);
mNsdService = createNsdService(context);
+ mNearbyService = createNearbyService(context);
}
@Override
@@ -55,6 +59,19 @@
Log.i(TAG, "Registering " + Context.NSD_SERVICE);
publishBinderService(Context.NSD_SERVICE, mNsdService, /* allowIsolated= */ false);
}
+
+ if (mNearbyService != null) {
+ Log.i(TAG, "Registering " + ConstantsShim.NEARBY_SERVICE);
+ publishBinderService(ConstantsShim.NEARBY_SERVICE, mNearbyService,
+ /* allowIsolated= */ false);
+ }
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (mNearbyService != null) {
+ mNearbyService.onBootPhase(phase);
+ }
}
/**
@@ -76,4 +93,17 @@
return null;
}
}
+
+ /** Return Nearby service instance or null if current SDK is lower than T */
+ private NearbyService createNearbyService(final Context context) {
+ if (!SdkLevel.isAtLeastT()) return null;
+ try {
+ return new NearbyService(context);
+ } catch (UnsupportedOperationException e) {
+ // Nearby is not yet supported in all branches
+ // TODO: remove catch clause when it is available.
+ Log.i(TAG, "Skipping unsupported service " + ConstantsShim.NEARBY_SERVICE);
+ return null;
+ }
+ }
}
diff --git a/service/Android.bp b/service/Android.bp
index a4d8d64..aed4b1e 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -159,7 +159,7 @@
static_libs: [
"service-connectivity-pre-jarjar",
"service-connectivity-tiramisu-pre-jarjar",
- "service-nearby",
+ "service-nearby-pre-jarjar",
],
jarjar_rules: ":connectivity-jarjar-rules",
apex_available: [
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index dd92a18..3c404b4 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -2172,7 +2172,7 @@
@Override
@Nullable
- public LinkProperties redactLinkPropertiesForPackage(@NonNull LinkProperties lp, int uid,
+ public LinkProperties getRedactedLinkPropertiesForPackage(@NonNull LinkProperties lp, int uid,
@NonNull String packageName, @Nullable String callingAttributionTag) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(lp);
@@ -2207,8 +2207,9 @@
}
@Override
- public NetworkCapabilities redactNetworkCapabilitiesForPackage(@NonNull NetworkCapabilities nc,
- int uid, @NonNull String packageName, @Nullable String callingAttributionTag) {
+ public NetworkCapabilities getRedactedNetworkCapabilitiesForPackage(
+ @NonNull NetworkCapabilities nc, int uid, @NonNull String packageName,
+ @Nullable String callingAttributionTag) {
Objects.requireNonNull(nc);
Objects.requireNonNull(packageName);
enforceNetworkStackOrSettingsPermission();
@@ -3502,6 +3503,12 @@
return false;
}
+ private boolean isDisconnectRequest(Message msg) {
+ if (msg.what != NetworkAgent.EVENT_NETWORK_INFO_CHANGED) return false;
+ final NetworkInfo info = (NetworkInfo) ((Pair) msg.obj).second;
+ return info.getState() == NetworkInfo.State.DISCONNECTED;
+ }
+
// must be stateless - things change under us.
private class NetworkStateTrackerHandler extends Handler {
public NetworkStateTrackerHandler(Looper looper) {
@@ -3518,6 +3525,11 @@
return;
}
+ // If the network has been destroyed, the only thing that it can do is disconnect.
+ if (nai.destroyed && !isDisconnectRequest(msg)) {
+ return;
+ }
+
switch (msg.what) {
case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: {
final NetworkCapabilities networkCapabilities = new NetworkCapabilities(
@@ -3619,12 +3631,60 @@
}
break;
}
+ case NetworkAgent.EVENT_DESTROY_AND_AWAIT_REPLACEMENT: {
+ // If nai is not yet created, or is already destroyed, ignore.
+ if (!shouldDestroyNativeNetwork(nai)) break;
+
+ final int timeoutMs = (int) arg.second;
+ if (timeoutMs < 0 || timeoutMs > NetworkAgent.MAX_TEARDOWN_DELAY_MS) {
+ Log.e(TAG, "Invalid network replacement timer " + timeoutMs
+ + ", must be between 0 and " + NetworkAgent.MAX_TEARDOWN_DELAY_MS);
+ }
+
+ // Marking a network awaiting replacement is used to ensure that any requests
+ // satisfied by the network do not switch to another network until a
+ // replacement is available or the wait for a replacement times out.
+ // If the network is inactive (i.e., nascent or lingering), then there are no
+ // such requests, and there is no point keeping it. Just tear it down.
+ // Note that setLingerDuration(0) cannot be used to do this because the network
+ // could be nascent.
+ nai.clearInactivityState();
+ if (unneeded(nai, UnneededFor.TEARDOWN)) {
+ Log.d(TAG, nai.toShortString()
+ + " marked awaiting replacement is unneeded, tearing down instead");
+ teardownUnneededNetwork(nai);
+ break;
+ }
+
+ Log.d(TAG, "Marking " + nai.toShortString()
+ + " destroyed, awaiting replacement within " + timeoutMs + "ms");
+ destroyNativeNetwork(nai);
+
+ // TODO: deduplicate this call with the one in disconnectAndDestroyNetwork.
+ // This is not trivial because KeepaliveTracker#handleStartKeepalive does not
+ // consider the fact that the network could already have disconnected or been
+ // destroyed. Fix the code to send ERROR_INVALID_NETWORK when this happens
+ // (taking care to ensure no dup'd FD leaks), then remove the code duplication
+ // and move this code to a sensible location (destroyNativeNetwork perhaps?).
+ mKeepaliveTracker.handleStopAllKeepalives(nai,
+ SocketKeepalive.ERROR_INVALID_NETWORK);
+
+ nai.updateScoreForNetworkAgentUpdate();
+ // This rematch is almost certainly not going to result in any changes, because
+ // the destroyed flag is only just above the "current satisfier wins"
+ // tie-breaker. But technically anything that affects scoring should rematch.
+ rematchAllNetworksAndRequests();
+ mHandler.postDelayed(() -> nai.disconnect(), timeoutMs);
+ break;
+ }
}
}
private boolean maybeHandleNetworkMonitorMessage(Message msg) {
final int netId = msg.arg2;
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
+ // If a network has already been destroyed, all NetworkMonitor updates are ignored.
+ if (nai != null && nai.destroyed) return true;
switch (msg.what) {
default:
return false;
@@ -4124,6 +4184,10 @@
}
}
+ private static boolean shouldDestroyNativeNetwork(@NonNull NetworkAgentInfo nai) {
+ return nai.created && !nai.destroyed;
+ }
+
private void handleNetworkAgentDisconnected(Message msg) {
NetworkAgentInfo nai = (NetworkAgentInfo) msg.obj;
disconnectAndDestroyNetwork(nai);
@@ -4230,7 +4294,7 @@
}
private void destroyNetwork(NetworkAgentInfo nai) {
- if (nai.created) {
+ if (shouldDestroyNativeNetwork(nai)) {
// Tell netd to clean up the configuration for this network
// (routing rules, DNS, etc).
// This may be slow as it requires a lot of netd shelling out to ip and
@@ -4239,15 +4303,15 @@
// network or service a new request from an app), so network traffic isn't interrupted
// for an unnecessarily long time.
destroyNativeNetwork(nai);
- mDnsManager.removeNetwork(nai.network);
-
- // clean up tc police filters on interface.
- if (nai.everConnected && canNetworkBeRateLimited(nai) && mIngressRateLimit >= 0) {
- mDeps.disableIngressRateLimit(nai.linkProperties.getInterfaceName());
- }
+ }
+ if (!nai.created && !SdkLevel.isAtLeastT()) {
+ // Backwards compatibility: send onNetworkDestroyed even if network was never created.
+ // This can never run if the code above runs because shouldDestroyNativeNetwork is
+ // false if the network was never created.
+ // TODO: delete when S is no longer supported.
+ nai.onNetworkDestroyed();
}
mNetIdManager.releaseNetId(nai.network.getNetId());
- nai.onNetworkDestroyed();
}
private boolean createNativeNetwork(@NonNull NetworkAgentInfo nai) {
@@ -4290,6 +4354,18 @@
} catch (RemoteException | ServiceSpecificException e) {
loge("Exception destroying network: " + e);
}
+ // TODO: defer calling this until the network is removed from mNetworkAgentInfos.
+ // Otherwise, a private DNS configuration update for a destroyed network, or one that never
+ // gets created, could add data to DnsManager data structures that will never get deleted.
+ mDnsManager.removeNetwork(nai.network);
+
+ // clean up tc police filters on interface.
+ if (nai.everConnected && canNetworkBeRateLimited(nai) && mIngressRateLimit >= 0) {
+ mDeps.disableIngressRateLimit(nai.linkProperties.getInterfaceName());
+ }
+
+ nai.destroyed = true;
+ nai.onNetworkDestroyed();
}
// If this method proves to be too slow then we can maintain a separate
@@ -8542,11 +8618,19 @@
log(" accepting network in place of " + previousSatisfier.toShortString());
}
previousSatisfier.removeRequest(previousRequest.requestId);
- if (canSupportGracefulNetworkSwitch(previousSatisfier, newSatisfier)) {
+ if (canSupportGracefulNetworkSwitch(previousSatisfier, newSatisfier)
+ && !previousSatisfier.destroyed) {
// If this network switch can't be supported gracefully, the request is not
// lingered. This allows letting go of the network sooner to reclaim some
// performance on the new network, since the radio can't do both at the same
// time while preserving good performance.
+ //
+ // Also don't linger the request if the old network has been destroyed.
+ // A destroyed network does not provide actual network connectivity, so
+ // lingering it is not useful. In particular this ensures that a destroyed
+ // network is outscored by its replacement,
+ // then it is torn down immediately instead of being lingered, and any apps that
+ // were using it immediately get onLost and can connect using the new network.
previousSatisfier.lingerRequest(previousRequest.requestId, now);
}
} else {
diff --git a/service/src/com/android/server/connectivity/Nat464Xlat.java b/service/src/com/android/server/connectivity/Nat464Xlat.java
index c66a280..7b06682 100644
--- a/service/src/com/android/server/connectivity/Nat464Xlat.java
+++ b/service/src/com/android/server/connectivity/Nat464Xlat.java
@@ -132,8 +132,8 @@
final boolean skip464xlat = (nai.netAgentConfig() != null)
&& nai.netAgentConfig().skip464xlat;
- return supported && connected && isIpv6OnlyNetwork && !skip464xlat
- && (nai.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
+ return supported && connected && isIpv6OnlyNetwork && !skip464xlat && !nai.destroyed
+ && (nai.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
? isCellular464XlatEnabled() : true);
}
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index e29d616..ee45e5c 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -732,6 +732,12 @@
mHandler.obtainMessage(NetworkAgent.EVENT_REMOVE_ALL_DSCP_POLICIES,
new Pair<>(NetworkAgentInfo.this, null)).sendToTarget();
}
+
+ @Override
+ public void sendDestroyAndAwaitReplacement(final int timeoutMillis) {
+ mHandler.obtainMessage(NetworkAgent.EVENT_DESTROY_AND_AWAIT_REPLACEMENT,
+ new Pair<>(NetworkAgentInfo.this, timeoutMillis)).sendToTarget();
+ }
}
/**
@@ -976,7 +982,7 @@
/**
* Update the ConnectivityService-managed bits in the score.
*
- * Call this after updating the network agent config.
+ * Call this after changing any data that might affect the score (e.g., agent config).
*/
public void updateScoreForNetworkAgentUpdate() {
mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig,
@@ -1256,6 +1262,8 @@
+ "network{" + network + "} handle{" + network.getNetworkHandle() + "} ni{"
+ networkInfo.toShortString() + "} "
+ mScore + " "
+ + (created ? " created" : "")
+ + (destroyed ? " destroyed" : "")
+ (isNascent() ? " nascent" : (isLingering() ? " lingering" : ""))
+ (everValidated ? " everValidated" : "")
+ (lastValidated ? " lastValidated" : "")
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 a7f9157..a840242 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
@@ -881,6 +881,11 @@
}
}
+ protected void startActivity() throws Exception {
+ final Intent launchIntent = getIntentForComponent(TYPE_COMPONENT_ACTIVTIY);
+ mContext.startActivity(launchIntent);
+ }
+
private void startForegroundService() throws Exception {
final Intent launchIntent = getIntentForComponent(TYPE_COMPONENT_FOREGROUND_SERVICE);
mContext.startForegroundService(launchIntent);
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
index 604a0b6..2f30536 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
@@ -20,6 +20,7 @@
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
+import static com.android.compatibility.common.util.FeatureUtil.isTV;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
import static com.android.cts.net.hostside.Property.METERED_NETWORK;
@@ -27,14 +28,14 @@
import static org.junit.Assert.fail;
+import androidx.test.filters.LargeTest;
+
import com.android.compatibility.common.util.CddTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-import androidx.test.filters.LargeTest;
-
@RequiredProperties({DATA_SAVER_MODE, METERED_NETWORK})
@LargeTest
public class DataSaverModeTest extends AbstractRestrictBackgroundNetworkTestCase {
@@ -113,6 +114,11 @@
turnScreenOff();
assertBackgroundNetworkAccess(false);
turnScreenOn();
+ // On some TVs, it is possible that the activity on top may change after the screen is
+ // turned off and on again, so relaunch the activity in the test app again.
+ if (isTV()) {
+ startActivity();
+ }
assertForegroundNetworkAccess();
// Goes back to background state.
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index d605799..48a1c79 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -30,6 +30,8 @@
</target_preparer>
<target_preparer class="com.android.testutils.ConnectivityCheckTargetPreparer">
</target_preparer>
+ <target_preparer class="com.android.testutils.DisableConfigSyncTargetPreparer">
+ </target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.net.cts" />
<option name="runtime-hint" value="9m4s" />
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 5e8bffa..d40bc9f 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -591,7 +591,7 @@
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
@Test
- public void testRedactLinkPropertiesForPackage() throws Exception {
+ public void testGetRedactedLinkPropertiesForPackage() throws Exception {
final String groundedPkg = findPackageByPermissions(
List.of(), /* requiredPermissions */
List.of(ACCESS_NETWORK_STATE) /* forbiddenPermissions */);
@@ -628,54 +628,55 @@
// No matter what the given uid is, a SecurityException will be thrown if the caller
// doesn't hold the NETWORK_SETTINGS permission.
assertThrows(SecurityException.class,
- () -> mCm.redactLinkPropertiesForPackage(lp, groundedUid, groundedPkg));
+ () -> mCm.getRedactedLinkPropertiesForPackage(lp, groundedUid, groundedPkg));
assertThrows(SecurityException.class,
- () -> mCm.redactLinkPropertiesForPackage(lp, normalUid, normalPkg));
+ () -> mCm.getRedactedLinkPropertiesForPackage(lp, normalUid, normalPkg));
assertThrows(SecurityException.class,
- () -> mCm.redactLinkPropertiesForPackage(lp, privilegedUid, privilegedPkg));
+ () -> mCm.getRedactedLinkPropertiesForPackage(lp, privilegedUid, privilegedPkg));
runAsShell(NETWORK_SETTINGS, () -> {
// No matter what the given uid is, if the given LinkProperties is null, then
// NullPointerException will be thrown.
assertThrows(NullPointerException.class,
- () -> mCm.redactLinkPropertiesForPackage(null, groundedUid, groundedPkg));
+ () -> mCm.getRedactedLinkPropertiesForPackage(null, groundedUid, groundedPkg));
assertThrows(NullPointerException.class,
- () -> mCm.redactLinkPropertiesForPackage(null, normalUid, normalPkg));
+ () -> mCm.getRedactedLinkPropertiesForPackage(null, normalUid, normalPkg));
assertThrows(NullPointerException.class,
- () -> mCm.redactLinkPropertiesForPackage(null, privilegedUid, privilegedPkg));
+ () -> mCm.getRedactedLinkPropertiesForPackage(
+ null, privilegedUid, privilegedPkg));
// Make sure null is returned for a UID without ACCESS_NETWORK_STATE.
- assertNull(mCm.redactLinkPropertiesForPackage(lp, groundedUid, groundedPkg));
+ assertNull(mCm.getRedactedLinkPropertiesForPackage(lp, groundedUid, groundedPkg));
// CaptivePortalApiUrl & CaptivePortalData will be set to null if given uid doesn't hold
// the NETWORK_SETTINGS permission.
- assertNull(mCm.redactLinkPropertiesForPackage(lp, normalUid, normalPkg)
+ assertNull(mCm.getRedactedLinkPropertiesForPackage(lp, normalUid, normalPkg)
.getCaptivePortalApiUrl());
- assertNull(mCm.redactLinkPropertiesForPackage(lp, normalUid, normalPkg)
+ assertNull(mCm.getRedactedLinkPropertiesForPackage(lp, normalUid, normalPkg)
.getCaptivePortalData());
// MTU is not sensitive and is not redacted.
- assertEquals(mtu, mCm.redactLinkPropertiesForPackage(lp, normalUid, normalPkg)
+ assertEquals(mtu, mCm.getRedactedLinkPropertiesForPackage(lp, normalUid, normalPkg)
.getMtu());
// CaptivePortalApiUrl & CaptivePortalData will be preserved if the given uid holds the
// NETWORK_SETTINGS permission.
assertEquals(capportUrl,
- mCm.redactLinkPropertiesForPackage(lp, privilegedUid, privilegedPkg)
+ mCm.getRedactedLinkPropertiesForPackage(lp, privilegedUid, privilegedPkg)
.getCaptivePortalApiUrl());
assertEquals(capportData,
- mCm.redactLinkPropertiesForPackage(lp, privilegedUid, privilegedPkg)
+ mCm.getRedactedLinkPropertiesForPackage(lp, privilegedUid, privilegedPkg)
.getCaptivePortalData());
});
}
private NetworkCapabilities redactNc(@NonNull final NetworkCapabilities nc, int uid,
@NonNull String packageName) {
- return mCm.redactNetworkCapabilitiesForPackage(nc, uid, packageName);
+ return mCm.getRedactedNetworkCapabilitiesForPackage(nc, uid, packageName);
}
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
@Test
- public void testRedactNetworkCapabilitiesForPackage() throws Exception {
+ public void testGetRedactedNetworkCapabilitiesForPackage() throws Exception {
final String groundedPkg = findPackageByPermissions(
List.of(), /* requiredPermissions */
List.of(ACCESS_NETWORK_STATE) /* forbiddenPermissions */);
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 225602f..af567ff 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -19,6 +19,7 @@
import android.app.Instrumentation
import android.content.Context
import android.net.ConnectivityManager
+import android.net.EthernetNetworkSpecifier
import android.net.INetworkAgent
import android.net.INetworkAgentRegistry
import android.net.InetAddresses
@@ -35,6 +36,7 @@
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
@@ -42,7 +44,9 @@
import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkCapabilities.TRANSPORT_TEST
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkCapabilities.TRANSPORT_VPN
import android.net.NetworkInfo
import android.net.NetworkProvider
@@ -100,6 +104,7 @@
import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnUnregisterQosCallback
import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnValidationStatus
import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.assertThrows
import org.junit.After
import org.junit.Assume.assumeFalse
import org.junit.Before
@@ -112,6 +117,8 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.timeout
import org.mockito.Mockito.verify
+import java.io.IOException
+import java.net.DatagramSocket
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.Socket
@@ -249,6 +256,28 @@
.build()
}
+ private fun makeTestNetworkCapabilities(
+ specifier: String? = null,
+ transports: IntArray = intArrayOf()
+ ) = NetworkCapabilities().apply {
+ addTransportType(TRANSPORT_TEST)
+ removeCapability(NET_CAPABILITY_TRUSTED)
+ removeCapability(NET_CAPABILITY_INTERNET)
+ addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ addCapability(NET_CAPABILITY_NOT_ROAMING)
+ addCapability(NET_CAPABILITY_NOT_VPN)
+ if (SdkLevel.isAtLeastS()) {
+ addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ }
+ if (null != specifier) {
+ setNetworkSpecifier(CompatUtil.makeEthernetNetworkSpecifier(specifier))
+ }
+ for (t in transports) { addTransportType(t) }
+ // Most transports are not allowed on test networks unless the network is marked restricted.
+ // This test does not need
+ if (transports.size > 0) removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ }
+
private fun createNetworkAgent(
context: Context = realContext,
specifier: String? = null,
@@ -256,20 +285,7 @@
initialLp: LinkProperties? = null,
initialConfig: NetworkAgentConfig? = null
): TestableNetworkAgent {
- val nc = initialNc ?: NetworkCapabilities().apply {
- addTransportType(TRANSPORT_TEST)
- removeCapability(NET_CAPABILITY_TRUSTED)
- removeCapability(NET_CAPABILITY_INTERNET)
- addCapability(NET_CAPABILITY_NOT_SUSPENDED)
- addCapability(NET_CAPABILITY_NOT_ROAMING)
- addCapability(NET_CAPABILITY_NOT_VPN)
- if (SdkLevel.isAtLeastS()) {
- addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
- }
- if (null != specifier) {
- setNetworkSpecifier(CompatUtil.makeEthernetNetworkSpecifier(specifier))
- }
- }
+ val nc = initialNc ?: makeTestNetworkCapabilities(specifier)
val lp = initialLp ?: LinkProperties().apply {
addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 32))
addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
@@ -284,12 +300,14 @@
context: Context = realContext,
specifier: String? = UUID.randomUUID().toString(),
initialConfig: NetworkAgentConfig? = null,
- expectedInitSignalStrengthThresholds: IntArray? = intArrayOf()
+ expectedInitSignalStrengthThresholds: IntArray? = intArrayOf(),
+ transports: IntArray = intArrayOf()
): Pair<TestableNetworkAgent, TestableNetworkCallback> {
val callback = TestableNetworkCallback()
// Ensure this NetworkAgent is never unneeded by filing a request with its specifier.
requestNetwork(makeTestNetworkRequest(specifier = specifier), callback)
- val agent = createNetworkAgent(context, specifier, initialConfig = initialConfig)
+ val nc = makeTestNetworkCapabilities(specifier, transports)
+ val agent = createNetworkAgent(context, initialConfig = initialConfig, initialNc = nc)
agent.setTeardownDelayMillis(0)
// Connect the agent and verify initial status callbacks.
agent.register()
@@ -301,6 +319,15 @@
return agent to callback
}
+ private fun connectNetwork(vararg transports: Int): Pair<TestableNetworkAgent, Network> {
+ val (agent, callback) = createConnectedNetworkAgent(transports = transports)
+ val network = agent.network!!
+ // createConnectedNetworkAgent internally files a request; release it so that the network
+ // will be torn down if unneeded.
+ mCM.unregisterNetworkCallback(callback)
+ return agent to network
+ }
+
private fun createNetworkAgentWithFakeCS() = createNetworkAgent().also {
mFakeConnectivityService.connect(it.registerForTest(Network(FAKE_NET_ID)))
}
@@ -1123,4 +1150,138 @@
remoteAddresses
)
}
+
+ @Test
+ fun testDestroyAndAwaitReplacement() {
+ // Keeps an eye on all test networks.
+ val matchAllCallback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS)
+ registerNetworkCallback(makeTestNetworkRequest(), matchAllCallback)
+
+ // File a request that matches and keeps up the best-scoring test network.
+ val testCallback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS)
+ requestNetwork(makeTestNetworkRequest(), testCallback)
+
+ // Connect the first network. This should satisfy the request.
+ val (agent1, network1) = connectNetwork()
+ matchAllCallback.expectAvailableThenValidatedCallbacks(network1)
+ testCallback.expectAvailableThenValidatedCallbacks(network1)
+ // Check that network1 exists by binding a socket to it and getting no exceptions.
+ network1.bindSocket(DatagramSocket())
+
+ // Connect a second agent. network1 is preferred because it was already registered, so
+ // testCallback will not see any events. agent2 is be torn down because it has no requests.
+ val (agent2, network2) = connectNetwork()
+ matchAllCallback.expectAvailableThenValidatedCallbacks(network2)
+ matchAllCallback.expectCallback<Lost>(network2)
+ agent2.expectCallback<OnNetworkUnwanted>()
+ agent2.expectCallback<OnNetworkDestroyed>()
+ assertNull(mCM.getLinkProperties(network2))
+
+ // Mark the first network as awaiting replacement. This should destroy the underlying
+ // native network and send onNetworkDestroyed, but will not send any NetworkCallbacks,
+ // because for callback and scoring purposes network1 is still connected.
+ agent1.destroyAndAwaitReplacement(5_000 /* timeoutMillis */)
+ agent1.expectCallback<OnNetworkDestroyed>()
+ assertThrows(IOException::class.java) { network1.bindSocket(DatagramSocket()) }
+ assertNotNull(mCM.getLinkProperties(network1))
+
+ // Calling destroyAndAwaitReplacement more than once has no effect.
+ // If it did, this test would fail because the 1ms timeout means that the network would be
+ // torn down before the replacement arrives.
+ agent1.destroyAndAwaitReplacement(1 /* timeoutMillis */)
+
+ // Connect a third network. Because network1 is awaiting replacement, network3 is preferred
+ // as soon as it validates (until then, it is outscored by network1).
+ // The fact that the first events seen by matchAllCallback is the connection of network3
+ // implicitly ensures that no callbacks are sent since network1 was lost.
+ val (agent3, network3) = connectNetwork()
+ matchAllCallback.expectAvailableThenValidatedCallbacks(network3)
+ testCallback.expectAvailableDoubleValidatedCallbacks(network3)
+
+ // As soon as the replacement arrives, network1 is disconnected.
+ // Check that this happens before the replacement timeout (5 seconds) fires.
+ matchAllCallback.expectCallback<Lost>(network1, 2_000 /* timeoutMs */)
+ agent1.expectCallback<OnNetworkUnwanted>()
+
+ // Test lingering:
+ // - Connect a higher-scoring network and check that network3 starts lingering.
+ // - Mark network3 awaiting replacement.
+ // - Check that network3 is torn down immediately without waiting for the linger timer or
+ // the replacement timer to fire. This is a regular teardown, so it results in
+ // onNetworkUnwanted before onNetworkDestroyed.
+ val (agent4, agent4callback) = createConnectedNetworkAgent()
+ val network4 = agent4.network!!
+ matchAllCallback.expectAvailableThenValidatedCallbacks(network4)
+ agent4.sendNetworkScore(NetworkScore.Builder().setTransportPrimary(true).build())
+ matchAllCallback.expectCallback<Losing>(network3)
+ testCallback.expectAvailableCallbacks(network4, validated = true)
+ mCM.unregisterNetworkCallback(agent4callback)
+ agent3.destroyAndAwaitReplacement(5_000)
+ agent3.expectCallback<OnNetworkUnwanted>()
+ matchAllCallback.expectCallback<Lost>(network3, 1000L)
+ agent3.expectCallback<OnNetworkDestroyed>()
+
+ // Now mark network4 awaiting replacement with a low timeout, and check that if no
+ // replacement arrives, it is torn down.
+ agent4.destroyAndAwaitReplacement(100 /* timeoutMillis */)
+ matchAllCallback.expectCallback<Lost>(network4, 1000L /* timeoutMs */)
+ testCallback.expectCallback<Lost>(network4, 1000L /* timeoutMs */)
+ agent4.expectCallback<OnNetworkDestroyed>()
+ agent4.expectCallback<OnNetworkUnwanted>()
+
+ // If a network that is awaiting replacement is unregistered, it disconnects immediately,
+ // before the replacement timeout fires.
+ val (agent5, network5) = connectNetwork()
+ matchAllCallback.expectAvailableThenValidatedCallbacks(network5)
+ testCallback.expectAvailableThenValidatedCallbacks(network5)
+ agent5.destroyAndAwaitReplacement(5_000 /* timeoutMillis */)
+ agent5.unregister()
+ matchAllCallback.expectCallback<Lost>(network5, 1000L /* timeoutMs */)
+ testCallback.expectCallback<Lost>(network5, 1000L /* timeoutMs */)
+ agent5.expectCallback<OnNetworkDestroyed>()
+ agent5.expectCallback<OnNetworkUnwanted>()
+
+ // If wifi is replaced within the timeout, the device does not switch to cellular.
+ val (cellAgent, cellNetwork) = connectNetwork(TRANSPORT_CELLULAR)
+ testCallback.expectAvailableThenValidatedCallbacks(cellNetwork)
+ matchAllCallback.expectAvailableThenValidatedCallbacks(cellNetwork)
+
+ val (wifiAgent, wifiNetwork) = connectNetwork(TRANSPORT_WIFI)
+ testCallback.expectAvailableCallbacks(wifiNetwork, validated = true)
+ testCallback.expectCapabilitiesThat(wifiNetwork) {
+ it.hasCapability(NET_CAPABILITY_VALIDATED)
+ }
+ matchAllCallback.expectAvailableCallbacks(wifiNetwork, validated = false)
+ matchAllCallback.expectCallback<Losing>(cellNetwork)
+ matchAllCallback.expectCapabilitiesThat(wifiNetwork) {
+ it.hasCapability(NET_CAPABILITY_VALIDATED)
+ }
+
+ wifiAgent.destroyAndAwaitReplacement(5_000 /* timeoutMillis */)
+ wifiAgent.expectCallback<OnNetworkDestroyed>()
+
+ // Once the network is awaiting replacement, changing LinkProperties, NetworkCapabilities or
+ // score, or calling reportNetworkConnectivity, have no effect.
+ val wifiSpecifier = mCM.getNetworkCapabilities(wifiNetwork)!!.networkSpecifier
+ assertNotNull(wifiSpecifier)
+ assertTrue(wifiSpecifier is EthernetNetworkSpecifier)
+
+ val wifiNc = makeTestNetworkCapabilities(wifiSpecifier.interfaceName,
+ intArrayOf(TRANSPORT_WIFI))
+ wifiAgent.sendNetworkCapabilities(wifiNc)
+ val wifiLp = mCM.getLinkProperties(wifiNetwork)!!
+ val newRoute = RouteInfo(IpPrefix("192.0.2.42/24"))
+ assertFalse(wifiLp.getRoutes().contains(newRoute))
+ wifiLp.addRoute(newRoute)
+ wifiAgent.sendLinkProperties(wifiLp)
+ mCM.reportNetworkConnectivity(wifiNetwork, false)
+ // The test implicitly checks that no callbacks are sent here, because the next events seen
+ // by the callbacks are for the new network connecting.
+
+ val (newWifiAgent, newWifiNetwork) = connectNetwork(TRANSPORT_WIFI)
+ testCallback.expectAvailableCallbacks(newWifiNetwork, validated = true)
+ matchAllCallback.expectAvailableThenValidatedCallbacks(newWifiNetwork)
+ matchAllCallback.expectCallback<Lost>(wifiNetwork)
+ wifiAgent.expectCallback<OnNetworkUnwanted>()
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
index 147fca9..fb720a7 100644
--- a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
@@ -21,6 +21,9 @@
import static android.app.usage.NetworkStats.Bucket.METERED_ALL;
import static android.app.usage.NetworkStats.Bucket.METERED_NO;
import static android.app.usage.NetworkStats.Bucket.METERED_YES;
+import static android.app.usage.NetworkStats.Bucket.ROAMING_ALL;
+import static android.app.usage.NetworkStats.Bucket.ROAMING_NO;
+import static android.app.usage.NetworkStats.Bucket.ROAMING_YES;
import static android.app.usage.NetworkStats.Bucket.STATE_ALL;
import static android.app.usage.NetworkStats.Bucket.STATE_DEFAULT;
import static android.app.usage.NetworkStats.Bucket.STATE_FOREGROUND;
@@ -77,6 +80,7 @@
private abstract class NetworkInterfaceToTest {
private boolean mMetered;
+ private boolean mRoaming;
private boolean mIsDefault;
abstract int getNetworkType();
@@ -90,6 +94,14 @@
this.mMetered = metered;
}
+ public boolean getRoaming() {
+ return mRoaming;
+ }
+
+ public void setRoaming(boolean roaming) {
+ this.mRoaming = roaming;
+ }
+
public boolean getIsDefault() {
return mIsDefault;
}
@@ -267,6 +279,7 @@
private URL mUrl;
public boolean success;
public boolean metered;
+ public boolean roaming;
public boolean isDefault;
NetworkCallback(long tolerance, URL url) {
@@ -274,6 +287,7 @@
mUrl = url;
success = false;
metered = false;
+ roaming = false;
isDefault = false;
}
@@ -303,6 +317,8 @@
success = true;
metered = !mCm.getNetworkCapabilities(network)
.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+ roaming = !mCm.getNetworkCapabilities(network)
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
synchronized (NetworkStatsManagerTest.this) {
NetworkStatsManagerTest.this.notify();
}
@@ -333,6 +349,7 @@
}
if (callback.success) {
mNetworkInterfacesToTest[networkTypeIndex].setMetered(callback.metered);
+ mNetworkInterfacesToTest[networkTypeIndex].setRoaming(callback.roaming);
mNetworkInterfacesToTest[networkTypeIndex].setIsDefault(callback.isDefault);
return true;
}
@@ -377,6 +394,7 @@
assertEquals(bucket.getState(), STATE_ALL);
assertEquals(bucket.getUid(), UID_ALL);
assertEquals(bucket.getMetered(), METERED_ALL);
+ assertEquals(bucket.getRoaming(), ROAMING_ALL);
assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
try {
@@ -412,6 +430,7 @@
assertEquals(bucket.getState(), STATE_ALL);
assertEquals(bucket.getUid(), UID_ALL);
assertEquals(bucket.getMetered(), METERED_ALL);
+ assertEquals(bucket.getRoaming(), ROAMING_ALL);
assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
try {
@@ -451,15 +470,19 @@
long totalTxBytes = 0;
long totalRxBytes = 0;
boolean hasCorrectMetering = false;
+ boolean hasCorrectRoaming = false;
boolean hasCorrectDefaultStatus = false;
int expectedMetering = mNetworkInterfacesToTest[i].getMetered()
? METERED_YES : METERED_NO;
+ int expectedRoaming = mNetworkInterfacesToTest[i].getRoaming()
+ ? ROAMING_YES : ROAMING_NO;
int expectedDefaultStatus = mNetworkInterfacesToTest[i].getIsDefault()
? DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO;
while (result.hasNextBucket()) {
assertTrue(result.getNextBucket(bucket));
assertTimestamps(bucket);
hasCorrectMetering |= bucket.getMetered() == expectedMetering;
+ hasCorrectRoaming |= bucket.getRoaming() == expectedRoaming;
if (bucket.getUid() == Process.myUid()) {
totalTxPackets += bucket.getTxPackets();
totalRxPackets += bucket.getRxPackets();
@@ -472,6 +495,8 @@
assertFalse(result.getNextBucket(bucket));
assertTrue("Incorrect metering for NetworkType: "
+ mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectMetering);
+ assertTrue("Incorrect roaming for NetworkType: "
+ + mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectRoaming);
assertTrue("Incorrect isDefault for NetworkType: "
+ mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectDefaultStatus);
assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0);
@@ -564,6 +589,7 @@
assertTimestamps(bucket);
assertEquals(bucket.getState(), STATE_ALL);
assertEquals(bucket.getMetered(), METERED_ALL);
+ assertEquals(bucket.getRoaming(), ROAMING_ALL);
assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
assertEquals(bucket.getUid(), Process.myUid());
totalTxPackets += bucket.getTxPackets();
@@ -617,6 +643,7 @@
assertTimestamps(bucket);
assertEquals(bucket.getState(), STATE_ALL);
assertEquals(bucket.getMetered(), METERED_ALL);
+ assertEquals(bucket.getRoaming(), ROAMING_ALL);
assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
assertEquals(bucket.getUid(), Process.myUid());
if (bucket.getTag() == NETWORK_TAG) {
@@ -838,6 +865,7 @@
if (expectedTag != null) assertEquals(bucket.getTag(), (int) expectedTag);
if (expectedState != null) assertEquals(bucket.getState(), (int) expectedState);
assertEquals(bucket.getMetered(), METERED_ALL);
+ assertEquals(bucket.getRoaming(), ROAMING_ALL);
assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
if (bucket.getUid() == Process.myUid()) {
totalTxPackets += bucket.getTxPackets();
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 9313ac2..07dcae3 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -55,6 +55,7 @@
name: "non-connectivity-module-test",
srcs: [
"java/android/app/usage/*.java",
+ "java/android/net/EthernetNetworkUpdateRequestTest.java",
"java/android/net/Ikev2VpnProfileTest.java",
"java/android/net/IpMemoryStoreTest.java",
"java/android/net/IpSecAlgorithmTest.java",
diff --git a/tests/unit/java/android/net/EthernetNetworkUpdateRequestTest.java b/tests/unit/java/android/net/EthernetNetworkUpdateRequestTest.java
new file mode 100644
index 0000000..314fbcf
--- /dev/null
+++ b/tests/unit/java/android/net/EthernetNetworkUpdateRequestTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+public class EthernetNetworkUpdateRequestTest {
+ private IpConfiguration buildIpConfiguration() {
+ return new IpConfiguration.Builder().setHttpProxy(
+ new ProxyInfo("test.example.com", 1234, "")).build();
+ }
+
+ private NetworkCapabilities buildNetworkCapabilities() {
+ return new NetworkCapabilities.Builder().addTransportType(
+ NetworkCapabilities.TRANSPORT_ETHERNET).build();
+ }
+
+ @Test
+ public void testParcelUnparcel() {
+ EthernetNetworkUpdateRequest reqWithNonNull =
+ new EthernetNetworkUpdateRequest.Builder().setIpConfiguration(
+ buildIpConfiguration()).setNetworkCapabilities(
+ buildNetworkCapabilities()).build();
+ EthernetNetworkUpdateRequest reqWithNullCaps =
+ new EthernetNetworkUpdateRequest.Builder().setIpConfiguration(
+ buildIpConfiguration()).build();
+
+ assertParcelSane(reqWithNonNull, 2);
+ assertParcelSane(reqWithNullCaps, 2);
+ }
+}
diff --git a/tests/unit/java/com/android/server/NetworkManagementServiceTest.java b/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
index a3b0e7c..7688a6b 100644
--- a/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
+++ b/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
@@ -250,7 +250,7 @@
mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, true);
assertTrue("Should be true since mobile data usage is restricted",
mNMService.isNetworkRestricted(TEST_UID));
- verify(mCm).updateMeteredNetworkDenyList(TEST_UID, true /* enabled */);
+ verify(mCm).addUidToMeteredNetworkDenyList(TEST_UID);
mNMService.setDataSaverModeEnabled(true);
verify(mNetdService).bandwidthEnableDataSaver(true);
@@ -258,16 +258,16 @@
mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, false);
assertTrue("Should be true since data saver is on and the uid is not allowlisted",
mNMService.isNetworkRestricted(TEST_UID));
- verify(mCm).updateMeteredNetworkDenyList(TEST_UID, true /* false */);
+ verify(mCm).removeUidFromMeteredNetworkDenyList(TEST_UID);
mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, true);
assertFalse("Should be false since data saver is on and the uid is allowlisted",
mNMService.isNetworkRestricted(TEST_UID));
- verify(mCm).updateMeteredNetworkAllowList(TEST_UID, true /* enabled */);
+ verify(mCm).addUidToMeteredNetworkAllowList(TEST_UID);
// remove uid from allowlist and turn datasaver off again
mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, false);
- verify(mCm).updateMeteredNetworkAllowList(TEST_UID, false /* enabled */);
+ verify(mCm).removeUidFromMeteredNetworkAllowList(TEST_UID);
mNMService.setDataSaverModeEnabled(false);
verify(mNetdService).bandwidthEnableDataSaver(false);
assertFalse("Network should not be restricted when data saver is off",
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index aa4e4bb..ceeb997 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -517,10 +517,10 @@
.insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
.insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L));
- mService.setUidForeground(UID_RED, false);
+ mService.noteUidForeground(UID_RED, false);
verify(mUidCounterSetMap, never()).deleteEntry(any());
mService.incrementOperationCount(UID_RED, 0xFAAD, 4);
- mService.setUidForeground(UID_RED, true);
+ mService.noteUidForeground(UID_RED, true);
verify(mUidCounterSetMap).updateEntry(
eq(new U32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
mService.incrementOperationCount(UID_RED, 0xFAAD, 6);
@@ -1118,7 +1118,7 @@
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L));
- mService.setUidForeground(UID_RED, true);
+ mService.noteUidForeground(UID_RED, true);
verify(mUidCounterSetMap).updateEntry(
eq(new U32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
mService.incrementOperationCount(UID_RED, 0xFAAD, 1);