Mark 24Q3 (ab/AP3A.240905.015) as merged
Bug: 347831320
Merged-In: I651dae8c9daac24b195196c03b54e69a757de8a7
Change-Id: I4464f4ee2de3ebda5a67cfd885b50cbcb92a02ad
diff --git a/OWNERS_core_networking_xts b/OWNERS_core_networking_xts
index b24e3ac..9e4e4a1 100644
--- a/OWNERS_core_networking_xts
+++ b/OWNERS_core_networking_xts
@@ -1,11 +1,12 @@
lorenzo@google.com
satk@google.com #{LAST_RESORT_SUGGESTION}
-# For cherry-picks of CLs that are already merged in aosp/master, or flaky test fixes.
+# For cherry-picks of CLs that are already merged in aosp/master, flaky test
+# fixes, or no-op refactors.
jchalard@google.com #{LAST_RESORT_SUGGESTION}
-# In addition to cherry-picks and flaky test fixes, also for APF firmware tests
-# (to verify correct behaviour of the wifi APF interpreter)
+# In addition to cherry-picks, flaky test fixes and no-op refactors, also for
+# APF firmware tests (to verify correct behaviour of the wifi APF interpreter)
maze@google.com #{LAST_RESORT_SUGGESTION}
-# In addition to cherry-picks and flaky test fixes, also for incremental changes on NsdManager tests
-# to increase coverage for existing behavior, and testing of bug fixes in NsdManager
+# In addition to cherry-picks, flaky test fixes and no-op refactors, also for
+# NsdManager tests
reminv@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 83619d6..39009cb 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,6 +1,15 @@
+[Builtin Hooks]
+bpfmt = true
+clang_format = true
+ktfmt = true
+
+[Builtin Hooks Options]
+clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp,hpp
+ktfmt = --kotlinlang-style
+
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
-ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
+ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES}
hidden_api_txt_checksorted_hook = ${REPO_ROOT}/tools/platform-compat/hiddenapi/checksorted_sha.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 4cf93a8..bcf5e8b 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -1,7 +1,7 @@
{
"captiveportal-networkstack-resolve-tethering-mainline-presubmit": [
{
- "name": "CtsNetTestCasesLatestSdk",
+ "name": "CtsNetTestCases",
"options": [
{
"exclude-annotation": "com.android.testutils.SkipPresubmit"
@@ -89,7 +89,7 @@
// Test with APK modules only, in cases where APEX is not supported, or the other modules
// were simply not updated
{
- "name": "CtsNetTestCasesLatestSdk",
+ "name": "CtsNetTestCases",
"options": [
{
"exclude-annotation": "com.android.testutils.SkipPresubmit"
@@ -115,7 +115,7 @@
// really exist in the field, but there is no strong guarantee, and it is required by MTS
// testing for module qualification, where modules are tested independently.
{
- "name": "CtsNetTestCasesLatestSdk",
+ "name": "CtsNetTestCases",
"options": [
{
"exclude-annotation": "com.android.testutils.SkipPresubmit"
@@ -158,8 +158,6 @@
},
// Run in addition to mainline-presubmit as mainline-presubmit is not
// supported in every branch.
- // CtsNetTestCasesLatestSdk uses stable API shims, so does not exercise
- // some latest APIs. Run CtsNetTestCases to get coverage of newer APIs.
{
"name": "CtsNetTestCases",
"options": [
@@ -171,18 +169,6 @@
}
]
},
- // Also run CtsNetTestCasesLatestSdk to ensure tests using older shims pass.
- {
- "name": "CtsNetTestCasesLatestSdk",
- "options": [
- {
- "exclude-annotation": "com.android.testutils.SkipPresubmit"
- },
- {
- "exclude-annotation": "androidx.test.filters.RequiresDevice"
- }
- ]
- },
// CTS tests that target older SDKs.
{
"name": "CtsNetTestCasesMaxTargetSdk30",
@@ -267,11 +253,15 @@
},
{
"name": "FrameworksNetTests"
+ },
+ // TODO: Move to presumit after meet SLO requirement.
+ {
+ "name": "NetworkStaticLibHostPythonTests"
}
],
"mainline-presubmit": [
{
- "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+ "name": "CtsNetTestCases[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
"options": [
{
"exclude-annotation": "com.android.testutils.SkipPresubmit"
@@ -329,7 +319,7 @@
// Test with APK modules only, in cases where APEX is not supported, or the other modules
// were simply not updated
{
- "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk]",
+ "name": "CtsNetTestCases[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk]",
"options": [
{
"exclude-annotation": "com.android.testutils.SkipPresubmit"
@@ -353,7 +343,7 @@
// really exist in the field, but there is no strong guarantee, and it is required by MTS
// testing for module qualification, where modules are tested independently.
{
- "name": "CtsNetTestCasesLatestSdk[com.google.android.tethering.apex]",
+ "name": "CtsNetTestCases[com.google.android.tethering.apex]",
"options": [
{
"exclude-annotation": "com.android.testutils.SkipPresubmit"
@@ -404,7 +394,7 @@
"mainline-postsubmit": [
// Tests on physical devices with SIM cards: postsubmit only for capacity constraints
{
- "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+ "name": "CtsNetTestCases[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
"keywords": ["sim"]
},
{
@@ -418,7 +408,7 @@
},
// Postsubmit on virtual devices to monitor flakiness of @SkipMainlinePresubmit methods
{
- "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+ "name": "CtsNetTestCases[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
"options": [
{
"exclude-annotation": "androidx.test.filters.RequiresDevice"
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index e84573b..d04660d 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -69,13 +69,7 @@
"android.hardware.tetheroffload.control-V1.0-java",
"android.hardware.tetheroffload.control-V1.1-java",
"android.hidl.manager-V1.2-java",
- "net-utils-framework-common",
- "net-utils-device-common",
- "net-utils-device-common-bpf",
- "net-utils-device-common-ip",
- "net-utils-device-common-netlink",
- "net-utils-device-common-struct",
- "net-utils-device-common-struct-base",
+ "net-utils-connectivity-apks",
"netd-client",
"tetheringstatsprotos",
],
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 8a5d249..8ed5ac0 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -101,7 +101,6 @@
"block.o",
"clatd.o",
"dscpPolicy.o",
- "gentle.o",
"netd.o",
"offload.o",
"offload@mainline.o",
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index 9fa073b..39a7540 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -58,6 +58,7 @@
permitted_packages: ["android.net"],
lint: {
strict_updatability_linting: true,
+ baseline_filename: "lint-baseline.xml",
},
aconfig_declarations: [
"com.android.net.flags-aconfig",
diff --git a/Tethering/common/TetheringLib/api/system-current.txt b/Tethering/common/TetheringLib/api/system-current.txt
index a287b42..cccafd5 100644
--- a/Tethering/common/TetheringLib/api/system-current.txt
+++ b/Tethering/common/TetheringLib/api/system-current.txt
@@ -47,6 +47,7 @@
field public static final int TETHERING_INVALID = -1; // 0xffffffff
field public static final int TETHERING_NCM = 4; // 0x4
field public static final int TETHERING_USB = 1; // 0x1
+ field @FlaggedApi("com.android.net.flags.tethering_request_virtual") public static final int TETHERING_VIRTUAL = 7; // 0x7
field public static final int TETHERING_WIFI = 0; // 0x0
field public static final int TETHERING_WIFI_P2P = 3; // 0x3
field public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; // 0xc
diff --git a/Tethering/common/TetheringLib/lint-baseline.xml b/Tethering/common/TetheringLib/lint-baseline.xml
new file mode 100644
index 0000000..5171efb
--- /dev/null
+++ b/Tethering/common/TetheringLib/lint-baseline.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha08" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha08">
+
+ <issue
+ id="FlaggedApi"
+ message="Field `TETHERING_VIRTUAL` is a flagged API and should be inside an `if (Flags.tetheringRequestVirtual())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.TETHERING_REQUEST_VIRTUAL) to transfer requirement to caller`)"
+ errorLine1=" public static final int MAX_TETHERING_TYPE = TETHERING_VIRTUAL;"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/Tethering/common/TetheringLib/src/android/net/TetheringManager.java"
+ line="211"
+ column="50"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `TetheringRequest()` is a flagged API and should be inside an `if (Flags.tetheringRequestWithSoftApConfig())` check (or annotate the surrounding method `build` with `@FlaggedApi(Flags.TETHERING_REQUEST_WITH_SOFT_AP_CONFIG) to transfer requirement to caller`)"
+ errorLine1=" return new TetheringRequest(mBuilderParcel);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/Tethering/common/TetheringLib/src/android/net/TetheringManager.java"
+ line="814"
+ column="24"/>
+ </issue>
+
+</issues>
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 7b769d4..0f5a014 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -68,6 +68,8 @@
public static class Flags {
static final String TETHERING_REQUEST_WITH_SOFT_AP_CONFIG =
"com.android.net.flags.tethering_request_with_soft_ap_config";
+ static final String TETHERING_REQUEST_VIRTUAL =
+ "com.android.net.flags.tethering_request_virtual";
}
private static final String TAG = TetheringManager.class.getSimpleName();
@@ -195,10 +197,22 @@
public static final int TETHERING_WIGIG = 6;
/**
+ * VIRTUAL tethering type.
+ *
+ * This tethering type is for providing external network to virtual machines
+ * running on top of Android devices, which are created and managed by
+ * AVF(Android Virtualization Framework).
+ * @hide
+ */
+ @FlaggedApi(Flags.TETHERING_REQUEST_VIRTUAL)
+ @SystemApi
+ public static final int TETHERING_VIRTUAL = 7;
+
+ /**
* The int value of last tethering type.
* @hide
*/
- public static final int MAX_TETHERING_TYPE = TETHERING_WIGIG;
+ public static final int MAX_TETHERING_TYPE = TETHERING_VIRTUAL;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -688,7 +702,11 @@
/** A configuration set for TetheringRequest. */
private final TetheringRequestParcel mRequestParcel;
- private TetheringRequest(@NonNull final TetheringRequestParcel request) {
+ /**
+ * @hide
+ */
+ @FlaggedApi(Flags.TETHERING_REQUEST_WITH_SOFT_AP_CONFIG)
+ public TetheringRequest(@NonNull final TetheringRequestParcel request) {
mRequestParcel = request;
}
@@ -891,6 +909,28 @@
+ mRequestParcel.exemptFromEntitlementCheck + ", showProvisioningUi= "
+ mRequestParcel.showProvisioningUi + " ]";
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (!(obj instanceof TetheringRequest otherRequest)) return false;
+ TetheringRequestParcel parcel = getParcel();
+ TetheringRequestParcel otherParcel = otherRequest.getParcel();
+ return parcel.tetheringType == otherParcel.tetheringType
+ && Objects.equals(parcel.localIPv4Address, otherParcel.localIPv4Address)
+ && Objects.equals(parcel.staticClientAddress, otherParcel.staticClientAddress)
+ && parcel.exemptFromEntitlementCheck == otherParcel.exemptFromEntitlementCheck
+ && parcel.showProvisioningUi == otherParcel.showProvisioningUi
+ && parcel.connectivityScope == otherParcel.connectivityScope;
+ }
+
+ @Override
+ public int hashCode() {
+ TetheringRequestParcel parcel = getParcel();
+ return Objects.hash(parcel.tetheringType, parcel.localIPv4Address,
+ parcel.staticClientAddress, parcel.exemptFromEntitlementCheck,
+ parcel.showProvisioningUi, parcel.connectivityScope);
+ }
}
/**
@@ -1343,6 +1383,9 @@
@RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE)
public void registerTetheringEventCallback(@NonNull Executor executor,
@NonNull TetheringEventCallback callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "registerTetheringEventCallback caller:" + callerPkg);
@@ -1497,6 +1540,8 @@
Manifest.permission.ACCESS_NETWORK_STATE
})
public void unregisterTetheringEventCallback(@NonNull final TetheringEventCallback callback) {
+ Objects.requireNonNull(callback);
+
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "unregisterTetheringEventCallback caller:" + callerPkg);
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 9e0c970..b807544 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -34,7 +34,6 @@
import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH;
import static com.android.networkstack.tethering.TetheringConfiguration.USE_SYNC_SM;
-import static com.android.networkstack.tethering.UpstreamNetworkState.isVcnInterface;
import static com.android.networkstack.tethering.util.PrefixUtils.asIpPrefix;
import static com.android.networkstack.tethering.util.TetheringMessageBase.BASE_IPSERVER;
@@ -45,10 +44,9 @@
import android.net.LinkProperties;
import android.net.MacAddress;
import android.net.RouteInfo;
-import android.net.RoutingCoordinatorManager;
import android.net.TetheredClient;
import android.net.TetheringManager;
-import android.net.TetheringRequestParcel;
+import android.net.TetheringManager.TetheringRequest;
import android.net.dhcp.DhcpLeaseParcelable;
import android.net.dhcp.DhcpServerCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
@@ -73,21 +71,17 @@
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.NetdUtils;
-import com.android.net.module.util.SdkUtil.LateSdk;
+import com.android.net.module.util.RoutingCoordinatorManager;
import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.SyncStateMachine.StateInfo;
import com.android.net.module.util.ip.InterfaceController;
-import com.android.net.module.util.ip.IpNeighborMonitor;
-import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEvent;
import com.android.networkstack.tethering.BpfCoordinator;
-import com.android.networkstack.tethering.BpfCoordinator.ClientInfo;
-import com.android.networkstack.tethering.BpfCoordinator.Ipv6DownstreamRule;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
import com.android.networkstack.tethering.TetheringConfiguration;
import com.android.networkstack.tethering.metrics.TetheringMetrics;
import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.networkstack.tethering.util.PrefixUtils;
import com.android.networkstack.tethering.util.StateMachineShim;
-import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo;
import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -189,12 +183,6 @@
return new DadProxy(handler, ifParams);
}
- /** Create an IpNeighborMonitor to be used by this IpServer */
- public IpNeighborMonitor getIpNeighborMonitor(Handler handler, SharedLog log,
- IpNeighborMonitor.NeighborEventConsumer consumer) {
- return new IpNeighborMonitor(handler, log, consumer);
- }
-
/** Create a RouterAdvertisementDaemon instance to be used by IpServer.*/
public RouterAdvertisementDaemon getRouterAdvertisementDaemon(InterfaceParams ifParams) {
return new RouterAdvertisementDaemon(ifParams);
@@ -234,13 +222,11 @@
public static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IPSERVER + 9;
// new IPv6 tethering parameters need to be processed
public static final int CMD_IPV6_TETHER_UPDATE = BASE_IPSERVER + 10;
- // new neighbor cache entry on our interface
- public static final int CMD_NEIGHBOR_EVENT = BASE_IPSERVER + 11;
// request from DHCP server that it wants to have a new prefix
- public static final int CMD_NEW_PREFIX_REQUEST = BASE_IPSERVER + 12;
+ public static final int CMD_NEW_PREFIX_REQUEST = BASE_IPSERVER + 11;
// request from PrivateAddressCoordinator to restart tethering.
- public static final int CMD_NOTIFY_PREFIX_CONFLICT = BASE_IPSERVER + 13;
- public static final int CMD_SERVICE_FAILED_TO_START = BASE_IPSERVER + 14;
+ public static final int CMD_NOTIFY_PREFIX_CONFLICT = BASE_IPSERVER + 12;
+ public static final int CMD_SERVICE_FAILED_TO_START = BASE_IPSERVER + 13;
private final State mInitialState;
private final State mLocalHotspotState;
@@ -252,11 +238,8 @@
private final INetd mNetd;
@NonNull
private final BpfCoordinator mBpfCoordinator;
- // Contains null if the connectivity module is unsupported, as the routing coordinator is not
- // available. Must use LateSdk because MessageUtils enumerates fields in this class, so it
- // must be able to find all classes at runtime.
@NonNull
- private final LateSdk<RoutingCoordinatorManager> mRoutingCoordinator;
+ private final RoutingCoordinatorManager mRoutingCoordinator;
private final Callback mCallback;
private final InterfaceController mInterfaceCtrl;
private final PrivateAddressCoordinator mPrivateAddressCoordinator;
@@ -301,18 +284,9 @@
private List<TetheredClient> mDhcpLeases = Collections.emptyList();
private int mLastIPv6UpstreamIfindex = 0;
- private boolean mUpstreamSupportsBpf = false;
@NonNull
private Set<IpPrefix> mLastIPv6UpstreamPrefixes = Collections.emptySet();
- private class MyNeighborEventConsumer implements IpNeighborMonitor.NeighborEventConsumer {
- public void accept(NeighborEvent e) {
- sendMessage(CMD_NEIGHBOR_EVENT, e);
- }
- }
-
- private final IpNeighborMonitor mIpNeighborMonitor;
-
private LinkAddress mIpv4Address;
private final TetheringMetrics mTetheringMetrics;
@@ -323,7 +297,7 @@
public IpServer(
String ifaceName, Handler handler, int interfaceType, SharedLog log,
INetd netd, @NonNull BpfCoordinator bpfCoordinator,
- @Nullable LateSdk<RoutingCoordinatorManager> routingCoordinator, Callback callback,
+ RoutingCoordinatorManager routingCoordinatorManager, Callback callback,
TetheringConfiguration config, PrivateAddressCoordinator addressCoordinator,
TetheringMetrics tetheringMetrics, Dependencies deps) {
super(ifaceName, USE_SYNC_SM ? null : handler.getLooper());
@@ -331,7 +305,7 @@
mLog = log.forSubComponent(ifaceName);
mNetd = netd;
mBpfCoordinator = bpfCoordinator;
- mRoutingCoordinator = routingCoordinator;
+ mRoutingCoordinator = routingCoordinatorManager;
mCallback = callback;
mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog);
mIfaceName = ifaceName;
@@ -346,15 +320,6 @@
mLastError = TETHER_ERROR_NO_ERROR;
mServingMode = STATE_AVAILABLE;
- mIpNeighborMonitor = mDeps.getIpNeighborMonitor(getHandler(), mLog,
- new MyNeighborEventConsumer());
-
- // IP neighbor monitor monitors the neighbor events for adding/removing IPv6 downstream rule
- // per client. If BPF offload is not supported, don't start listening for neighbor events.
- if (mBpfCoordinator.isUsingBpfOffload() && !mIpNeighborMonitor.start()) {
- mLog.e("Failed to create IpNeighborMonitor on " + mIfaceName);
- }
-
mInitialState = new InitialState();
mLocalHotspotState = new LocalHotspotState();
mTetheredState = new TetheredState();
@@ -410,6 +375,22 @@
return mIpv4Address;
}
+ /** The IPv6 upstream interface index */
+ public int getIpv6UpstreamIfindex() {
+ return mLastIPv6UpstreamIfindex;
+ }
+
+ /** The IPv6 upstream interface prefixes */
+ @NonNull
+ public Set<IpPrefix> getIpv6UpstreamPrefixes() {
+ return Collections.unmodifiableSet(mLastIPv6UpstreamPrefixes);
+ }
+
+ /** The interface parameters which IpServer is using */
+ public InterfaceParams getInterfaceParams() {
+ return mInterfaceParams;
+ }
+
/**
* Get the latest list of DHCP leases that was reported. Must be called on the IpServer looper
* thread.
@@ -419,7 +400,7 @@
}
/** Enable this IpServer. IpServer state machine will be tethered or localHotspot state. */
- public void enable(final int requestedState, final TetheringRequestParcel request) {
+ public void enable(final int requestedState, final TetheringRequest request) {
sendMessage(CMD_TETHER_REQUESTED, requestedState, 0, request);
}
@@ -813,14 +794,15 @@
setRaParams(params);
// Not support BPF on virtual upstream interface
- final boolean upstreamSupportsBpf = upstreamIface != null && !isVcnInterface(upstreamIface);
final Set<IpPrefix> upstreamPrefixes = params != null ? params.prefixes : Set.of();
- updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, mLastIPv6UpstreamPrefixes,
- upstreamIfIndex, upstreamPrefixes, upstreamSupportsBpf);
+ // mBpfCoordinator#updateIpv6UpstreamInterface must be called before updating
+ // mLastIPv6UpstreamIfindex and mLastIPv6UpstreamPrefixes because BpfCoordinator will call
+ // IpServer#getIpv6UpstreamIfindex and IpServer#getIpv6UpstreamPrefixes to retrieve current
+ // upstream interface index and prefixes when handling upstream changes.
+ mBpfCoordinator.updateIpv6UpstreamInterface(this, upstreamIfIndex, upstreamPrefixes);
mLastIPv6LinkProperties = v6only;
mLastIPv6UpstreamIfindex = upstreamIfIndex;
mLastIPv6UpstreamPrefixes = upstreamPrefixes;
- mUpstreamSupportsBpf = upstreamSupportsBpf;
if (mDadProxy != null) {
mDadProxy.setUpstreamIface(upstreamIfaceParams);
}
@@ -839,47 +821,25 @@
private void addInterfaceToNetwork(final int netId, @NonNull final String ifaceName) {
try {
- if (SdkLevel.isAtLeastS() && null != mRoutingCoordinator.value) {
- // TODO : remove this call in favor of using the LocalNetworkConfiguration
- // correctly, which will let ConnectivityService do it automatically.
- mRoutingCoordinator.value.addInterfaceToNetwork(netId, ifaceName);
- } else {
- mNetd.networkAddInterface(netId, ifaceName);
- }
- } catch (ServiceSpecificException | RemoteException e) {
+ // TODO : remove this call in favor of using the LocalNetworkConfiguration
+ // correctly, which will let ConnectivityService do it automatically.
+ mRoutingCoordinator.addInterfaceToNetwork(netId, ifaceName);
+ } catch (ServiceSpecificException e) {
mLog.e("Failed to add " + mIfaceName + " to local table: ", e);
}
}
- private void addInterfaceForward(@NonNull final String fromIface,
- @NonNull final String toIface) throws ServiceSpecificException, RemoteException {
- if (SdkLevel.isAtLeastS() && null != mRoutingCoordinator.value) {
- mRoutingCoordinator.value.addInterfaceForward(fromIface, toIface);
- } else {
- mNetd.tetherAddForward(fromIface, toIface);
- mNetd.ipfwdAddInterfaceForward(fromIface, toIface);
- }
+ private void addInterfaceForward(@NonNull final String fromIface, @NonNull final String toIface)
+ throws ServiceSpecificException {
+ mRoutingCoordinator.addInterfaceForward(fromIface, toIface);
}
private void removeInterfaceForward(@NonNull final String fromIface,
@NonNull final String toIface) {
- if (SdkLevel.isAtLeastS() && null != mRoutingCoordinator.value) {
- try {
- mRoutingCoordinator.value.removeInterfaceForward(fromIface, toIface);
- } catch (ServiceSpecificException e) {
- mLog.e("Exception in removeInterfaceForward", e);
- }
- } else {
- try {
- mNetd.ipfwdRemoveInterfaceForward(fromIface, toIface);
- } catch (RemoteException | ServiceSpecificException e) {
- mLog.e("Exception in ipfwdRemoveInterfaceForward", e);
- }
- try {
- mNetd.tetherRemoveForward(fromIface, toIface);
- } catch (RemoteException | ServiceSpecificException e) {
- mLog.e("Exception in disableNat", e);
- }
+ try {
+ mRoutingCoordinator.removeInterfaceForward(fromIface, toIface);
+ } catch (RuntimeException e) {
+ mLog.e("Exception in removeInterfaceForward", e);
}
}
@@ -964,77 +924,6 @@
}
}
- private int getInterfaceIndexForRule(int ifindex, boolean supportsBpf) {
- return supportsBpf ? ifindex : NO_UPSTREAM;
- }
-
- // Handles updates to IPv6 forwarding rules if the upstream or its prefixes change.
- private void updateIpv6ForwardingRules(int prevUpstreamIfindex,
- @NonNull Set<IpPrefix> prevUpstreamPrefixes, int upstreamIfindex,
- @NonNull Set<IpPrefix> upstreamPrefixes, boolean upstreamSupportsBpf) {
- // If the upstream interface has changed, remove all rules and re-add them with the new
- // upstream interface. If upstream is a virtual network, treated as no upstream.
- if (prevUpstreamIfindex != upstreamIfindex
- || !prevUpstreamPrefixes.equals(upstreamPrefixes)) {
- mBpfCoordinator.updateAllIpv6Rules(this, this.mInterfaceParams,
- getInterfaceIndexForRule(upstreamIfindex, upstreamSupportsBpf),
- upstreamPrefixes);
- }
- }
-
- // Handles updates to IPv6 downstream rules if a neighbor event is received.
- private void addOrRemoveIpv6Downstream(NeighborEvent e) {
- // mInterfaceParams must be non-null or the event would not have arrived.
- if (e == null) return;
- if (!(e.ip instanceof Inet6Address) || e.ip.isMulticastAddress()
- || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) {
- return;
- }
-
- // When deleting rules, we still need to pass a non-null MAC, even though it's ignored.
- // Do this here instead of in the Ipv6DownstreamRule constructor to ensure that we
- // never add rules with a null MAC, only delete them.
- MacAddress dstMac = e.isValid() ? e.macAddr : NULL_MAC_ADDRESS;
- Ipv6DownstreamRule rule = new Ipv6DownstreamRule(
- getInterfaceIndexForRule(mLastIPv6UpstreamIfindex, mUpstreamSupportsBpf),
- mInterfaceParams.index, (Inet6Address) e.ip, mInterfaceParams.macAddr, dstMac);
- if (e.isValid()) {
- mBpfCoordinator.addIpv6DownstreamRule(this, rule);
- } else {
- mBpfCoordinator.removeIpv6DownstreamRule(this, rule);
- }
- }
-
- // TODO: consider moving into BpfCoordinator.
- private void updateClientInfoIpv4(NeighborEvent e) {
- if (e == null) return;
- if (!(e.ip instanceof Inet4Address) || e.ip.isMulticastAddress()
- || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) {
- return;
- }
-
- // When deleting clients, IpServer still need to pass a non-null MAC, even though it's
- // ignored. Do this here instead of in the ClientInfo constructor to ensure that
- // IpServer never add clients with a null MAC, only delete them.
- final MacAddress clientMac = e.isValid() ? e.macAddr : NULL_MAC_ADDRESS;
- final ClientInfo clientInfo = new ClientInfo(mInterfaceParams.index,
- mInterfaceParams.macAddr, (Inet4Address) e.ip, clientMac);
- if (e.isValid()) {
- mBpfCoordinator.tetherOffloadClientAdd(this, clientInfo);
- } else {
- mBpfCoordinator.tetherOffloadClientRemove(this, clientInfo);
- }
- }
-
- private void handleNeighborEvent(NeighborEvent e) {
- if (mInterfaceParams != null
- && mInterfaceParams.index == e.ifindex
- && mInterfaceParams.hasMacAddress) {
- addOrRemoveIpv6Downstream(e);
- updateClientInfoIpv4(e);
- }
- }
-
private byte getHopLimit(String upstreamIface, int adjustTTL) {
try {
int upstreamHopLimit = Integer.parseUnsignedInt(
@@ -1069,7 +958,6 @@
switch (what) {
// Suppress some CMD_* to avoid log flooding.
case CMD_IPV6_TETHER_UPDATE:
- case CMD_NEIGHBOR_EVENT:
break;
default:
mLog.log(state.getName() + " got "
@@ -1092,18 +980,18 @@
mLinkProperties.setInterfaceName(mIfaceName);
}
- private void maybeConfigureStaticIp(final TetheringRequestParcel request) {
+ private void maybeConfigureStaticIp(final TetheringRequest request) {
// Ignore static address configuration if they are invalid or null. In theory, static
// addresses should not be invalid here because TetheringManager do not allow caller to
// specify invalid static address configuration.
- if (request == null || request.localIPv4Address == null
- || request.staticClientAddress == null || !checkStaticAddressConfiguration(
- request.localIPv4Address, request.staticClientAddress)) {
+ if (request == null || request.getLocalIpv4Address() == null
+ || request.getClientStaticIpv4Address() == null || !checkStaticAddressConfiguration(
+ request.getLocalIpv4Address(), request.getClientStaticIpv4Address())) {
return;
}
- mStaticIpv4ServerAddr = request.localIPv4Address;
- mStaticIpv4ClientAddr = request.staticClientAddress;
+ mStaticIpv4ServerAddr = request.getLocalIpv4Address();
+ mStaticIpv4ClientAddr = request.getClientStaticIpv4Address();
}
class InitialState extends State {
@@ -1120,11 +1008,11 @@
mLastError = TETHER_ERROR_NO_ERROR;
switch (message.arg1) {
case STATE_LOCAL_ONLY:
- maybeConfigureStaticIp((TetheringRequestParcel) message.obj);
+ maybeConfigureStaticIp((TetheringRequest) message.obj);
transitionTo(mLocalHotspotState);
break;
case STATE_TETHERED:
- maybeConfigureStaticIp((TetheringRequestParcel) message.obj);
+ maybeConfigureStaticIp((TetheringRequest) message.obj);
transitionTo(mTetheredState);
break;
default:
@@ -1141,14 +1029,6 @@
}
}
- private void startConntrackMonitoring() {
- mBpfCoordinator.startMonitoring(this);
- }
-
- private void stopConntrackMonitoring() {
- mBpfCoordinator.stopMonitoring(this);
- }
-
abstract class BaseServingState extends State {
private final int mDesiredInterfaceState;
@@ -1158,7 +1038,7 @@
@Override
public void enter() {
- startConntrackMonitoring();
+ mBpfCoordinator.addIpServer(IpServer.this);
startServingInterface();
@@ -1226,7 +1106,7 @@
}
stopIPv4();
- stopConntrackMonitoring();
+ mBpfCoordinator.removeIpServer(IpServer.this);
resetLinkProperties();
@@ -1397,8 +1277,8 @@
for (String ifname : mUpstreamIfaceSet.ifnames) cleanupUpstreamInterface(ifname);
mUpstreamIfaceSet = null;
- mBpfCoordinator.updateAllIpv6Rules(
- IpServer.this, IpServer.this.mInterfaceParams, NO_UPSTREAM, Set.of());
+ mBpfCoordinator.updateIpv6UpstreamInterface(IpServer.this, NO_UPSTREAM,
+ Collections.emptySet());
}
private void cleanupUpstreamInterface(String upstreamIface) {
@@ -1464,7 +1344,7 @@
mBpfCoordinator.maybeAttachProgram(mIfaceName, ifname);
try {
addInterfaceForward(mIfaceName, ifname);
- } catch (RemoteException | ServiceSpecificException e) {
+ } catch (RuntimeException e) {
mLog.e("Exception enabling iface forward", e);
cleanupUpstream();
mLastError = TETHER_ERROR_ENABLE_FORWARDING_ERROR;
@@ -1473,9 +1353,6 @@
}
}
break;
- case CMD_NEIGHBOR_EVENT:
- handleNeighborEvent((NeighborEvent) message.obj);
- break;
default:
return false;
}
@@ -1515,9 +1392,6 @@
class UnavailableState extends State {
@Override
public void enter() {
- // TODO: move mIpNeighborMonitor.stop() to TetheredState#exit, and trigger a neighbours
- // dump after starting mIpNeighborMonitor.
- mIpNeighborMonitor.stop();
mLastError = TETHER_ERROR_NO_ERROR;
sendInterfaceState(STATE_UNAVAILABLE);
}
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 00d9152..5c853f4 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -76,6 +76,9 @@
import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.net.module.util.ip.ConntrackMonitor;
import com.android.net.module.util.ip.ConntrackMonitor.ConntrackEventConsumer;
+import com.android.net.module.util.ip.IpNeighborMonitor;
+import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEvent;
+import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEventConsumer;
import com.android.net.module.util.netlink.ConntrackMessage;
import com.android.net.module.util.netlink.NetlinkConstants;
import com.android.net.module.util.netlink.NetlinkUtils;
@@ -181,6 +184,10 @@
private final BpfCoordinatorShim mBpfCoordinatorShim;
@NonNull
private final BpfConntrackEventConsumer mBpfConntrackEventConsumer;
+ @NonNull
+ private final IpNeighborMonitor mIpNeighborMonitor;
+ @NonNull
+ private final BpfNeighborEventConsumer mBpfNeighborEventConsumer;
// True if BPF offload is supported, false otherwise. The BPF offload could be disabled by
// a runtime resource overlay package or device configuration. This flag is only initialized
@@ -189,14 +196,6 @@
// to make it simpler. See also TetheringConfiguration.
private final boolean mIsBpfEnabled;
- // Tracks whether BPF tethering is started or not. This is set by tethering before it
- // starts the first IpServer and is cleared by tethering shortly before the last IpServer
- // is stopped. Note that rule updates (especially deletions, but sometimes additions as
- // well) may arrive when this is false. If they do, they must be communicated to netd.
- // Changes in data limits may also arrive when this is false, and if they do, they must
- // also be communicated to netd.
- private boolean mPollingStarted = false;
-
// Tracking remaining alert quota. Unlike limit quota is subject to interface, the alert
// quota is interface independent and global for tether offload.
private long mRemainingAlertQuota = QUOTA_UNLIMITED;
@@ -279,9 +278,6 @@
private final HashMap<IpServer, HashMap<Inet4Address, ClientInfo>>
mTetherClients = new HashMap<>();
- // Set for which downstream is monitoring the conntrack netlink message.
- private final Set<IpServer> mMonitoringIpServers = new HashSet<>();
-
// Map of upstream interface IPv4 address to interface index.
// TODO: consider making the key to be unique because the upstream address is not unique. It
// is okay for now because there have only one upstream generally.
@@ -303,16 +299,19 @@
@Nullable
private UpstreamInfo mIpv4UpstreamInfo = null;
+ // The IpServers that are currently served by BpfCoordinator.
+ private final ArraySet<IpServer> mServedIpServers = new ArraySet<>();
+
// Runnable that used by scheduling next polling of stats.
private final Runnable mScheduledPollingStats = () -> {
updateForwardedStats();
- maybeSchedulePollingStats();
+ schedulePollingStats();
};
// Runnable that used by scheduling next refreshing of conntrack timeout.
private final Runnable mScheduledConntrackTimeoutUpdate = () -> {
refreshAllConntrackTimeouts();
- maybeScheduleConntrackTimeoutUpdate();
+ scheduleConntrackTimeoutUpdate();
};
// TODO: add BpfMap<TetherDownstream64Key, TetherDownstream64Value> retrieving function.
@@ -338,6 +337,11 @@
return new ConntrackMonitor(getHandler(), getSharedLog(), consumer);
}
+ /** Get ip neighbor monitor */
+ @NonNull public IpNeighborMonitor getIpNeighborMonitor(NeighborEventConsumer consumer) {
+ return new IpNeighborMonitor(getHandler(), getSharedLog(), consumer);
+ }
+
/** Get interface information for a given interface. */
@NonNull public InterfaceParams getInterfaceParams(String ifName) {
return InterfaceParams.getByName(ifName);
@@ -485,6 +489,9 @@
mBpfConntrackEventConsumer = new BpfConntrackEventConsumer();
mConntrackMonitor = mDeps.getConntrackMonitor(mBpfConntrackEventConsumer);
+ mBpfNeighborEventConsumer = new BpfNeighborEventConsumer();
+ mIpNeighborMonitor = mDeps.getIpNeighborMonitor(mBpfNeighborEventConsumer);
+
BpfTetherStatsProvider provider = new BpfTetherStatsProvider();
try {
mDeps.getNetworkStatsManager().registerNetworkStatsProvider(
@@ -504,37 +511,25 @@
}
/**
- * Start BPF tethering offload stats polling when the first upstream is started.
+ * Start BPF tethering offload stats and conntrack timeout polling.
* Note that this can be only called on handler thread.
- * TODO: Perhaps check BPF support before starting.
- * TODO: Start the stats polling only if there is any client on the downstream.
*/
- public void startPolling() {
- if (mPollingStarted) return;
+ private void startStatsAndConntrackTimeoutPolling() {
+ schedulePollingStats();
+ scheduleConntrackTimeoutUpdate();
- if (!isUsingBpf()) {
- mLog.i("BPF is not using");
- return;
- }
-
- mPollingStarted = true;
- maybeSchedulePollingStats();
- maybeScheduleConntrackTimeoutUpdate();
-
- mLog.i("Polling started");
+ mLog.i("Polling started.");
}
/**
- * Stop BPF tethering offload stats polling.
+ * Stop BPF tethering offload stats and conntrack timeout polling.
* The data limit cleanup and the tether stats maps cleanup are not implemented here.
* These cleanups rely on all IpServers calling #removeIpv6DownstreamRule. After the
* last rule is removed from the upstream, #removeIpv6DownstreamRule does the cleanup
* functionality.
* Note that this can be only called on handler thread.
*/
- public void stopPolling() {
- if (!mPollingStarted) return;
-
+ private void stopStatsAndConntrackTimeoutPolling() {
// Stop scheduled polling conntrack timeout.
if (mHandler.hasCallbacks(mScheduledConntrackTimeoutUpdate)) {
mHandler.removeCallbacks(mScheduledConntrackTimeoutUpdate);
@@ -544,9 +539,8 @@
mHandler.removeCallbacks(mScheduledPollingStats);
}
updateForwardedStats();
- mPollingStarted = false;
- mLog.i("Polling stopped");
+ mLog.i("Polling stopped.");
}
/**
@@ -567,7 +561,6 @@
/**
* Start conntrack message monitoring.
- * Note that this can be only called on handler thread.
*
* TODO: figure out a better logging for non-interesting conntrack message.
* For example, the following logging is an IPCTNL_MSG_CT_GET message but looks scary.
@@ -587,45 +580,23 @@
* +------------------+--------------------------------------------------------+
* See NetlinkMonitor#handlePacket, NetlinkMessage#parseNfMessage.
*/
- public void startMonitoring(@NonNull final IpServer ipServer) {
+ private void startConntrackMonitoring() {
// TODO: Wrap conntrackMonitor starting function into mBpfCoordinatorShim.
- if (!isUsingBpf() || !mDeps.isAtLeastS()) return;
+ if (!mDeps.isAtLeastS()) return;
- if (mMonitoringIpServers.contains(ipServer)) {
- Log.wtf(TAG, "The same downstream " + ipServer.interfaceName()
- + " should not start monitoring twice.");
- return;
- }
-
- if (mMonitoringIpServers.isEmpty()) {
- mConntrackMonitor.start();
- mLog.i("Monitoring started");
- }
-
- mMonitoringIpServers.add(ipServer);
+ mConntrackMonitor.start();
+ mLog.i("Conntrack monitoring started.");
}
/**
* Stop conntrack event monitoring.
- * Note that this can be only called on handler thread.
*/
- public void stopMonitoring(@NonNull final IpServer ipServer) {
+ private void stopConntrackMonitoring() {
// TODO: Wrap conntrackMonitor stopping function into mBpfCoordinatorShim.
- if (!isUsingBpf() || !mDeps.isAtLeastS()) return;
-
- // Ignore stopping monitoring if the monitor has never started for a given IpServer.
- if (!mMonitoringIpServers.contains(ipServer)) {
- mLog.e("Ignore stopping monitoring because monitoring has never started for "
- + ipServer.interfaceName());
- return;
- }
-
- mMonitoringIpServers.remove(ipServer);
-
- if (!mMonitoringIpServers.isEmpty()) return;
+ if (!mDeps.isAtLeastS()) return;
mConntrackMonitor.stop();
- mLog.i("Monitoring stopped");
+ mLog.i("Conntrack monitoring stopped.");
}
/**
@@ -688,9 +659,8 @@
/**
* Add IPv6 downstream rule.
- * Note that this can be only called on handler thread.
*/
- public void addIpv6DownstreamRule(
+ private void addIpv6DownstreamRule(
@NonNull final IpServer ipServer, @NonNull final Ipv6DownstreamRule rule) {
if (!isUsingBpf()) return;
@@ -706,9 +676,8 @@
/**
* Remove IPv6 downstream rule.
- * Note that this can be only called on handler thread.
*/
- public void removeIpv6DownstreamRule(
+ private void removeIpv6DownstreamRule(
@NonNull final IpServer ipServer, @NonNull final Ipv6DownstreamRule rule) {
if (!isUsingBpf()) return;
@@ -762,9 +731,8 @@
/**
* Delete all upstream and downstream rules for the passed-in IpServer, and if the new upstream
* is nonzero, reapply them to the new upstream.
- * Note that this can be only called on handler thread.
*/
- public void updateAllIpv6Rules(@NonNull final IpServer ipServer,
+ private void updateAllIpv6Rules(@NonNull final IpServer ipServer,
final InterfaceParams interfaceParams, int newUpstreamIfindex,
@NonNull final Set<IpPrefix> newUpstreamPrefixes) {
if (!isUsingBpf()) return;
@@ -886,6 +854,141 @@
}
/**
+ * Register an IpServer (downstream).
+ * Note that this can be only called on handler thread.
+ */
+ public void addIpServer(@NonNull final IpServer ipServer) {
+ if (!isUsingBpf()) return;
+ if (mServedIpServers.contains(ipServer)) {
+ Log.wtf(TAG, "The same downstream " + ipServer.interfaceName()
+ + " should not add twice.");
+ return;
+ }
+
+ // Start monitoring and polling when the first IpServer is added.
+ if (mServedIpServers.isEmpty()) {
+ startStatsAndConntrackTimeoutPolling();
+ startConntrackMonitoring();
+ mIpNeighborMonitor.start();
+ mLog.i("Neighbor monitoring started.");
+ }
+ mServedIpServers.add(ipServer);
+ }
+
+ /**
+ * Unregister an IpServer (downstream).
+ * Note that this can be only called on handler thread.
+ */
+ public void removeIpServer(@NonNull final IpServer ipServer) {
+ if (!isUsingBpf()) return;
+ if (!mServedIpServers.contains(ipServer)) {
+ mLog.e("Ignore removing because IpServer has never started for "
+ + ipServer.interfaceName());
+ return;
+ }
+ mServedIpServers.remove(ipServer);
+
+ // Stop monitoring and polling when the last IpServer is removed.
+ if (mServedIpServers.isEmpty()) {
+ stopStatsAndConntrackTimeoutPolling();
+ stopConntrackMonitoring();
+ mIpNeighborMonitor.stop();
+ mLog.i("Neighbor monitoring stopped.");
+ }
+ }
+
+ /**
+ * Update upstream interface and its prefixes.
+ * Note that this can be only called on handler thread.
+ */
+ public void updateIpv6UpstreamInterface(@NonNull final IpServer ipServer, int upstreamIfindex,
+ @NonNull Set<IpPrefix> upstreamPrefixes) {
+ if (!isUsingBpf()) return;
+
+ // If the upstream interface has changed, remove all rules and re-add them with the new
+ // upstream interface. If upstream is a virtual network, treated as no upstream.
+ final int prevUpstreamIfindex = ipServer.getIpv6UpstreamIfindex();
+ final InterfaceParams interfaceParams = ipServer.getInterfaceParams();
+ final Set<IpPrefix> prevUpstreamPrefixes = ipServer.getIpv6UpstreamPrefixes();
+ if (prevUpstreamIfindex != upstreamIfindex
+ || !prevUpstreamPrefixes.equals(upstreamPrefixes)) {
+ final boolean upstreamSupportsBpf = checkUpstreamSupportsBpf(upstreamIfindex);
+ updateAllIpv6Rules(ipServer, interfaceParams,
+ getInterfaceIndexForRule(upstreamIfindex, upstreamSupportsBpf),
+ upstreamPrefixes);
+ }
+ }
+
+ private boolean checkUpstreamSupportsBpf(int upstreamIfindex) {
+ final String iface = mInterfaceNames.get(upstreamIfindex);
+ return iface != null && !isVcnInterface(iface);
+ }
+
+ private int getInterfaceIndexForRule(int ifindex, boolean supportsBpf) {
+ return supportsBpf ? ifindex : NO_UPSTREAM;
+ }
+
+ // Handles updates to IPv6 downstream rules if a neighbor event is received.
+ private void addOrRemoveIpv6Downstream(@NonNull IpServer ipServer, NeighborEvent e) {
+ // mInterfaceParams must be non-null or the event would not have arrived.
+ if (e == null) return;
+ if (!(e.ip instanceof Inet6Address) || e.ip.isMulticastAddress()
+ || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) {
+ return;
+ }
+
+ // When deleting rules, we still need to pass a non-null MAC, even though it's ignored.
+ // Do this here instead of in the Ipv6DownstreamRule constructor to ensure that we
+ // never add rules with a null MAC, only delete them.
+ final InterfaceParams interfaceParams = ipServer.getInterfaceParams();
+ if (interfaceParams == null || interfaceParams.macAddr == null) return;
+ final int lastIpv6UpstreamIfindex = ipServer.getIpv6UpstreamIfindex();
+ final boolean isUpstreamSupportsBpf = checkUpstreamSupportsBpf(lastIpv6UpstreamIfindex);
+ MacAddress dstMac = e.isValid() ? e.macAddr : NULL_MAC_ADDRESS;
+ Ipv6DownstreamRule rule = new Ipv6DownstreamRule(
+ getInterfaceIndexForRule(lastIpv6UpstreamIfindex, isUpstreamSupportsBpf),
+ interfaceParams.index, (Inet6Address) e.ip, interfaceParams.macAddr, dstMac);
+ if (e.isValid()) {
+ addIpv6DownstreamRule(ipServer, rule);
+ } else {
+ removeIpv6DownstreamRule(ipServer, rule);
+ }
+ }
+
+ private void updateClientInfoIpv4(@NonNull IpServer ipServer, NeighborEvent e) {
+ if (e == null) return;
+ if (!(e.ip instanceof Inet4Address) || e.ip.isMulticastAddress()
+ || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) {
+ return;
+ }
+
+ InterfaceParams interfaceParams = ipServer.getInterfaceParams();
+ if (interfaceParams == null) return;
+
+ // When deleting clients, IpServer still need to pass a non-null MAC, even though it's
+ // ignored. Do this here instead of in the ClientInfo constructor to ensure that
+ // IpServer never add clients with a null MAC, only delete them.
+ final MacAddress clientMac = e.isValid() ? e.macAddr : NULL_MAC_ADDRESS;
+ final ClientInfo clientInfo = new ClientInfo(interfaceParams.index,
+ interfaceParams.macAddr, (Inet4Address) e.ip, clientMac);
+ if (e.isValid()) {
+ tetherOffloadClientAdd(ipServer, clientInfo);
+ } else {
+ tetherOffloadClientRemove(ipServer, clientInfo);
+ }
+ }
+
+ private void handleNeighborEvent(@NonNull IpServer ipServer, NeighborEvent e) {
+ InterfaceParams interfaceParams = ipServer.getInterfaceParams();
+ if (interfaceParams != null
+ && interfaceParams.index == e.ifindex
+ && interfaceParams.hasMacAddress) {
+ addOrRemoveIpv6Downstream(ipServer, e);
+ updateClientInfoIpv4(ipServer, e);
+ }
+ }
+
+ /**
* Clear all forwarding IPv4 rules for a given client.
* Note that this can be only called on handler thread.
*/
@@ -1136,7 +1239,7 @@
// Note that EthernetTetheringTest#isTetherConfigBpfOffloadEnabled relies on
// "mIsBpfEnabled" to check tethering config via dumpsys. Beware of the change if any.
pw.println("mIsBpfEnabled: " + mIsBpfEnabled);
- pw.println("Polling " + (mPollingStarted ? "started" : "not started"));
+ pw.println("Polling " + (mServedIpServers.isEmpty() ? "not started" : "started"));
pw.println("Stats provider " + (mStatsProvider != null
? "registered" : "not registered"));
pw.println("Upstream quota: " + mInterfaceQuotas.toString());
@@ -2038,6 +2141,15 @@
}
}
+ @VisibleForTesting
+ private class BpfNeighborEventConsumer implements NeighborEventConsumer {
+ public void accept(NeighborEvent e) {
+ for (IpServer ipServer : mServedIpServers) {
+ handleNeighborEvent(ipServer, e);
+ }
+ }
+ }
+
private boolean isBpfEnabled() {
final TetheringConfiguration config = mDeps.getTetherConfig();
return (config != null) ? config.isBpfOffloadEnabled() : true /* default value */;
@@ -2365,9 +2477,7 @@
});
}
- private void maybeSchedulePollingStats() {
- if (!mPollingStarted) return;
-
+ private void schedulePollingStats() {
if (mHandler.hasCallbacks(mScheduledPollingStats)) {
mHandler.removeCallbacks(mScheduledPollingStats);
}
@@ -2375,9 +2485,7 @@
mHandler.postDelayed(mScheduledPollingStats, getPollingInterval());
}
- private void maybeScheduleConntrackTimeoutUpdate() {
- if (!mPollingStarted) return;
-
+ private void scheduleConntrackTimeoutUpdate() {
if (mHandler.hasCallbacks(mScheduledConntrackTimeoutUpdate)) {
mHandler.removeCallbacks(mScheduledConntrackTimeoutUpdate);
}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index d85d92f..d62f18f 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -38,6 +38,7 @@
import static android.net.TetheringManager.TETHERING_INVALID;
import static android.net.TetheringManager.TETHERING_NCM;
import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_VIRTUAL;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
import static android.net.TetheringManager.TETHERING_WIGIG;
@@ -90,14 +91,12 @@
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkInfo;
-import android.net.RoutingCoordinatorManager;
import android.net.TetherStatesParcel;
import android.net.TetheredClient;
import android.net.TetheringCallbackStartedParcel;
import android.net.TetheringConfigurationParcel;
import android.net.TetheringInterface;
import android.net.TetheringManager.TetheringRequest;
-import android.net.TetheringRequestParcel;
import android.net.Uri;
import android.net.ip.IpServer;
import android.net.wifi.WifiClient;
@@ -138,7 +137,7 @@
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.HandlerUtils;
import com.android.net.module.util.NetdUtils;
-import com.android.net.module.util.SdkUtil.LateSdk;
+import com.android.net.module.util.RoutingCoordinatorManager;
import com.android.net.module.util.SharedLog;
import com.android.networkstack.apishim.common.BluetoothPanShim;
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim;
@@ -147,7 +146,6 @@
import com.android.networkstack.tethering.metrics.TetheringMetrics;
import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.networkstack.tethering.util.PrefixUtils;
-import com.android.networkstack.tethering.util.TetheringUtils;
import com.android.networkstack.tethering.util.VersionedBroadcastListener;
import com.android.networkstack.tethering.wear.WearableConnectionManager;
@@ -231,7 +229,7 @@
// Currently active tethering requests per tethering type. Only one of each type can be
// requested at a time. After a tethering type is requested, the map keeps tethering parameters
// to be used after the interface comes up asynchronously.
- private final SparseArray<TetheringRequestParcel> mActiveTetheringRequests =
+ private final SparseArray<TetheringRequest> mActiveTetheringRequests =
new SparseArray<>();
private final Context mContext;
@@ -247,10 +245,7 @@
private final Handler mHandler;
private final INetd mNetd;
private final NetdCallback mNetdCallback;
- // Contains null if the connectivity module is unsupported, as the routing coordinator is not
- // available. Must use LateSdk because MessageUtils enumerates fields in this class, so it
- // must be able to find all classes at runtime.
- @NonNull private final LateSdk<RoutingCoordinatorManager> mRoutingCoordinator;
+ private final RoutingCoordinatorManager mRoutingCoordinator;
private final UserRestrictionActionListener mTetheringRestriction;
private final ActiveDataSubIdListener mActiveDataSubIdListener;
private final ConnectedClientsTracker mConnectedClientsTracker;
@@ -278,6 +273,7 @@
private TetheredInterfaceRequestShim mBluetoothIfaceRequest;
private String mConfiguredEthernetIface;
private String mConfiguredBluetoothIface;
+ private String mConfiguredVirtualIface;
private EthernetCallback mEthernetCallback;
private TetheredInterfaceCallbackShim mBluetoothCallback;
private SettingsObserver mSettingsObserver;
@@ -294,11 +290,11 @@
mLog.mark("Tethering.constructed");
mDeps = deps;
mContext = mDeps.getContext();
- mNetd = mDeps.getINetd(mContext);
- mRoutingCoordinator = mDeps.getRoutingCoordinator(mContext);
+ mNetd = mDeps.getINetd(mContext, mLog);
+ mRoutingCoordinator = mDeps.getRoutingCoordinator(mContext, mLog);
mLooper = mDeps.makeTetheringLooper();
mNotificationUpdater = mDeps.makeNotificationUpdater(mContext, mLooper);
- mTetheringMetrics = mDeps.makeTetheringMetrics();
+ mTetheringMetrics = mDeps.makeTetheringMetrics(mContext);
// This is intended to ensrure that if something calls startTethering(bluetooth) just after
// bluetooth is enabled. Before onServiceConnected is called, store the calls into this
@@ -659,28 +655,27 @@
processInterfaceStateChange(iface, false /* enabled */);
}
- void startTethering(final TetheringRequestParcel request, final String callerPkg,
+ void startTethering(final TetheringRequest request, final String callerPkg,
final IIntResultListener listener) {
mHandler.post(() -> {
- final TetheringRequestParcel unfinishedRequest = mActiveTetheringRequests.get(
- request.tetheringType);
+ final int type = request.getTetheringType();
+ final TetheringRequest unfinishedRequest = mActiveTetheringRequests.get(type);
// If tethering is already enabled with a different request,
// disable before re-enabling.
- if (unfinishedRequest != null
- && !TetheringUtils.isTetheringRequestEquals(unfinishedRequest, request)) {
- enableTetheringInternal(request.tetheringType, false /* disabled */, null);
- mEntitlementMgr.stopProvisioningIfNeeded(request.tetheringType);
+ if (unfinishedRequest != null && !unfinishedRequest.equals(request)) {
+ enableTetheringInternal(type, false /* disabled */, null);
+ mEntitlementMgr.stopProvisioningIfNeeded(type);
}
- mActiveTetheringRequests.put(request.tetheringType, request);
+ mActiveTetheringRequests.put(type, request);
- if (request.exemptFromEntitlementCheck) {
- mEntitlementMgr.setExemptedDownstreamType(request.tetheringType);
+ if (request.isExemptFromEntitlementCheck()) {
+ mEntitlementMgr.setExemptedDownstreamType(type);
} else {
- mEntitlementMgr.startProvisioningIfNeeded(request.tetheringType,
- request.showProvisioningUi);
+ mEntitlementMgr.startProvisioningIfNeeded(type,
+ request.getShouldShowEntitlementUi());
}
- enableTetheringInternal(request.tetheringType, true /* enabled */, listener);
- mTetheringMetrics.createBuilder(request.tetheringType, callerPkg);
+ enableTetheringInternal(type, true /* enabled */, listener);
+ mTetheringMetrics.createBuilder(type, callerPkg);
});
}
@@ -719,6 +714,9 @@
case TETHERING_ETHERNET:
result = setEthernetTethering(enable);
break;
+ case TETHERING_VIRTUAL:
+ result = setVirtualMachineTethering(enable);
+ break;
default:
Log.w(TAG, "Invalid tether type.");
result = TETHER_ERROR_UNKNOWN_TYPE;
@@ -972,6 +970,21 @@
}
}
+ private int setVirtualMachineTethering(final boolean enable) {
+ // TODO(340377643): Use bridge ifname when it's introduced, not fixed TAP ifname.
+ if (enable) {
+ mConfiguredVirtualIface = "avf_tap_fixed";
+ enableIpServing(
+ TETHERING_VIRTUAL,
+ mConfiguredVirtualIface,
+ getRequestedState(TETHERING_VIRTUAL));
+ } else if (mConfiguredVirtualIface != null) {
+ ensureIpServerStopped(mConfiguredVirtualIface);
+ mConfiguredVirtualIface = null;
+ }
+ return TETHER_ERROR_NO_ERROR;
+ }
+
void tether(String iface, int requestedState, final IIntResultListener listener) {
mHandler.post(() -> {
try {
@@ -998,7 +1011,7 @@
//
// This code cannot race with untether() because they both run on the handler thread.
final int type = tetherState.ipServer.interfaceType();
- final TetheringRequestParcel request = mActiveTetheringRequests.get(type, null);
+ final TetheringRequest request = mActiveTetheringRequests.get(type, null);
if (request != null) {
mActiveTetheringRequests.delete(type);
}
@@ -1055,14 +1068,14 @@
}
private int getRequestedState(int type) {
- final TetheringRequestParcel request = mActiveTetheringRequests.get(type);
+ final TetheringRequest request = mActiveTetheringRequests.get(type);
// The request could have been deleted before we had a chance to complete it.
// If so, assume that the scope is the default scope for this tethering type.
// This likely doesn't matter - if the request has been deleted, then tethering is
// likely going to be stopped soon anyway.
final int connectivityScope = (request != null)
- ? request.connectivityScope
+ ? request.getConnectivityScope()
: TetheringRequest.getDefaultConnectivityScope(type);
return connectivityScope == CONNECTIVITY_SCOPE_LOCAL
@@ -1361,7 +1374,7 @@
}
@VisibleForTesting
- SparseArray<TetheringRequestParcel> getActiveTetheringRequests() {
+ SparseArray<TetheringRequest> getActiveTetheringRequests() {
return mActiveTetheringRequests;
}
@@ -2069,9 +2082,6 @@
chooseUpstreamType(true);
mTryCell = false;
}
-
- // TODO: Check the upstream interface if it is managed by BPF offload.
- mBpfCoordinator.startPolling();
}
@Override
@@ -2085,7 +2095,6 @@
reportUpstreamChanged(null);
mNotificationUpdater.onUpstreamCapabilitiesChanged(null);
}
- mBpfCoordinator.stopPolling();
mTetheringMetrics.cleanup();
}
@@ -2384,9 +2393,6 @@
hasCallingPermission(NETWORK_SETTINGS)
|| hasCallingPermission(PERMISSION_MAINLINE_NETWORK_STACK)
|| hasCallingPermission(NETWORK_STACK);
- if (callback == null) {
- throw new NullPointerException();
- }
mHandler.post(() -> {
mTetheringEventCallbacks.register(callback, new CallbackCookie(hasListPermission));
final TetheringCallbackStartedParcel parcel = new TetheringCallbackStartedParcel();
@@ -2424,9 +2430,6 @@
/** Unregister tethering event callback */
void unregisterTetheringEventCallback(ITetheringEventCallback callback) {
- if (callback == null) {
- throw new NullPointerException();
- }
mHandler.post(() -> {
mTetheringEventCallbacks.unregister(callback);
});
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 3f86056..5d9d349 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -22,7 +22,6 @@
import android.bluetooth.BluetoothPan;
import android.content.Context;
import android.net.INetd;
-import android.net.RoutingCoordinatorManager;
import android.net.connectivity.ConnectivityInternalApiUtil;
import android.net.ip.IpServer;
import android.os.Build;
@@ -36,7 +35,8 @@
import androidx.annotation.RequiresApi;
import com.android.modules.utils.build.SdkLevel;
-import com.android.net.module.util.SdkUtil.LateSdk;
+import com.android.net.module.util.RoutingCoordinatorManager;
+import com.android.net.module.util.RoutingCoordinatorService;
import com.android.net.module.util.SharedLog;
import com.android.networkstack.apishim.BluetoothPanShimImpl;
import com.android.networkstack.apishim.common.BluetoothPanShim;
@@ -120,19 +120,26 @@
/**
* Get a reference to INetd to be used by tethering.
*/
- public INetd getINetd(Context context) {
- return INetd.Stub.asInterface(
- (IBinder) context.getSystemService(Context.NETD_SERVICE));
+ public INetd getINetd(Context context, SharedLog log) {
+ final INetd netd =
+ INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE));
+ if (netd == null) {
+ log.wtf("INetd is null");
+ }
+ return netd;
}
/**
- * Get the routing coordinator, or null if below S.
+ * Get the routing coordinator.
*/
- @Nullable
- public LateSdk<RoutingCoordinatorManager> getRoutingCoordinator(Context context) {
- if (!SdkLevel.isAtLeastS()) return new LateSdk<>(null);
- return new LateSdk<>(
- ConnectivityInternalApiUtil.getRoutingCoordinatorManager(context));
+ public RoutingCoordinatorManager getRoutingCoordinator(Context context, SharedLog log) {
+ IBinder binder;
+ if (!SdkLevel.isAtLeastS()) {
+ binder = new RoutingCoordinatorService(getINetd(context, log));
+ } else {
+ binder = ConnectivityInternalApiUtil.getRoutingCoordinator(context);
+ }
+ return new RoutingCoordinatorManager(context, binder);
}
/**
@@ -186,8 +193,8 @@
/**
* Make the TetheringMetrics to be used by tethering.
*/
- public TetheringMetrics makeTetheringMetrics() {
- return new TetheringMetrics();
+ public TetheringMetrics makeTetheringMetrics(Context ctx) {
+ return new TetheringMetrics(ctx);
}
/**
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index 623f502..454cbf1 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -38,6 +38,7 @@
import android.net.ITetheringConnector;
import android.net.ITetheringEventCallback;
import android.net.NetworkStack;
+import android.net.TetheringManager.TetheringRequest;
import android.net.TetheringRequestParcel;
import android.net.dhcp.DhcpServerCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
@@ -137,8 +138,8 @@
listener)) {
return;
}
-
- mTethering.startTethering(request, callerPkg, listener);
+ // TODO(b/216524590): Add UID/packageName of caller to TetheringRequest here
+ mTethering.startTethering(new TetheringRequest(request), callerPkg, listener);
}
@Override
@@ -163,6 +164,8 @@
@Override
public void registerTetheringEventCallback(ITetheringEventCallback callback,
String callerPkg) {
+ // Silently ignore call if the callback is null. This can only happen via reflection.
+ if (callback == null) return;
try {
if (!hasTetherAccessPermission()) {
callback.onCallbackStopped(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION);
@@ -175,6 +178,8 @@
@Override
public void unregisterTetheringEventCallback(ITetheringEventCallback callback,
String callerPkg) {
+ // Silently ignore call if the callback is null. This can only happen via reflection.
+ if (callback == null) return;
try {
if (!hasTetherAccessPermission()) {
callback.onCallbackStopped(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION);
diff --git a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
index 814afcd..136dfb1 100644
--- a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
+++ b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
@@ -46,6 +46,7 @@
import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
import android.annotation.Nullable;
+import android.content.Context;
import android.net.NetworkCapabilities;
import android.stats.connectivity.DownstreamType;
import android.stats.connectivity.ErrorCode;
@@ -81,16 +82,50 @@
private final SparseArray<NetworkTetheringReported.Builder> mBuilderMap = new SparseArray<>();
private final SparseArray<Long> mDownstreamStartTime = new SparseArray<Long>();
private final ArrayList<RecordUpstreamEvent> mUpstreamEventList = new ArrayList<>();
+ private final Context mContext;
+ private final Dependencies mDependencies;
private UpstreamType mCurrentUpstream = null;
private Long mCurrentUpStreamStartTime = 0L;
+ /**
+ * Dependencies of TetheringMetrics, for injection in tests.
+ */
+ @VisibleForTesting
+ public static class Dependencies {
+ /**
+ * @see TetheringStatsLog
+ */
+ public void write(NetworkTetheringReported reported) {
+ TetheringStatsLog.write(
+ TetheringStatsLog.NETWORK_TETHERING_REPORTED,
+ reported.getErrorCode().getNumber(),
+ reported.getDownstreamType().getNumber(),
+ reported.getUpstreamType().getNumber(),
+ reported.getUserType().getNumber(),
+ reported.getUpstreamEvents().toByteArray(),
+ reported.getDurationMillis());
+ }
+
+ /**
+ * @see System#currentTimeMillis()
+ */
+ public long timeNow() {
+ return System.currentTimeMillis();
+ }
+ }
/**
- * Return the current system time in milliseconds.
- * @return the current system time in milliseconds.
+ * Constructor for the TetheringMetrics class.
+ *
+ * @param context The Context object used to access system services.
*/
- public long timeNow() {
- return System.currentTimeMillis();
+ public TetheringMetrics(Context context) {
+ this(context, new Dependencies());
+ }
+
+ TetheringMetrics(Context context, Dependencies dependencies) {
+ mContext = context;
+ mDependencies = dependencies;
}
private static class RecordUpstreamEvent {
@@ -123,7 +158,7 @@
.setUpstreamEvents(UpstreamEvents.newBuilder())
.setDurationMillis(0);
mBuilderMap.put(downstreamType, statsBuilder);
- mDownstreamStartTime.put(downstreamType, timeNow());
+ mDownstreamStartTime.put(downstreamType, mDependencies.timeNow());
}
/**
@@ -149,7 +184,7 @@
UpstreamType upstream = transportTypeToUpstreamTypeEnum(ns);
if (upstream.equals(mCurrentUpstream)) return;
- final long newTime = timeNow();
+ final long newTime = mDependencies.timeNow();
if (mCurrentUpstream != null) {
mUpstreamEventList.add(new RecordUpstreamEvent(mCurrentUpStreamStartTime, newTime,
mCurrentUpstream));
@@ -206,7 +241,7 @@
event.mUpstreamType, 0L /* txBytes */, 0L /* rxBytes */);
}
final long startTime = Math.max(downstreamStartTime, mCurrentUpStreamStartTime);
- final long stopTime = timeNow();
+ final long stopTime = mDependencies.timeNow();
// Handle the last upstream event.
addUpstreamEvent(upstreamEventsBuilder, startTime, stopTime, mCurrentUpstream,
0L /* txBytes */, 0L /* rxBytes */);
@@ -248,15 +283,7 @@
@VisibleForTesting
public void write(@NonNull final NetworkTetheringReported reported) {
final byte[] upstreamEvents = reported.getUpstreamEvents().toByteArray();
-
- TetheringStatsLog.write(
- TetheringStatsLog.NETWORK_TETHERING_REPORTED,
- reported.getErrorCode().getNumber(),
- reported.getDownstreamType().getNumber(),
- reported.getUpstreamType().getNumber(),
- reported.getUserType().getNumber(),
- upstreamEvents,
- reported.getDurationMillis());
+ mDependencies.write(reported);
if (DBG) {
Log.d(
TAG,
@@ -374,4 +401,22 @@
return UpstreamType.UT_UNKNOWN;
}
+
+ /**
+ * Check whether tethering metrics' data usage can be collected for a given upstream type.
+ *
+ * @param type the upstream type
+ */
+ public static boolean isUsageSupportedForUpstreamType(@NonNull UpstreamType type) {
+ switch(type) {
+ case UT_CELLULAR:
+ case UT_WIFI:
+ case UT_BLUETOOTH:
+ case UT_ETHERNET:
+ return true;
+ default:
+ break;
+ }
+ return false;
+ }
}
diff --git a/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java b/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java
index 078a35f..c236188 100644
--- a/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java
+++ b/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java
@@ -22,7 +22,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
-import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo;
+import com.android.net.module.util.SyncStateMachine;
+import com.android.net.module.util.SyncStateMachine.StateInfo;
import java.util.List;
diff --git a/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java b/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
index e6236df..76c2f0d 100644
--- a/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
+++ b/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
@@ -16,7 +16,6 @@
package com.android.networkstack.tethering.util;
import android.net.TetherStatsParcel;
-import android.net.TetheringRequestParcel;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -29,7 +28,6 @@
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Arrays;
-import java.util.Objects;
/**
* The classes and the methods for tethering utilization.
@@ -158,20 +156,6 @@
return s & 0xffff;
}
- /** Check whether two TetheringRequestParcels are the same. */
- public static boolean isTetheringRequestEquals(final TetheringRequestParcel request,
- final TetheringRequestParcel otherRequest) {
- if (request == otherRequest) return true;
-
- return request != null && otherRequest != null
- && request.tetheringType == otherRequest.tetheringType
- && Objects.equals(request.localIPv4Address, otherRequest.localIPv4Address)
- && Objects.equals(request.staticClientAddress, otherRequest.staticClientAddress)
- && request.exemptFromEntitlementCheck == otherRequest.exemptFromEntitlementCheck
- && request.showProvisioningUi == otherRequest.showProvisioningUi
- && request.connectivityScope == otherRequest.connectivityScope;
- }
-
/** Get inet6 address for all nodes given scope ID. */
public static Inet6Address getAllNodesForScopeId(int scopeId) {
try {
diff --git a/Tethering/src/com/android/networkstack/tethering/util/VersionedBroadcastListener.java b/Tethering/src/com/android/networkstack/tethering/util/VersionedBroadcastListener.java
index c9e75c0..5eb1551 100644
--- a/Tethering/src/com/android/networkstack/tethering/util/VersionedBroadcastListener.java
+++ b/Tethering/src/com/android/networkstack/tethering/util/VersionedBroadcastListener.java
@@ -62,8 +62,8 @@
if (DBG) Log.d(mTag, "startListening");
if (mReceiver != null) return;
- mReceiver = new Receiver(mTag, mGenerationNumber, mCallback);
- mContext.registerReceiver(mReceiver, mFilter, null, mHandler);
+ mReceiver = new Receiver(mTag, mGenerationNumber, mCallback, mHandler);
+ mContext.registerReceiver(mReceiver, mFilter);
}
/** Stop listening to intent broadcast. */
@@ -77,30 +77,35 @@
}
private static class Receiver extends BroadcastReceiver {
- public final String tag;
- public final AtomicInteger atomicGenerationNumber;
- public final Consumer<Intent> callback;
+ final String mTag;
+ final AtomicInteger mAtomicGenerationNumber;
+ final Consumer<Intent> mCallback;
// Used to verify this receiver is still current.
- public final int generationNumber;
+ final int mGenerationNumber;
+ private final Handler mHandler;
- Receiver(String tag, AtomicInteger atomicGenerationNumber, Consumer<Intent> callback) {
- this.tag = tag;
- this.atomicGenerationNumber = atomicGenerationNumber;
- this.callback = callback;
- generationNumber = atomicGenerationNumber.incrementAndGet();
+ Receiver(String tag, AtomicInteger atomicGenerationNumber, Consumer<Intent> callback,
+ Handler handler) {
+ mTag = tag;
+ mAtomicGenerationNumber = atomicGenerationNumber;
+ mCallback = callback;
+ mGenerationNumber = atomicGenerationNumber.incrementAndGet();
+ mHandler = handler;
}
@Override
public void onReceive(Context context, Intent intent) {
- final int currentGenerationNumber = atomicGenerationNumber.get();
+ mHandler.post(() -> {
+ final int currentGenerationNumber = mAtomicGenerationNumber.get();
- if (DBG) {
- Log.d(tag, "receiver generationNumber=" + generationNumber
- + ", current generationNumber=" + currentGenerationNumber);
- }
- if (generationNumber != currentGenerationNumber) return;
+ if (DBG) {
+ Log.d(mTag, "receiver generationNumber=" + mGenerationNumber
+ + ", current generationNumber=" + currentGenerationNumber);
+ }
+ if (mGenerationNumber != currentGenerationNumber) return;
- callback.accept(intent);
+ mCallback.accept(intent);
+ });
}
}
}
diff --git a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
index 120b871..3944a8a 100644
--- a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
+++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
@@ -16,7 +16,6 @@
package android.net;
-import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.TETHER_PRIVILEGED;
@@ -42,7 +41,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
@@ -65,7 +63,6 @@
import androidx.annotation.NonNull;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.Struct;
import com.android.net.module.util.structs.Ipv6Header;
import com.android.testutils.HandlerUtils;
@@ -105,7 +102,7 @@
// Used to check if any tethering interface is available. Choose 200ms to be request timeout
// because the average interface requested time on cuttlefish@acloud is around 10ms.
// See TetheredInterfaceRequester.getInterface, isInterfaceForTetheringAvailable.
- private static final int AVAILABLE_TETHER_IFACE_REQUEST_TIMEOUT_MS = 200;
+ private static final int SHORT_TIMEOUT_MS = 1000;
private static final int TETHER_REACHABILITY_ATTEMPTS = 20;
protected static final long WAIT_RA_TIMEOUT_MS = 2000;
@@ -154,7 +151,7 @@
private boolean mRunTests;
private HandlerThread mHandlerThread;
private Handler mHandler;
- private TetheredInterfaceRequester mTetheredInterfaceRequester;
+ protected TetheredInterfaceRequester mTetheredInterfaceRequester;
// Late initialization in initTetheringTester().
private TapPacketReader mUpstreamReader;
@@ -245,12 +242,14 @@
maybeUnregisterTetheringEventCallback(mTetheringEventCallback);
mTetheringEventCallback = null;
- runAsShell(NETWORK_SETTINGS, () -> mTetheredInterfaceRequester.release());
setIncludeTestInterfaces(false);
}
@After
public void tearDown() throws Exception {
+ if (mTetheredInterfaceRequester != null) {
+ mTetheredInterfaceRequester.release();
+ }
try {
if (mRunTests) cleanUp();
} finally {
@@ -263,33 +262,17 @@
}
}
- protected static boolean isInterfaceForTetheringAvailable() throws Exception {
- // Before T, all ethernet interfaces could be used for server mode. Instead of
- // waiting timeout, just checking whether the system currently has any
- // ethernet interface is more reliable.
- if (!SdkLevel.isAtLeastT()) {
- return runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS, () -> sEm.isAvailable());
- }
-
+ protected boolean isInterfaceForTetheringAvailable() throws Exception {
// If previous test case doesn't release tethering interface successfully, the other tests
// after that test may be skipped as unexcepted.
// TODO: figure out a better way to check default tethering interface existenion.
- final TetheredInterfaceRequester requester = new TetheredInterfaceRequester();
- try {
- // Use short timeout (200ms) for requesting an existing interface, if any, because
- // it should reurn faster than requesting a new tethering interface. Using default
- // timeout (5000ms, TIMEOUT_MS) may make that total testing time is over 1 minute
- // test module timeout on internal testing.
- // TODO: if this becomes flaky, consider using default timeout (5000ms) and moving
- // this check into #setUpOnce.
- return requester.getInterface(AVAILABLE_TETHER_IFACE_REQUEST_TIMEOUT_MS) != null;
- } catch (TimeoutException e) {
- return false;
- } finally {
- runAsShell(NETWORK_SETTINGS, () -> {
- requester.release();
- });
- }
+ // Use short timeout (200ms) for requesting an existing interface, if any, because
+ // it should reurn faster than requesting a new tethering interface. Using default
+ // timeout (5000ms, TIMEOUT_MS) may make that total testing time is over 1 minute
+ // test module timeout on internal testing.
+ // TODO: if this becomes flaky, consider using default timeout (5000ms) and moving
+ // this check into #setUpOnce.
+ return mTetheredInterfaceRequester.isPhysicalInterfaceAvailable(SHORT_TIMEOUT_MS);
}
protected static void setIncludeTestInterfaces(boolean include) {
@@ -304,14 +287,6 @@
});
}
- protected String getTetheredInterface() throws Exception {
- return mTetheredInterfaceRequester.getInterface();
- }
-
- protected CompletableFuture<String> requestTetheredInterface() throws Exception {
- return mTetheredInterfaceRequester.requestInterface();
- }
-
protected static void waitForRouterAdvertisement(TapPacketReader reader, String iface,
long timeoutMs) {
final long deadline = SystemClock.uptimeMillis() + timeoutMs;
@@ -605,6 +580,11 @@
private TetheredInterfaceRequest mRequest;
private final CompletableFuture<String> mFuture = new CompletableFuture<>();
+ TetheredInterfaceRequester() {
+ mRequest = runAsShell(NETWORK_SETTINGS, () ->
+ sEm.requestTetheredInterface(c -> c.run() /* executor */, this));
+ }
+
@Override
public void onAvailable(String iface) {
Log.d(TAG, "Ethernet interface available: " + iface);
@@ -616,28 +596,21 @@
mFuture.completeExceptionally(new IllegalStateException("onUnavailable received"));
}
- public CompletableFuture<String> requestInterface() {
- assertNull("BUG: more than one tethered interface request", mRequest);
- Log.d(TAG, "Requesting tethered interface");
- mRequest = runAsShell(NETWORK_SETTINGS, () ->
- sEm.requestTetheredInterface(c -> c.run() /* executor */, this));
- return mFuture;
- }
-
- public String getInterface(int timeout) throws Exception {
- return requestInterface().get(timeout, TimeUnit.MILLISECONDS);
+ public boolean isPhysicalInterfaceAvailable(int timeout) {
+ try {
+ final String iface = mFuture.get(timeout, TimeUnit.MILLISECONDS);
+ return !iface.startsWith("testtap");
+ } catch (Exception e) {
+ return false;
+ }
}
public String getInterface() throws Exception {
- return getInterface(TIMEOUT_MS);
+ return mFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
public void release() {
- if (mRequest != null) {
- mFuture.obtrudeException(new IllegalStateException("Request already released"));
- mRequest.release();
- mRequest = null;
- }
+ runAsShell(NETWORK_SETTINGS, () -> mRequest.release());
}
}
@@ -658,7 +631,10 @@
lp.setLinkAddresses(addresses);
lp.setDnsServers(dnses);
- return runAsShell(MANAGE_TEST_NETWORKS, () -> initTestNetwork(sContext, lp, TIMEOUT_MS));
+ // TODO: initTestNetwork can take up to 15 seconds on a workstation. Investigate when and
+ // why this is the case. It is unclear whether a 30 second timeout is enough when running
+ // these tests in the much slower test infra.
+ return runAsShell(MANAGE_TEST_NETWORKS, () -> initTestNetwork(sContext, lp, 30_000));
}
protected void sendDownloadPacketUdp(@NonNull final InetAddress srcIp,
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index c54d1b4..9cdba2f 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -16,6 +16,7 @@
package android.net;
+import static android.Manifest.permission.DUMP;
import static android.net.InetAddresses.parseNumericAddress;
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.TETHERING_ETHERNET;
@@ -26,40 +27,55 @@
import static android.net.TetheringTester.isExpectedUdpDnsPacket;
import static android.system.OsConstants.ICMP_ECHO;
import static android.system.OsConstants.ICMP_ECHOREPLY;
+import static android.system.OsConstants.IPPROTO_UDP;
import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA;
import static com.android.net.module.util.HexDump.dumpHexString;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
+import static com.android.testutils.DeviceInfoUtils.KVersion;
+import static com.android.testutils.TestPermissionUtil.runAsShell;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
+import android.content.Context;
import android.net.TetheringManager.TetheringRequest;
import android.net.TetheringTester.TetheredDevice;
import android.os.Build;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.VintfRuntimeInfo;
import android.util.Log;
+import android.util.Pair;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.net.module.util.BpfDump;
import com.android.net.module.util.Ipv6Utils;
import com.android.net.module.util.Struct;
+import com.android.net.module.util.bpf.Tether4Key;
+import com.android.net.module.util.bpf.Tether4Value;
+import com.android.net.module.util.bpf.TetherStatsKey;
+import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.net.module.util.structs.Ipv4Header;
import com.android.net.module.util.structs.UdpHeader;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DeviceInfoUtils;
+import com.android.testutils.DumpTestUtils;
import com.android.testutils.NetworkStackModuleTest;
import com.android.testutils.TapPacketReader;
-import org.junit.BeforeClass;
+import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -73,10 +89,10 @@
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Random;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@RunWith(AndroidJUnit4.class)
@@ -91,6 +107,26 @@
private static final short ICMPECHO_ID = 0x0;
private static final short ICMPECHO_SEQ = 0x0;
+ private static final int DUMP_POLLING_MAX_RETRY = 100;
+ private static final int DUMP_POLLING_INTERVAL_MS = 50;
+ // Kernel treats a confirmed UDP connection which active after two seconds as stream mode.
+ // See upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5.
+ private static final int UDP_STREAM_TS_MS = 2000;
+ // Give slack time for waiting UDP stream mode because handling conntrack event in user space
+ // may not in precise time. Used to reduce the flaky rate.
+ private static final int UDP_STREAM_SLACK_MS = 500;
+ // Per RX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
+ private static final int RX_UDP_PACKET_SIZE = 30;
+ private static final int RX_UDP_PACKET_COUNT = 456;
+ // Per TX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
+ private static final int TX_UDP_PACKET_SIZE = 30;
+ private static final int TX_UDP_PACKET_COUNT = 123;
+
+ private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
+ private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
+ private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
+ private static final String LINE_DELIMITER = "\\n";
+
// TODO: use class DnsPacket to build DNS query and reply message once DnsPacket supports
// building packet for given arguments.
private static final ByteBuffer DNS_QUERY = ByteBuffer.wrap(new byte[] {
@@ -151,33 +187,14 @@
(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04 /* Address: 1.2.3.4 */
};
- /** Enable/disable tethering once before running the tests. */
- @BeforeClass
- public static void setUpOnce() throws Exception {
- // The first test case may experience tethering restart with IP conflict handling.
- // Tethering would cache the last upstreams so that the next enabled tethering avoids
- // picking up the address that is in conflict with the upstreams. To protect subsequent
- // tests, turn tethering on and off before running them.
- MyTetheringEventCallback callback = null;
- TestNetworkInterface testIface = null;
- assumeTrue(sEm != null);
- try {
- // If the physical ethernet interface is available, do nothing.
- if (isInterfaceForTetheringAvailable()) return;
-
- testIface = createTestInterface();
- setIncludeTestInterfaces(true);
-
- callback = enableEthernetTethering(testIface.getInterfaceName(), null);
- callback.awaitUpstreamChanged(true /* throwTimeoutException */);
- } catch (TimeoutException e) {
- Log.d(TAG, "WARNNING " + e);
- } finally {
- maybeCloseTestInterface(testIface);
- maybeUnregisterTetheringEventCallback(callback);
-
- setIncludeTestInterfaces(false);
- }
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ // TODO: See b/318121782#comment4. Register an ethernet InterfaceStateListener, and wait for
+ // the callback to report client mode. This happens as soon as both
+ // TetheredInterfaceRequester and the tethering code itself have released the interface,
+ // i.e. after stopTethering() has completed.
+ Thread.sleep(3000);
}
@Test
@@ -201,7 +218,7 @@
Log.d(TAG, "Including test interfaces");
setIncludeTestInterfaces(true);
- final String iface = getTetheredInterface();
+ final String iface = mTetheredInterfaceRequester.getInterface();
assertEquals("TetheredInterfaceCallback for unexpected interface",
downstreamIface.getInterfaceName(), iface);
@@ -223,8 +240,6 @@
// This test requires manipulating packets. Skip if there is a physical Ethernet connected.
assumeFalse(isInterfaceForTetheringAvailable());
- CompletableFuture<String> futureIface = requestTetheredInterface();
-
setIncludeTestInterfaces(true);
TestNetworkInterface downstreamIface = null;
@@ -234,7 +249,7 @@
try {
downstreamIface = createTestInterface();
- final String iface = futureIface.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ final String iface = mTetheredInterfaceRequester.getInterface();
assertEquals("TetheredInterfaceCallback for unexpected interface",
downstreamIface.getInterfaceName(), iface);
@@ -264,7 +279,7 @@
try {
downstreamIface = createTestInterface();
- final String iface = getTetheredInterface();
+ final String iface = mTetheredInterfaceRequester.getInterface();
assertEquals("TetheredInterfaceCallback for unexpected interface",
downstreamIface.getInterfaceName(), iface);
@@ -338,7 +353,7 @@
try {
downstreamIface = createTestInterface();
- final String iface = getTetheredInterface();
+ final String iface = mTetheredInterfaceRequester.getInterface();
assertEquals("TetheredInterfaceCallback for unexpected interface",
downstreamIface.getInterfaceName(), iface);
@@ -388,7 +403,7 @@
MyTetheringEventCallback tetheringEventCallback = null;
try {
// Get an interface to use.
- final String iface = getTetheredInterface();
+ final String iface = mTetheredInterfaceRequester.getInterface();
// Enable Ethernet tethering and check that it starts.
tetheringEventCallback = enableEthernetTethering(iface, null /* any upstream */);
@@ -509,17 +524,23 @@
// TODO: test BPF offload maps {rule, stats}.
}
- // Test network topology:
- //
- // public network (rawip) private network
- // | UE |
- // +------------+ V +------------+------------+ V +------------+
- // | Sever +---------+ Upstream | Downstream +---------+ Client |
- // +------------+ +------------+------------+ +------------+
- // remote ip public ip private ip
- // 8.8.8.8:443 <Upstream ip>:9876 <TetheredDevice ip>:9876
- //
- private void runUdp4Test() throws Exception {
+
+ /**
+ * Basic IPv4 UDP tethering test. Verify that UDP tethered packets are transferred no matter
+ * using which data path.
+ */
+ @Test
+ public void testTetherUdpV4() throws Exception {
+ // Test network topology:
+ //
+ // public network (rawip) private network
+ // | UE |
+ // +------------+ V +------------+------------+ V +------------+
+ // | Sever +---------+ Upstream | Downstream +---------+ Client |
+ // +------------+ +------------+------------+ +------------+
+ // remote ip public ip private ip
+ // 8.8.8.8:443 <Upstream ip>:9876 <TetheredDevice ip>:9876
+ //
final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
toList(TEST_IP4_DNS));
final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
@@ -541,15 +562,6 @@
sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
}
- /**
- * Basic IPv4 UDP tethering test. Verify that UDP tethered packets are transferred no matter
- * using which data path.
- */
- @Test
- public void testTetherUdpV4() throws Exception {
- runUdp4Test();
- }
-
// Test network topology:
//
// public network (rawip) private network
@@ -599,7 +611,7 @@
final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
// TODO: remove the connectivity verification for upstream connected notification race.
- // See the same reason in runUdp4Test().
+ // See the same reason in testTetherUdp4().
probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
final ByteBuffer request = buildIcmpEchoPacketV4(tethered.macAddr /* srcMac */,
@@ -707,7 +719,7 @@
final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
// TODO: remove the connectivity verification for upstream connected notification race.
- // See the same reason in runUdp4Test().
+ // See the same reason in testTetherUdp4().
probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
// [1] Send DNS query.
@@ -751,7 +763,7 @@
final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
// TODO: remove the connectivity verification for upstream connected notification race.
- // See the same reason in runUdp4Test().
+ // See the same reason in testTetherUdp4().
probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */,
@@ -828,4 +840,217 @@
final MacAddress macAddress = MacAddress.fromString("11:22:33:44:55:66");
assertTrue(tester.testDhcpServerAlive(macAddress));
}
+
+ private static boolean isUdpOffloadSupportedByKernel(final String kernelVersion) {
+ final KVersion current = DeviceInfoUtils.getMajorMinorSubminorVersion(kernelVersion);
+ return current.isInRange(new KVersion(4, 14, 222), new KVersion(4, 19, 0))
+ || current.isInRange(new KVersion(4, 19, 176), new KVersion(5, 4, 0))
+ || current.isAtLeast(new KVersion(5, 4, 98));
+ }
+
+ @Test
+ public void testIsUdpOffloadSupportedByKernel() throws Exception {
+ assertFalse(isUdpOffloadSupportedByKernel("4.14.221"));
+ assertTrue(isUdpOffloadSupportedByKernel("4.14.222"));
+ assertTrue(isUdpOffloadSupportedByKernel("4.16.0"));
+ assertTrue(isUdpOffloadSupportedByKernel("4.18.0"));
+ assertFalse(isUdpOffloadSupportedByKernel("4.19.0"));
+
+ assertFalse(isUdpOffloadSupportedByKernel("4.19.175"));
+ assertTrue(isUdpOffloadSupportedByKernel("4.19.176"));
+ assertTrue(isUdpOffloadSupportedByKernel("5.2.0"));
+ assertTrue(isUdpOffloadSupportedByKernel("5.3.0"));
+ assertFalse(isUdpOffloadSupportedByKernel("5.4.0"));
+
+ assertFalse(isUdpOffloadSupportedByKernel("5.4.97"));
+ assertTrue(isUdpOffloadSupportedByKernel("5.4.98"));
+ assertTrue(isUdpOffloadSupportedByKernel("5.10.0"));
+ }
+
+ private static void assumeKernelSupportBpfOffloadUdpV4() {
+ final String kernelVersion = VintfRuntimeInfo.getKernelVersion();
+ assumeTrue("Kernel version " + kernelVersion + " doesn't support IPv4 UDP BPF offload",
+ isUdpOffloadSupportedByKernel(kernelVersion));
+ }
+
+ @Test
+ public void testKernelSupportBpfOffloadUdpV4() throws Exception {
+ assumeKernelSupportBpfOffloadUdpV4();
+ }
+
+ private boolean isTetherConfigBpfOffloadEnabled() throws Exception {
+ final String dumpStr = runAsShell(DUMP, () ->
+ DumpTestUtils.dumpService(Context.TETHERING_SERVICE, "--short"));
+
+ // BPF offload tether config can be overridden by "config_tether_enable_bpf_offload" in
+ // packages/modules/Connectivity/Tethering/res/values/config.xml. OEM may disable config by
+ // RRO to override the enabled default value. Get the tethering config via dumpsys.
+ // $ dumpsys tethering
+ // mIsBpfEnabled: true
+ boolean enabled = dumpStr.contains("mIsBpfEnabled: true");
+ if (!enabled) {
+ Log.d(TAG, "BPF offload tether config not enabled: " + dumpStr);
+ }
+ return enabled;
+ }
+
+ @Test
+ public void testTetherConfigBpfOffloadEnabled() throws Exception {
+ assumeTrue(isTetherConfigBpfOffloadEnabled());
+ }
+
+ @NonNull
+ private <K extends Struct, V extends Struct> HashMap<K, V> dumpAndParseRawMap(
+ Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
+ throws Exception {
+ final String[] args = new String[] {DUMPSYS_TETHERING_RAWMAP_ARG, mapArg};
+ final String rawMapStr = runAsShell(DUMP, () ->
+ DumpTestUtils.dumpService(Context.TETHERING_SERVICE, args));
+ final HashMap<K, V> map = new HashMap<>();
+
+ for (final String line : rawMapStr.split(LINE_DELIMITER)) {
+ final Pair<K, V> rule =
+ BpfDump.fromBase64EncodedString(keyClass, valueClass, line.trim());
+ map.put(rule.first, rule.second);
+ }
+ return map;
+ }
+
+ @Nullable
+ private <K extends Struct, V extends Struct> HashMap<K, V> pollRawMapFromDump(
+ Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
+ throws Exception {
+ for (int retryCount = 0; retryCount < DUMP_POLLING_MAX_RETRY; retryCount++) {
+ final HashMap<K, V> map = dumpAndParseRawMap(keyClass, valueClass, mapArg);
+ if (!map.isEmpty()) return map;
+
+ Thread.sleep(DUMP_POLLING_INTERVAL_MS);
+ }
+
+ fail("Cannot get rules after " + DUMP_POLLING_MAX_RETRY * DUMP_POLLING_INTERVAL_MS + "ms");
+ return null;
+ }
+
+ // Test network topology:
+ //
+ // public network (rawip) private network
+ // | UE |
+ // +------------+ V +------------+------------+ V +------------+
+ // | Sever +---------+ Upstream | Downstream +---------+ Client |
+ // +------------+ +------------+------------+ +------------+
+ // remote ip public ip private ip
+ // 8.8.8.8:443 <Upstream ip>:9876 <TetheredDevice ip>:9876
+ //
+ private void runUdp4Test() throws Exception {
+ final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
+ toList(TEST_IP4_DNS));
+ final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
+
+ // TODO: remove the connectivity verification for upstream connected notification race.
+ // Because async upstream connected notification can't guarantee the tethering routing is
+ // ready to use. Need to test tethering connectivity before testing.
+ // For short term plan, consider using IPv6 RA to get MAC address because the prefix comes
+ // from upstream. That can guarantee that the routing is ready. Long term plan is that
+ // refactors upstream connected notification from async to sync.
+ probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
+
+ final MacAddress srcMac = tethered.macAddr;
+ final MacAddress dstMac = tethered.routerMacAddr;
+ final InetAddress remoteIp = REMOTE_IP4_ADDR;
+ final InetAddress tetheringUpstreamIp = TEST_IP4_ADDR.getAddress();
+ final InetAddress clientIp = tethered.ipv4Addr;
+ sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */);
+ sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
+
+ // Send second UDP packet in original direction.
+ // The BPF coordinator only offloads the ASSURED conntrack entry. The "request + reply"
+ // packets can make status IPS_SEEN_REPLY to be set. Need one more packet to make
+ // conntrack status IPS_ASSURED_BIT to be set. Note the third packet needs to delay
+ // 2 seconds because kernel monitors a UDP connection which still alive after 2 seconds
+ // and apply ASSURED flag.
+ // See kernel upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5 and
+ // nf_conntrack_udp_packet in net/netfilter/nf_conntrack_proto_udp.c
+ Thread.sleep(UDP_STREAM_TS_MS);
+ sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */);
+
+ // Give a slack time for handling conntrack event in user space.
+ Thread.sleep(UDP_STREAM_SLACK_MS);
+
+ // [1] Verify IPv4 upstream rule map.
+ final HashMap<Tether4Key, Tether4Value> upstreamMap = pollRawMapFromDump(
+ Tether4Key.class, Tether4Value.class, DUMPSYS_RAWMAP_ARG_UPSTREAM4);
+ assertNotNull(upstreamMap);
+ assertEquals(1, upstreamMap.size());
+
+ final Map.Entry<Tether4Key, Tether4Value> rule =
+ upstreamMap.entrySet().iterator().next();
+
+ final Tether4Key upstream4Key = rule.getKey();
+ assertEquals(IPPROTO_UDP, upstream4Key.l4proto);
+ assertTrue(Arrays.equals(tethered.ipv4Addr.getAddress(), upstream4Key.src4));
+ assertEquals(LOCAL_PORT, upstream4Key.srcPort);
+ assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(), upstream4Key.dst4));
+ assertEquals(REMOTE_PORT, upstream4Key.dstPort);
+
+ final Tether4Value upstream4Value = rule.getValue();
+ assertTrue(Arrays.equals(tetheringUpstreamIp.getAddress(),
+ InetAddress.getByAddress(upstream4Value.src46).getAddress()));
+ assertEquals(LOCAL_PORT, upstream4Value.srcPort);
+ assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(),
+ InetAddress.getByAddress(upstream4Value.dst46).getAddress()));
+ assertEquals(REMOTE_PORT, upstream4Value.dstPort);
+
+ // [2] Verify stats map.
+ // Transmit packets on both direction for verifying stats. Because we only care the
+ // packet count in stats test, we just reuse the existing packets to increaes
+ // the packet count on both direction.
+
+ // Send packets on original direction.
+ for (int i = 0; i < TX_UDP_PACKET_COUNT; i++) {
+ sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester,
+ false /* is4To6 */);
+ }
+
+ // Send packets on reply direction.
+ for (int i = 0; i < RX_UDP_PACKET_COUNT; i++) {
+ sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
+ }
+
+ // Dump stats map to verify.
+ final HashMap<TetherStatsKey, TetherStatsValue> statsMap = pollRawMapFromDump(
+ TetherStatsKey.class, TetherStatsValue.class, DUMPSYS_RAWMAP_ARG_STATS);
+ assertNotNull(statsMap);
+ assertEquals(1, statsMap.size());
+
+ final Map.Entry<TetherStatsKey, TetherStatsValue> stats =
+ statsMap.entrySet().iterator().next();
+
+ // TODO: verify the upstream index in TetherStatsKey.
+
+ final TetherStatsValue statsValue = stats.getValue();
+ assertEquals(RX_UDP_PACKET_COUNT, statsValue.rxPackets);
+ assertEquals(RX_UDP_PACKET_COUNT * RX_UDP_PACKET_SIZE, statsValue.rxBytes);
+ assertEquals(0, statsValue.rxErrors);
+ assertEquals(TX_UDP_PACKET_COUNT, statsValue.txPackets);
+ assertEquals(TX_UDP_PACKET_COUNT * TX_UDP_PACKET_SIZE, statsValue.txBytes);
+ assertEquals(0, statsValue.txErrors);
+ }
+
+ /**
+ * BPF offload IPv4 UDP tethering test. Verify that UDP tethered packets are offloaded by BPF.
+ * Minimum test requirement:
+ * 1. S+ device.
+ * 2. Tethering config enables tethering BPF offload.
+ * 3. Kernel supports IPv4 UDP BPF offload. See #isUdpOffloadSupportedByKernel.
+ *
+ * TODO: consider enabling the test even tethering config disables BPF offload. See b/238288883
+ */
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testTetherBpfOffloadUdpV4() throws Exception {
+ assumeTrue("Tethering config disabled BPF offload", isTetherConfigBpfOffloadEnabled());
+ assumeKernelSupportBpfOffloadUdpV4();
+
+ runUdp4Test();
+ }
}
diff --git a/Tethering/tests/mts/src/android/tethering/mts/MtsEthernetTetheringTest.java b/Tethering/tests/mts/src/android/tethering/mts/MtsEthernetTetheringTest.java
deleted file mode 100644
index c2bc812..0000000
--- a/Tethering/tests/mts/src/android/tethering/mts/MtsEthernetTetheringTest.java
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * 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 android.Manifest.permission.DUMP;
-import static android.system.OsConstants.IPPROTO_UDP;
-
-import static com.android.testutils.DeviceInfoUtils.KVersion;
-import static com.android.testutils.TestPermissionUtil.runAsShell;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
-import android.content.Context;
-import android.net.TetheringTester.TetheredDevice;
-import android.os.Build;
-import android.os.VintfRuntimeInfo;
-import android.util.Log;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.net.module.util.BpfDump;
-import com.android.net.module.util.Struct;
-import com.android.net.module.util.bpf.Tether4Key;
-import com.android.net.module.util.bpf.Tether4Value;
-import com.android.net.module.util.bpf.TetherStatsKey;
-import com.android.net.module.util.bpf.TetherStatsValue;
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
-import com.android.testutils.DeviceInfoUtils;
-import com.android.testutils.DumpTestUtils;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.net.InetAddress;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-
-@RunWith(AndroidJUnit4.class)
-@MediumTest
-public class MtsEthernetTetheringTest extends EthernetTetheringTestBase {
- @Rule
- public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
-
- private static final String TAG = MtsEthernetTetheringTest.class.getSimpleName();
-
- private static final int DUMP_POLLING_MAX_RETRY = 100;
- private static final int DUMP_POLLING_INTERVAL_MS = 50;
- // Kernel treats a confirmed UDP connection which active after two seconds as stream mode.
- // See upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5.
- private static final int UDP_STREAM_TS_MS = 2000;
- // Give slack time for waiting UDP stream mode because handling conntrack event in user space
- // may not in precise time. Used to reduce the flaky rate.
- private static final int UDP_STREAM_SLACK_MS = 500;
- // Per RX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
- private static final int RX_UDP_PACKET_SIZE = 30;
- private static final int RX_UDP_PACKET_COUNT = 456;
- // Per TX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
- private static final int TX_UDP_PACKET_SIZE = 30;
- private static final int TX_UDP_PACKET_COUNT = 123;
-
- private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
- private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
- private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
- private static final String LINE_DELIMITER = "\\n";
-
- private static boolean isUdpOffloadSupportedByKernel(final String kernelVersion) {
- final KVersion current = DeviceInfoUtils.getMajorMinorSubminorVersion(kernelVersion);
- return current.isInRange(new KVersion(4, 14, 222), new KVersion(4, 19, 0))
- || current.isInRange(new KVersion(4, 19, 176), new KVersion(5, 4, 0))
- || current.isAtLeast(new KVersion(5, 4, 98));
- }
-
- @Test
- public void testIsUdpOffloadSupportedByKernel() throws Exception {
- assertFalse(isUdpOffloadSupportedByKernel("4.14.221"));
- assertTrue(isUdpOffloadSupportedByKernel("4.14.222"));
- assertTrue(isUdpOffloadSupportedByKernel("4.16.0"));
- assertTrue(isUdpOffloadSupportedByKernel("4.18.0"));
- assertFalse(isUdpOffloadSupportedByKernel("4.19.0"));
-
- assertFalse(isUdpOffloadSupportedByKernel("4.19.175"));
- assertTrue(isUdpOffloadSupportedByKernel("4.19.176"));
- assertTrue(isUdpOffloadSupportedByKernel("5.2.0"));
- assertTrue(isUdpOffloadSupportedByKernel("5.3.0"));
- assertFalse(isUdpOffloadSupportedByKernel("5.4.0"));
-
- assertFalse(isUdpOffloadSupportedByKernel("5.4.97"));
- assertTrue(isUdpOffloadSupportedByKernel("5.4.98"));
- assertTrue(isUdpOffloadSupportedByKernel("5.10.0"));
- }
-
- private static void assumeKernelSupportBpfOffloadUdpV4() {
- final String kernelVersion = VintfRuntimeInfo.getKernelVersion();
- assumeTrue("Kernel version " + kernelVersion + " doesn't support IPv4 UDP BPF offload",
- isUdpOffloadSupportedByKernel(kernelVersion));
- }
-
- @Test
- public void testKernelSupportBpfOffloadUdpV4() throws Exception {
- assumeKernelSupportBpfOffloadUdpV4();
- }
-
- private boolean isTetherConfigBpfOffloadEnabled() throws Exception {
- final String dumpStr = runAsShell(DUMP, () ->
- DumpTestUtils.dumpService(Context.TETHERING_SERVICE, "--short"));
-
- // BPF offload tether config can be overridden by "config_tether_enable_bpf_offload" in
- // packages/modules/Connectivity/Tethering/res/values/config.xml. OEM may disable config by
- // RRO to override the enabled default value. Get the tethering config via dumpsys.
- // $ dumpsys tethering
- // mIsBpfEnabled: true
- boolean enabled = dumpStr.contains("mIsBpfEnabled: true");
- if (!enabled) {
- Log.d(TAG, "BPF offload tether config not enabled: " + dumpStr);
- }
- return enabled;
- }
-
- @Test
- public void testTetherConfigBpfOffloadEnabled() throws Exception {
- assumeTrue(isTetherConfigBpfOffloadEnabled());
- }
-
- @NonNull
- private <K extends Struct, V extends Struct> HashMap<K, V> dumpAndParseRawMap(
- Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
- throws Exception {
- final String[] args = new String[] {DUMPSYS_TETHERING_RAWMAP_ARG, mapArg};
- final String rawMapStr = runAsShell(DUMP, () ->
- DumpTestUtils.dumpService(Context.TETHERING_SERVICE, args));
- final HashMap<K, V> map = new HashMap<>();
-
- for (final String line : rawMapStr.split(LINE_DELIMITER)) {
- final Pair<K, V> rule =
- BpfDump.fromBase64EncodedString(keyClass, valueClass, line.trim());
- map.put(rule.first, rule.second);
- }
- return map;
- }
-
- @Nullable
- private <K extends Struct, V extends Struct> HashMap<K, V> pollRawMapFromDump(
- Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
- throws Exception {
- for (int retryCount = 0; retryCount < DUMP_POLLING_MAX_RETRY; retryCount++) {
- final HashMap<K, V> map = dumpAndParseRawMap(keyClass, valueClass, mapArg);
- if (!map.isEmpty()) return map;
-
- Thread.sleep(DUMP_POLLING_INTERVAL_MS);
- }
-
- fail("Cannot get rules after " + DUMP_POLLING_MAX_RETRY * DUMP_POLLING_INTERVAL_MS + "ms");
- return null;
- }
-
- // Test network topology:
- //
- // public network (rawip) private network
- // | UE |
- // +------------+ V +------------+------------+ V +------------+
- // | Sever +---------+ Upstream | Downstream +---------+ Client |
- // +------------+ +------------+------------+ +------------+
- // remote ip public ip private ip
- // 8.8.8.8:443 <Upstream ip>:9876 <TetheredDevice ip>:9876
- //
- private void runUdp4Test() throws Exception {
- final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
- toList(TEST_IP4_DNS));
- final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
-
- // TODO: remove the connectivity verification for upstream connected notification race.
- // Because async upstream connected notification can't guarantee the tethering routing is
- // ready to use. Need to test tethering connectivity before testing.
- // For short term plan, consider using IPv6 RA to get MAC address because the prefix comes
- // from upstream. That can guarantee that the routing is ready. Long term plan is that
- // refactors upstream connected notification from async to sync.
- probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
-
- final MacAddress srcMac = tethered.macAddr;
- final MacAddress dstMac = tethered.routerMacAddr;
- final InetAddress remoteIp = REMOTE_IP4_ADDR;
- final InetAddress tetheringUpstreamIp = TEST_IP4_ADDR.getAddress();
- final InetAddress clientIp = tethered.ipv4Addr;
- sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */);
- sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
-
- // Send second UDP packet in original direction.
- // The BPF coordinator only offloads the ASSURED conntrack entry. The "request + reply"
- // packets can make status IPS_SEEN_REPLY to be set. Need one more packet to make
- // conntrack status IPS_ASSURED_BIT to be set. Note the third packet needs to delay
- // 2 seconds because kernel monitors a UDP connection which still alive after 2 seconds
- // and apply ASSURED flag.
- // See kernel upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5 and
- // nf_conntrack_udp_packet in net/netfilter/nf_conntrack_proto_udp.c
- Thread.sleep(UDP_STREAM_TS_MS);
- sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */);
-
- // Give a slack time for handling conntrack event in user space.
- Thread.sleep(UDP_STREAM_SLACK_MS);
-
- // [1] Verify IPv4 upstream rule map.
- final HashMap<Tether4Key, Tether4Value> upstreamMap = pollRawMapFromDump(
- Tether4Key.class, Tether4Value.class, DUMPSYS_RAWMAP_ARG_UPSTREAM4);
- assertNotNull(upstreamMap);
- assertEquals(1, upstreamMap.size());
-
- final Map.Entry<Tether4Key, Tether4Value> rule =
- upstreamMap.entrySet().iterator().next();
-
- final Tether4Key upstream4Key = rule.getKey();
- assertEquals(IPPROTO_UDP, upstream4Key.l4proto);
- assertTrue(Arrays.equals(tethered.ipv4Addr.getAddress(), upstream4Key.src4));
- assertEquals(LOCAL_PORT, upstream4Key.srcPort);
- assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(), upstream4Key.dst4));
- assertEquals(REMOTE_PORT, upstream4Key.dstPort);
-
- final Tether4Value upstream4Value = rule.getValue();
- assertTrue(Arrays.equals(tetheringUpstreamIp.getAddress(),
- InetAddress.getByAddress(upstream4Value.src46).getAddress()));
- assertEquals(LOCAL_PORT, upstream4Value.srcPort);
- assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(),
- InetAddress.getByAddress(upstream4Value.dst46).getAddress()));
- assertEquals(REMOTE_PORT, upstream4Value.dstPort);
-
- // [2] Verify stats map.
- // Transmit packets on both direction for verifying stats. Because we only care the
- // packet count in stats test, we just reuse the existing packets to increaes
- // the packet count on both direction.
-
- // Send packets on original direction.
- for (int i = 0; i < TX_UDP_PACKET_COUNT; i++) {
- sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester,
- false /* is4To6 */);
- }
-
- // Send packets on reply direction.
- for (int i = 0; i < RX_UDP_PACKET_COUNT; i++) {
- sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
- }
-
- // Dump stats map to verify.
- final HashMap<TetherStatsKey, TetherStatsValue> statsMap = pollRawMapFromDump(
- TetherStatsKey.class, TetherStatsValue.class, DUMPSYS_RAWMAP_ARG_STATS);
- assertNotNull(statsMap);
- assertEquals(1, statsMap.size());
-
- final Map.Entry<TetherStatsKey, TetherStatsValue> stats =
- statsMap.entrySet().iterator().next();
-
- // TODO: verify the upstream index in TetherStatsKey.
-
- final TetherStatsValue statsValue = stats.getValue();
- assertEquals(RX_UDP_PACKET_COUNT, statsValue.rxPackets);
- assertEquals(RX_UDP_PACKET_COUNT * RX_UDP_PACKET_SIZE, statsValue.rxBytes);
- assertEquals(0, statsValue.rxErrors);
- assertEquals(TX_UDP_PACKET_COUNT, statsValue.txPackets);
- assertEquals(TX_UDP_PACKET_COUNT * TX_UDP_PACKET_SIZE, statsValue.txBytes);
- assertEquals(0, statsValue.txErrors);
- }
-
- /**
- * BPF offload IPv4 UDP tethering test. Verify that UDP tethered packets are offloaded by BPF.
- * Minimum test requirement:
- * 1. S+ device.
- * 2. Tethering config enables tethering BPF offload.
- * 3. Kernel supports IPv4 UDP BPF offload. See #isUdpOffloadSupportedByKernel.
- *
- * TODO: consider enabling the test even tethering config disables BPF offload. See b/238288883
- */
- @Test
- @IgnoreUpTo(Build.VERSION_CODES.R)
- public void testTetherBpfOffloadUdpV4() throws Exception {
- assumeTrue("Tethering config disabled BPF offload", isTetherConfigBpfOffloadEnabled());
- assumeKernelSupportBpfOffloadUdpV4();
-
- runUdp4Test();
- }
-}
diff --git a/Tethering/tests/privileged/Android.bp b/Tethering/tests/privileged/Android.bp
index ba6be66..3597a91 100644
--- a/Tethering/tests/privileged/Android.bp
+++ b/Tethering/tests/privileged/Android.bp
@@ -53,4 +53,5 @@
"TetheringApiCurrentLib",
],
compile_multilib: "both",
+ min_sdk_version: "30",
}
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index a7064e8..177296a 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -51,9 +51,9 @@
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@@ -74,7 +74,6 @@
import android.net.LinkProperties;
import android.net.MacAddress;
import android.net.RouteInfo;
-import android.net.RoutingCoordinatorManager;
import android.net.dhcp.DhcpServerCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.IDhcpEventCallbacks;
@@ -92,9 +91,9 @@
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.RoutingCoordinatorManager;
import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
-import com.android.net.module.util.ip.IpNeighborMonitor;
import com.android.networkstack.tethering.BpfCoordinator;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
import com.android.networkstack.tethering.TetheringConfiguration;
@@ -174,11 +173,9 @@
@Mock private IDhcpServer mDhcpServer;
@Mock private DadProxy mDadProxy;
@Mock private RouterAdvertisementDaemon mRaDaemon;
- @Mock private IpNeighborMonitor mIpNeighborMonitor;
@Mock private IpServer.Dependencies mDependencies;
@Mock private PrivateAddressCoordinator mAddressCoordinator;
- private final LateSdk<RoutingCoordinatorManager> mRoutingCoordinatorManager =
- new LateSdk<>(SdkLevel.isAtLeastS() ? mock(RoutingCoordinatorManager.class) : null);
+ @Mock private RoutingCoordinatorManager mRoutingCoordinatorManager;
@Mock private NetworkStatsManager mStatsManager;
@Mock private TetheringConfiguration mTetherConfig;
@Mock private TetheringMetrics mTetheringMetrics;
@@ -213,20 +210,17 @@
mInterfaceConfiguration.prefixLength = BLUETOOTH_DHCP_PREFIX_LENGTH;
}
- doReturn(mIpNeighborMonitor).when(mDependencies).getIpNeighborMonitor(any(), any(), any());
-
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(usingBpfOffload);
when(mTetherConfig.useLegacyDhcpServer()).thenReturn(usingLegacyDhcp);
when(mTetherConfig.getP2pLeasesSubnetPrefixLength()).thenReturn(P2P_SUBNET_PREFIX_LENGTH);
when(mBpfCoordinator.isUsingBpfOffload()).thenReturn(usingBpfOffload);
mIpServer = createIpServer(interfaceType);
- verify(mIpNeighborMonitor).start();
mIpServer.start();
// Starting the state machine always puts us in a consistent state and notifies
// the rest of the world that we've changed from an unknown to available state.
mLooper.dispatchAll();
- reset(mNetd, mCallback, mIpNeighborMonitor);
+ reset(mNetd, mCallback);
when(mRaDaemon.start()).thenReturn(true);
}
@@ -242,6 +236,7 @@
throws Exception {
initStateMachine(interfaceType, usingLegacyDhcp, usingBpfOffload);
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
+ verify(mBpfCoordinator).addIpServer(mIpServer);
if (upstreamIface != null) {
InterfaceParams interfaceParams = mDependencies.getInterfaceParams(upstreamIface);
assertNotNull("missing upstream interface: " + upstreamIface, interfaceParams);
@@ -250,8 +245,12 @@
lp.setLinkAddresses(upstreamAddresses);
dispatchTetherConnectionChanged(upstreamIface, lp, 0);
Set<IpPrefix> upstreamPrefixes = getTetherableIpv6Prefixes(lp.getLinkAddresses());
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, interfaceParams.index, upstreamPrefixes);
+ // One is called when handling CMD_TETHER_CONNECTION_CHANGED and the other one is called
+ // when upstream's LinkProperties is updated (updateUpstreamIPv6LinkProperties)
+ verify(mBpfCoordinator, times(2)).maybeAddUpstreamToLookupTable(
+ interfaceParams.index, upstreamIface);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, interfaceParams.index, upstreamPrefixes);
}
reset(mNetd, mBpfCoordinator, mCallback, mAddressCoordinator);
when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(),
@@ -281,24 +280,6 @@
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(DEFAULT_USING_BPF_OFFLOAD);
when(mTetherConfig.useLegacyDhcpServer()).thenReturn(false /* default value */);
- // Simulate the behavior of RoutingCoordinator
- if (null != mRoutingCoordinatorManager.value) {
- doAnswer(it -> {
- final String fromIface = (String) it.getArguments()[0];
- final String toIface = (String) it.getArguments()[1];
- mNetd.tetherAddForward(fromIface, toIface);
- mNetd.ipfwdAddInterfaceForward(fromIface, toIface);
- return null;
- }).when(mRoutingCoordinatorManager.value).addInterfaceForward(any(), any());
- doAnswer(it -> {
- final String fromIface = (String) it.getArguments()[0];
- final String toIface = (String) it.getArguments()[1];
- mNetd.ipfwdRemoveInterfaceForward(fromIface, toIface);
- mNetd.tetherRemoveForward(fromIface, toIface);
- return null;
- }).when(mRoutingCoordinatorManager.value).removeInterfaceForward(any(), any());
- }
-
setUpDhcpServer();
}
@@ -314,8 +295,6 @@
@Test
public void startsOutAvailable() throws Exception {
- when(mDependencies.getIpNeighborMonitor(any(), any(), any()))
- .thenReturn(mIpNeighborMonitor);
mIpServer = createIpServer(TETHERING_BLUETOOTH);
mIpServer.start();
mLooper.dispatchAll();
@@ -458,107 +437,116 @@
// Telling the state machine about its upstream interface triggers
// a little more configuration.
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
- InOrder inOrder = inOrder(mNetd, mBpfCoordinator);
+ InOrder inOrder = inOrder(mBpfCoordinator, mRoutingCoordinatorManager);
// Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>.
inOrder.verify(mBpfCoordinator).maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX,
UPSTREAM_IFACE);
inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mRoutingCoordinatorManager).addInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
- verifyNoMoreInteractions(mNetd, mCallback, mBpfCoordinator);
+ verifyNoMoreInteractions(mCallback, mBpfCoordinator, mRoutingCoordinatorManager);
}
@Test
public void handlesChangingUpstream() throws Exception {
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
+ clearInvocations(mBpfCoordinator, mRoutingCoordinatorManager);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
- InOrder inOrder = inOrder(mNetd, mBpfCoordinator);
+ InOrder inOrder = inOrder(mBpfCoordinator, mRoutingCoordinatorManager);
// Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>.
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mRoutingCoordinatorManager)
+ .removeInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
// Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2>.
inOrder.verify(mBpfCoordinator).maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX2,
UPSTREAM_IFACE2);
inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mRoutingCoordinatorManager).addInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
- verifyNoMoreInteractions(mNetd, mCallback, mBpfCoordinator);
+ verifyNoMoreInteractions(mCallback, mBpfCoordinator, mRoutingCoordinatorManager);
}
@Test
public void handlesChangingUpstreamNatFailure() throws Exception {
initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
- doThrow(RemoteException.class).when(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
+ doThrow(RuntimeException.class)
+ .when(mRoutingCoordinatorManager)
+ .addInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
- InOrder inOrder = inOrder(mNetd, mBpfCoordinator);
+ InOrder inOrder = inOrder(mBpfCoordinator, mRoutingCoordinatorManager);
// Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>.
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mRoutingCoordinatorManager)
+ .removeInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
// Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> and expect that failed on
- // tetherAddForward.
+ // addInterfaceForward.
inOrder.verify(mBpfCoordinator).maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX2,
UPSTREAM_IFACE2);
inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mRoutingCoordinatorManager).addInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
// Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> to fallback.
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mRoutingCoordinatorManager)
+ .removeInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
}
@Test
public void handlesChangingUpstreamInterfaceForwardingFailure() throws Exception {
initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
- doThrow(RemoteException.class).when(mNetd).ipfwdAddInterfaceForward(
- IFACE_NAME, UPSTREAM_IFACE2);
+ doThrow(RuntimeException.class)
+ .when(mRoutingCoordinatorManager)
+ .addInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
- InOrder inOrder = inOrder(mNetd, mBpfCoordinator);
+ InOrder inOrder = inOrder(mBpfCoordinator, mRoutingCoordinatorManager);
// Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>.
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mRoutingCoordinatorManager)
+ .removeInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
// Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> and expect that failed on
// ipfwdAddInterfaceForward.
inOrder.verify(mBpfCoordinator).maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX2,
UPSTREAM_IFACE2);
inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mRoutingCoordinatorManager).addInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
// Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> to fallback.
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mRoutingCoordinatorManager)
+ .removeInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
}
@Test
public void canUnrequestTetheringWithUpstream() throws Exception {
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
+ clearInvocations(
+ mNetd, mCallback, mAddressCoordinator, mBpfCoordinator, mRoutingCoordinatorManager);
dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
- InOrder inOrder = inOrder(mNetd, mCallback, mAddressCoordinator, mBpfCoordinator);
+ InOrder inOrder =
+ inOrder(
+ mNetd,
+ mCallback,
+ mAddressCoordinator,
+ mBpfCoordinator,
+ mRoutingCoordinatorManager);
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ inOrder.verify(mRoutingCoordinatorManager)
+ .removeInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, NO_UPSTREAM, NO_PREFIXES);
// When tethering stops, upstream interface is set to zero and thus clearing all upstream
// rules. Downstream rules are needed to be cleared explicitly by calling
// BpfCoordinator#clearAllIpv6Rules in TetheredState#exit.
@@ -570,12 +558,13 @@
argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
inOrder.verify(mAddressCoordinator).releaseDownstream(any());
inOrder.verify(mBpfCoordinator).tetherOffloadClientClear(mIpServer);
- inOrder.verify(mBpfCoordinator).stopMonitoring(mIpServer);
+ inOrder.verify(mBpfCoordinator).removeIpServer(mIpServer);
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), any(LinkProperties.class));
- verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator, mBpfCoordinator);
+ verifyNoMoreInteractions(
+ mNetd, mCallback, mAddressCoordinator, mBpfCoordinator, mRoutingCoordinatorManager);
}
@Test
@@ -627,10 +616,14 @@
public void shouldTearDownUsbOnUpstreamError() throws Exception {
initTetheredStateMachine(TETHERING_USB, null);
- doThrow(RemoteException.class).when(mNetd).tetherAddForward(anyString(), anyString());
+ doThrow(RuntimeException.class)
+ .when(mRoutingCoordinatorManager)
+ .addInterfaceForward(anyString(), anyString());
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
- InOrder usbTeardownOrder = inOrder(mNetd, mCallback);
- usbTeardownOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE);
+ InOrder usbTeardownOrder = inOrder(mNetd, mCallback, mRoutingCoordinatorManager);
+ usbTeardownOrder
+ .verify(mRoutingCoordinatorManager)
+ .addInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
usbTeardownOrder.verify(mNetd, times(2)).interfaceSetCfg(
argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
@@ -765,8 +758,8 @@
lp.setInterfaceName(UPSTREAM_IFACE2);
lp.setLinkAddresses(UPSTREAM_ADDRESSES);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp, -1);
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES);
reset(mBpfCoordinator);
// Upstream link addresses change result in updating the rules.
@@ -774,8 +767,8 @@
lp2.setInterfaceName(UPSTREAM_IFACE2);
lp2.setLinkAddresses(UPSTREAM_ADDRESSES2);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp2, -1);
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2);
reset(mBpfCoordinator);
// When the upstream is lost, rules are removed.
@@ -784,53 +777,54 @@
// - processMessage CMD_TETHER_CONNECTION_CHANGED for the upstream is lost.
// - processMessage CMD_IPV6_TETHER_UPDATE for the IPv6 upstream is lost.
// See dispatchTetherConnectionChanged.
- verify(mBpfCoordinator, times(2)).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ verify(mBpfCoordinator, times(2)).updateIpv6UpstreamInterface(
+ mIpServer, NO_UPSTREAM, NO_PREFIXES);
reset(mBpfCoordinator);
// If the upstream is IPv4-only, no rules are added.
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
- verify(mBpfCoordinator, never()).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ verify(mBpfCoordinator, never()).updateIpv6UpstreamInterface(
+ mIpServer, NO_UPSTREAM, NO_PREFIXES);
reset(mBpfCoordinator);
// Rules are added again once upstream IPv6 connectivity is available.
lp.setInterfaceName(UPSTREAM_IFACE);
dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1);
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
reset(mBpfCoordinator);
// If upstream IPv6 connectivity is lost, rules are removed.
dispatchTetherConnectionChanged(UPSTREAM_IFACE, null, 0);
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, NO_UPSTREAM, NO_PREFIXES);
reset(mBpfCoordinator);
// When upstream IPv6 connectivity comes back, rules are added.
lp.setInterfaceName(UPSTREAM_IFACE);
dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1);
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
reset(mBpfCoordinator);
// When the downstream interface goes down, rules are removed.
mIpServer.stop();
mLooper.dispatchAll();
verify(mBpfCoordinator).clearAllIpv6Rules(mIpServer);
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ verify(mBpfCoordinator).removeIpServer(mIpServer);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, NO_UPSTREAM, NO_PREFIXES);
reset(mBpfCoordinator);
}
@Test
- public void stopNeighborMonitoringWhenInterfaceDown() throws Exception {
+ public void removeIpServerWhenInterfaceDown() throws Exception {
initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, UPSTREAM_ADDRESSES,
false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD);
mIpServer.stop();
mLooper.dispatchAll();
- verify(mIpNeighborMonitor).stop();
+ verify(mBpfCoordinator).removeIpServer(mIpServer);
}
private LinkProperties buildIpv6OnlyLinkProperties(final String iface) {
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 47ecf58..e54a7e0 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -25,8 +25,6 @@
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
import static android.net.NetworkStats.UID_TETHERING;
-import static android.net.TetheringManager.TETHERING_WIFI;
-import static android.net.ip.IpServer.STATE_TETHERED;
import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
import static android.system.OsConstants.ETH_P_IP;
import static android.system.OsConstants.ETH_P_IPV6;
@@ -79,9 +77,7 @@
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
@@ -100,11 +96,9 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkStats;
-import android.net.RoutingCoordinatorManager;
import android.net.TetherOffloadRuleParcel;
import android.net.TetherStatsParcel;
import android.net.ip.IpServer;
-import android.net.ip.RouterAdvertisementDaemon;
import android.os.Build;
import android.os.Handler;
import android.os.test.TestLooper;
@@ -119,12 +113,10 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.NetworkStackConstants;
-import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.Struct.S32;
import com.android.net.module.util.bpf.Tether4Key;
@@ -143,8 +135,6 @@
import com.android.networkstack.tethering.BpfCoordinator.ClientInfo;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6DownstreamRule;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6UpstreamRule;
-import com.android.networkstack.tethering.metrics.TetheringMetrics;
-import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -209,11 +199,6 @@
private static final MacAddress MAC_B = MacAddress.fromString("11:22:33:00:00:0b");
private static final MacAddress MAC_NULL = MacAddress.fromString("00:00:00:00:00:00");
- private static final LinkAddress UPSTREAM_ADDRESS = new LinkAddress("2001:db8:0:1234::168/64");
- private static final LinkAddress UPSTREAM_ADDRESS2 = new LinkAddress("2001:db8:0:abcd::168/64");
- private static final Set<LinkAddress> UPSTREAM_ADDRESSES = Set.of(UPSTREAM_ADDRESS);
- private static final Set<LinkAddress> UPSTREAM_ADDRESSES2 =
- Set.of(UPSTREAM_ADDRESS, UPSTREAM_ADDRESS2);
private static final IpPrefix UPSTREAM_PREFIX = new IpPrefix("2001:db8:0:1234::/64");
private static final IpPrefix UPSTREAM_PREFIX2 = new IpPrefix("2001:db8:0:abcd::/64");
private static final Set<IpPrefix> UPSTREAM_PREFIXES = Set.of(UPSTREAM_PREFIX);
@@ -449,13 +434,6 @@
@Mock private TetheringConfiguration mTetherConfig;
@Mock private ConntrackMonitor mConntrackMonitor;
@Mock private IpNeighborMonitor mIpNeighborMonitor;
- @Mock private RouterAdvertisementDaemon mRaDaemon;
- @Mock private IpServer.Dependencies mIpServerDeps;
- @Mock private IpServer.Callback mIpServerCallback;
- @Mock private PrivateAddressCoordinator mAddressCoordinator;
- private final LateSdk<RoutingCoordinatorManager> mRoutingCoordinatorManager =
- new LateSdk<>(SdkLevel.isAtLeastS() ? mock(RoutingCoordinatorManager.class) : null);
- @Mock private TetheringMetrics mTetheringMetrics;
// Late init since methods must be called by the thread that created this object.
private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb;
@@ -573,24 +551,8 @@
@Before public void setUp() {
MockitoAnnotations.initMocks(this);
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(true /* default value */);
-
- // Simulate the behavior of RoutingCoordinator
- if (null != mRoutingCoordinatorManager.value) {
- doAnswer(it -> {
- final String fromIface = (String) it.getArguments()[0];
- final String toIface = (String) it.getArguments()[1];
- mNetd.tetherAddForward(fromIface, toIface);
- mNetd.ipfwdAddInterfaceForward(fromIface, toIface);
- return null;
- }).when(mRoutingCoordinatorManager.value).addInterfaceForward(any(), any());
- doAnswer(it -> {
- final String fromIface = (String) it.getArguments()[0];
- final String toIface = (String) it.getArguments()[1];
- mNetd.ipfwdRemoveInterfaceForward(fromIface, toIface);
- mNetd.tetherRemoveForward(fromIface, toIface);
- return null;
- }).when(mRoutingCoordinatorManager.value).removeInterfaceForward(any(), any());
- }
+ when(mIpServer.getInterfaceParams()).thenReturn(DOWNSTREAM_IFACE_PARAMS);
+ when(mIpServer2.getInterfaceParams()).thenReturn(DOWNSTREAM_IFACE_PARAMS2);
}
private void waitForIdle() {
@@ -603,70 +565,39 @@
when(mNetd.tetherOffloadGetStats()).thenReturn(new TetherStatsParcel[0]);
}
- @NonNull
- private IpServer makeAndStartIpServer(String interfaceName, BpfCoordinator bpfCoordinator)
- throws Exception {
- final LinkAddress testAddress = new LinkAddress("192.168.42.5/24");
- when(mIpServerDeps.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon);
- when(mIpServerDeps.getInterfaceParams(DOWNSTREAM_IFACE)).thenReturn(
- DOWNSTREAM_IFACE_PARAMS);
- when(mIpServerDeps.getInterfaceParams(UPSTREAM_IFACE)).thenReturn(UPSTREAM_IFACE_PARAMS);
- when(mIpServerDeps.getInterfaceParams(UPSTREAM_IFACE2)).thenReturn(UPSTREAM_IFACE_PARAMS2);
- when(mIpServerDeps.getInterfaceParams(IPSEC_IFACE)).thenReturn(IPSEC_IFACE_PARAMS);
- when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(),
- anyBoolean())).thenReturn(testAddress);
- when(mRaDaemon.start()).thenReturn(true);
- ArgumentCaptor<NeighborEventConsumer> neighborEventCaptor =
- ArgumentCaptor.forClass(NeighborEventConsumer.class);
- doReturn(mIpNeighborMonitor).when(mIpServerDeps).getIpNeighborMonitor(any(), any(),
- neighborEventCaptor.capture());
- final IpServer ipServer = new IpServer(
- interfaceName, mHandler, TETHERING_WIFI, new SharedLog("test"), mNetd,
- bpfCoordinator, mRoutingCoordinatorManager, mIpServerCallback, mTetherConfig,
- mAddressCoordinator, mTetheringMetrics, mIpServerDeps);
- ipServer.start();
- ipServer.sendMessage(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
- mTestLooper.dispatchAll();
-
- LinkProperties lp = new LinkProperties();
- lp.setInterfaceName(UPSTREAM_IFACE);
- lp.setLinkAddresses(UPSTREAM_ADDRESSES);
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE, lp, 0);
-
- mNeighborEventConsumer = neighborEventCaptor.getValue();
- return ipServer;
- }
-
- private void dispatchTetherConnectionChanged(IpServer ipServer, String upstreamIface,
- LinkProperties v6lp, int ttlAdjustment) {
- dispatchTetherConnectionChanged(ipServer, upstreamIface);
- ipServer.sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, ttlAdjustment, 0, v6lp);
- mTestLooper.dispatchAll();
- }
-
- private void dispatchTetherConnectionChanged(IpServer ipServer, String upstreamIface) {
- final InterfaceSet ifs = (upstreamIface != null) ? new InterfaceSet(upstreamIface) : null;
- ipServer.sendMessage(IpServer.CMD_TETHER_CONNECTION_CHANGED, ifs);
- mTestLooper.dispatchAll();
+ private void dispatchIpv6UpstreamChanged(BpfCoordinator bpfCoordinator, IpServer ipServer,
+ int upstreamIfindex, String upstreamIface, Set<IpPrefix> upstreamPrefixes) {
+ bpfCoordinator.maybeAddUpstreamToLookupTable(upstreamIfindex, upstreamIface);
+ bpfCoordinator.updateIpv6UpstreamInterface(ipServer, upstreamIfindex, upstreamPrefixes);
+ when(ipServer.getIpv6UpstreamIfindex()).thenReturn(upstreamIfindex);
+ when(ipServer.getIpv6UpstreamPrefixes()).thenReturn(upstreamPrefixes);
}
private void recvNewNeigh(int ifindex, InetAddress addr, short nudState, MacAddress mac) {
mNeighborEventConsumer.accept(new NeighborEvent(0, RTM_NEWNEIGH, ifindex, addr,
nudState, mac));
- mTestLooper.dispatchAll();
}
private void recvDelNeigh(int ifindex, InetAddress addr, short nudState, MacAddress mac) {
mNeighborEventConsumer.accept(new NeighborEvent(0, RTM_DELNEIGH, ifindex, addr,
nudState, mac));
- mTestLooper.dispatchAll();
}
@NonNull
private BpfCoordinator makeBpfCoordinator() throws Exception {
+ return makeBpfCoordinator(true /* addDefaultIpServer */);
+ }
+
+ @NonNull
+ private BpfCoordinator makeBpfCoordinator(boolean addDefaultIpServer) throws Exception {
// mStatsManager will be invoked twice if BpfCoordinator is created the second time.
clearInvocations(mStatsManager);
+ ArgumentCaptor<NeighborEventConsumer> neighborCaptor =
+ ArgumentCaptor.forClass(NeighborEventConsumer.class);
+ doReturn(mIpNeighborMonitor).when(mDeps).getIpNeighborMonitor(neighborCaptor.capture());
final BpfCoordinator coordinator = new BpfCoordinator(mDeps);
+ mNeighborEventConsumer = neighborCaptor.getValue();
+ assertNotNull(mNeighborEventConsumer);
mConsumer = coordinator.getBpfConntrackEventConsumerForTesting();
mTetherClients = coordinator.getTetherClientsForTesting();
@@ -681,6 +612,10 @@
mTetherStatsProviderCb = new TestableNetworkStatsProviderCbBinder();
mTetherStatsProvider.setProviderCallbackBinder(mTetherStatsProviderCb);
+ if (addDefaultIpServer) {
+ coordinator.addIpServer(mIpServer);
+ }
+
return coordinator;
}
@@ -1008,7 +943,6 @@
final String mobileIface = "rmnet_data0";
final Integer mobileIfIndex = 100;
- coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface);
// InOrder is required because mBpfStatsMap may be accessed by both
// BpfCoordinator#tetherOffloadRuleAdd and BpfCoordinator#tetherOffloadGetAndClearStats.
@@ -1020,19 +954,18 @@
mobileIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC);
final Ipv6DownstreamRule downstreamRule = buildTestDownstreamRule(
mobileIfIndex, NEIGH_A, MAC_A);
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex, UPSTREAM_PREFIXES);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, mobileIfIndex, mobileIface, UPSTREAM_PREFIXES);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
verifyAddUpstreamRule(inOrder, upstreamRule);
- coordinator.addIpv6DownstreamRule(mIpServer, downstreamRule);
+ recvNewNeigh(DOWNSTREAM_IFINDEX, NEIGH_A, NUD_REACHABLE, MAC_A);
verifyAddDownstreamRule(inOrder, downstreamRule);
// Removing the last rule on current upstream immediately sends the cleanup stuff to BPF.
updateStatsEntryForTetherOffloadGetAndClearStats(
buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0));
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer, NO_UPSTREAM, null, NO_PREFIXES);
verifyRemoveDownstreamRule(inOrder, downstreamRule);
verifyRemoveUpstreamRule(inOrder, upstreamRule);
verifyTetherOffloadGetAndClearStats(inOrder, mobileIfIndex);
@@ -1056,7 +989,6 @@
doReturn(usingApiS).when(mDeps).isAtLeastS();
final BpfCoordinator coordinator = makeBpfCoordinator();
- coordinator.startPolling();
final String mobileIface = "rmnet_data0";
final Integer mobileIfIndex = 100;
@@ -1092,7 +1024,6 @@
setupFunctioningNetdInterface();
final BpfCoordinator coordinator = makeBpfCoordinator();
- coordinator.startPolling();
final String wlanIface = "wlan0";
final Integer wlanIfIndex = 100;
@@ -1148,7 +1079,7 @@
// [3] Stop coordinator.
// Shutdown the coordinator and clear the invocation history, especially the
// tetherOffloadGetStats() calls.
- coordinator.stopPolling();
+ coordinator.removeIpServer(mIpServer);
clearStatsInvocations();
// Verify the polling update thread stopped.
@@ -1162,7 +1093,6 @@
setupFunctioningNetdInterface();
final BpfCoordinator coordinator = makeBpfCoordinator();
- coordinator.startPolling();
final String mobileIface = "rmnet_data0";
final Integer mobileIfIndex = 100;
@@ -1350,7 +1280,6 @@
final String mobileIface = "rmnet_data0";
final int mobileIfIndex = 100;
- coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface);
// [1] Default limit.
// Set the unlimited quota as default if the service has never applied a data limit for a
@@ -1358,8 +1287,8 @@
final Ipv6UpstreamRule rule = buildTestUpstreamRule(
mobileIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC);
final InOrder inOrder = inOrder(mNetd, mBpfUpstream6Map, mBpfLimitMap, mBpfStatsMap);
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex, UPSTREAM_PREFIXES);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, mobileIfIndex, mobileIface, UPSTREAM_PREFIXES);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
verifyAddUpstreamRule(inOrder, rule);
@@ -1395,7 +1324,6 @@
final String mobileIface = "rmnet_data0";
final int mobileIfIndex = 100;
- coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface);
// Applying a data limit to the current upstream does not take any immediate action.
// The data limit could be only set on an upstream which has rules.
@@ -1408,31 +1336,30 @@
// Adding the first rule on current upstream immediately sends the quota to BPF.
final Ipv6UpstreamRule ruleA = buildTestUpstreamRule(
mobileIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC);
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex, UPSTREAM_PREFIXES);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, mobileIfIndex, mobileIface, UPSTREAM_PREFIXES);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, limit, true /* isInit */);
verifyAddUpstreamRule(inOrder, ruleA);
inOrder.verifyNoMoreInteractions();
// Adding the second rule on current upstream does not send the quota to BPF.
+ coordinator.addIpServer(mIpServer2);
final Ipv6UpstreamRule ruleB = buildTestUpstreamRule(
mobileIfIndex, DOWNSTREAM_IFINDEX2, UPSTREAM_PREFIX, DOWNSTREAM_MAC2);
- coordinator.updateAllIpv6Rules(
- mIpServer2, DOWNSTREAM_IFACE_PARAMS2, mobileIfIndex, UPSTREAM_PREFIXES);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer2, mobileIfIndex, mobileIface, UPSTREAM_PREFIXES);
verifyAddUpstreamRule(inOrder, ruleB);
verifyNeverTetherOffloadSetInterfaceQuota(inOrder);
// Removing the second rule on current upstream does not send the quota to BPF.
- coordinator.updateAllIpv6Rules(
- mIpServer2, DOWNSTREAM_IFACE_PARAMS2, NO_UPSTREAM, NO_PREFIXES);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer2, NO_UPSTREAM, null, NO_PREFIXES);
verifyRemoveUpstreamRule(inOrder, ruleB);
verifyNeverTetherOffloadSetInterfaceQuota(inOrder);
// Removing the last rule on current upstream immediately sends the cleanup stuff to BPF.
updateStatsEntryForTetherOffloadGetAndClearStats(
buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0));
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer, NO_UPSTREAM, null, NO_PREFIXES);
verifyRemoveUpstreamRule(inOrder, ruleA);
verifyTetherOffloadGetAndClearStats(inOrder, mobileIfIndex);
inOrder.verifyNoMoreInteractions();
@@ -1448,8 +1375,6 @@
final String mobileIface = "rmnet_data0";
final Integer ethIfIndex = 100;
final Integer mobileIfIndex = 101;
- coordinator.maybeAddUpstreamToLookupTable(ethIfIndex, ethIface);
- coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface);
final InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfUpstream6Map, mBpfLimitMap,
mBpfStatsMap);
@@ -1470,14 +1395,14 @@
final Ipv6DownstreamRule ethernetRuleB = buildTestDownstreamRule(
ethIfIndex, NEIGH_B, MAC_B);
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, ethIfIndex, UPSTREAM_PREFIXES);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, ethIfIndex, ethIface, UPSTREAM_PREFIXES);
verifyTetherOffloadSetInterfaceQuota(inOrder, ethIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
verifyAddUpstreamRule(inOrder, ethernetUpstreamRule);
- coordinator.addIpv6DownstreamRule(mIpServer, ethernetRuleA);
+ recvNewNeigh(DOWNSTREAM_IFINDEX, NEIGH_A, NUD_REACHABLE, MAC_A);
verifyAddDownstreamRule(inOrder, ethernetRuleA);
- coordinator.addIpv6DownstreamRule(mIpServer, ethernetRuleB);
+ recvNewNeigh(DOWNSTREAM_IFINDEX, NEIGH_B, NUD_REACHABLE, MAC_B);
verifyAddDownstreamRule(inOrder, ethernetRuleB);
// [2] Update the existing rules from Ethernet to cellular.
@@ -1494,8 +1419,8 @@
// Update the existing rules for upstream changes. The rules are removed and re-added one
// by one for updating upstream interface index and prefixes by #tetherOffloadRuleUpdate.
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex, UPSTREAM_PREFIXES2);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, mobileIfIndex, mobileIface, UPSTREAM_PREFIXES2);
verifyRemoveDownstreamRule(inOrder, ethernetRuleA);
verifyRemoveDownstreamRule(inOrder, ethernetRuleB);
verifyRemoveUpstreamRule(inOrder, ethernetUpstreamRule);
@@ -1532,7 +1457,6 @@
// #makeBpfCoordinator for testing.
// See #testBpfDisabledbyNoBpfDownstream6Map.
final BpfCoordinator coordinator = makeBpfCoordinator();
- coordinator.startPolling();
// The tether stats polling task should not be scheduled.
mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
@@ -1549,7 +1473,7 @@
final InetAddress neigh = InetAddresses.parseNumericAddress("2001:db8::1");
final MacAddress mac = MacAddress.fromString("00:00:00:00:00:0a");
final Ipv6DownstreamRule rule = buildTestDownstreamRule(ifIndex, neigh, mac);
- coordinator.addIpv6DownstreamRule(mIpServer, rule);
+ recvNewNeigh(DOWNSTREAM_IFINDEX, neigh, NUD_REACHABLE, mac);
verifyNeverAddDownstreamRule();
LinkedHashMap<Inet6Address, Ipv6DownstreamRule> rules =
coordinator.getIpv6DownstreamRulesForTesting().get(mIpServer);
@@ -1561,7 +1485,7 @@
rules = new LinkedHashMap<Inet6Address, Ipv6DownstreamRule>();
rules.put(rule.address, rule);
coordinator.getIpv6DownstreamRulesForTesting().put(mIpServer, rules);
- coordinator.removeIpv6DownstreamRule(mIpServer, rule);
+ recvNewNeigh(DOWNSTREAM_IFINDEX, neigh, NUD_STALE, mac);
verifyNeverRemoveDownstreamRule();
rules = coordinator.getIpv6DownstreamRulesForTesting().get(mIpServer);
assertNotNull(rules);
@@ -1575,8 +1499,8 @@
assertEquals(1, rules.size());
// The rule can't be updated.
- coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS,
- rule.upstreamIfindex + 1 /* new */, UPSTREAM_PREFIXES);
+ coordinator.updateIpv6UpstreamInterface(mIpServer, rule.upstreamIfindex + 1 /* new */,
+ UPSTREAM_PREFIXES);
verifyNeverRemoveDownstreamRule();
verifyNeverAddDownstreamRule();
rules = coordinator.getIpv6DownstreamRulesForTesting().get(mIpServer);
@@ -1753,18 +1677,14 @@
final BpfCoordinator coordinator = makeBpfCoordinator();
// [1] The default polling interval.
- coordinator.startPolling();
assertEquals(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS, coordinator.getPollingInterval());
- coordinator.stopPolling();
// [2] Expect the invalid polling interval isn't applied. The valid range of interval is
// DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS..max_long.
for (final int interval
: new int[] {0, 100, DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS - 1}) {
when(mTetherConfig.getOffloadPollInterval()).thenReturn(interval);
- coordinator.startPolling();
assertEquals(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS, coordinator.getPollingInterval());
- coordinator.stopPolling();
}
// [3] Set a specific polling interval which is larger than default value.
@@ -1772,7 +1692,6 @@
// approximation is used to verify the scheduled time of the polling thread.
final int pollingInterval = 100_000;
when(mTetherConfig.getOffloadPollInterval()).thenReturn(pollingInterval);
- coordinator.startPolling();
// Expect the specific polling interval to be applied.
assertEquals(pollingInterval, coordinator.getPollingInterval());
@@ -1800,19 +1719,19 @@
public void testStartStopConntrackMonitoring() throws Exception {
setupFunctioningNetdInterface();
- final BpfCoordinator coordinator = makeBpfCoordinator();
+ final BpfCoordinator coordinator = makeBpfCoordinator(false /* addDefaultIpServer */);
// [1] Don't stop monitoring if it has never started.
- coordinator.stopMonitoring(mIpServer);
+ coordinator.removeIpServer(mIpServer);
verify(mConntrackMonitor, never()).stop();
// [2] Start monitoring.
- coordinator.startMonitoring(mIpServer);
+ coordinator.addIpServer(mIpServer);
verify(mConntrackMonitor).start();
clearInvocations(mConntrackMonitor);
// [3] Stop monitoring.
- coordinator.stopMonitoring(mIpServer);
+ coordinator.removeIpServer(mIpServer);
verify(mConntrackMonitor).stop();
}
@@ -1823,12 +1742,12 @@
public void testStartStopConntrackMonitoring_R() throws Exception {
setupFunctioningNetdInterface();
- final BpfCoordinator coordinator = makeBpfCoordinator();
+ final BpfCoordinator coordinator = makeBpfCoordinator(false /* addDefaultIpServer */);
- coordinator.startMonitoring(mIpServer);
+ coordinator.addIpServer(mIpServer);
verify(mConntrackMonitor, never()).start();
- coordinator.stopMonitoring(mIpServer);
+ coordinator.removeIpServer(mIpServer);
verify(mConntrackMonitor, never()).stop();
}
@@ -1837,23 +1756,23 @@
public void testStartStopConntrackMonitoringWithTwoDownstreamIfaces() throws Exception {
setupFunctioningNetdInterface();
- final BpfCoordinator coordinator = makeBpfCoordinator();
+ final BpfCoordinator coordinator = makeBpfCoordinator(false /* addDefaultIpServer */);
// [1] Start monitoring at the first IpServer adding.
- coordinator.startMonitoring(mIpServer);
+ coordinator.addIpServer(mIpServer);
verify(mConntrackMonitor).start();
clearInvocations(mConntrackMonitor);
// [2] Don't start monitoring at the second IpServer adding.
- coordinator.startMonitoring(mIpServer2);
+ coordinator.addIpServer(mIpServer2);
verify(mConntrackMonitor, never()).start();
// [3] Don't stop monitoring if any downstream interface exists.
- coordinator.stopMonitoring(mIpServer2);
+ coordinator.removeIpServer(mIpServer2);
verify(mConntrackMonitor, never()).stop();
// [4] Stop monitoring if no downstream exists.
- coordinator.stopMonitoring(mIpServer);
+ coordinator.removeIpServer(mIpServer);
verify(mConntrackMonitor).stop();
}
@@ -2016,9 +1935,8 @@
public void testAddDevMapRule6() throws Exception {
final BpfCoordinator coordinator = makeBpfCoordinator();
- coordinator.maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE);
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(UPSTREAM_IFINDEX)),
eq(new TetherDevValue(UPSTREAM_IFINDEX)));
verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(DOWNSTREAM_IFINDEX)),
@@ -2027,8 +1945,9 @@
// Adding the second downstream, only the second downstream ifindex is added to DevMap,
// the existing upstream ifindex won't be added again.
- coordinator.updateAllIpv6Rules(
- mIpServer2, DOWNSTREAM_IFACE_PARAMS2, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
+ coordinator.addIpServer(mIpServer2);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer2, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(DOWNSTREAM_IFINDEX2)),
eq(new TetherDevValue(DOWNSTREAM_IFINDEX2)));
verify(mBpfDevMap, never()).updateEntry(eq(new TetherDevKey(UPSTREAM_IFINDEX)),
@@ -2087,7 +2006,6 @@
.startMocking();
try {
final BpfCoordinator coordinator = makeBpfCoordinator();
- coordinator.startPolling();
bpfMap.insertEntry(tcpKey, tcpValue);
bpfMap.insertEntry(udpKey, udpValue);
@@ -2116,7 +2034,7 @@
ExtendedMockito.clearInvocations(staticMockMarker(NetlinkUtils.class));
// [3] Don't refresh conntrack timeout if polling stopped.
- coordinator.stopPolling();
+ coordinator.removeIpServer(mIpServer);
mTestLooper.moveTimeForward(CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS);
waitForIdle();
ExtendedMockito.verifyNoMoreInteractions(staticMockMarker(NetlinkUtils.class));
@@ -2855,8 +2773,9 @@
final int myIfindex = DOWNSTREAM_IFINDEX;
final int notMyIfindex = myIfindex - 1;
final BpfCoordinator coordinator = makeBpfCoordinator();
- final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
resetNetdAndBpfMaps();
verifyNoMoreInteractions(mNetd, mBpfDownstream6Map, mBpfUpstream6Map);
@@ -2905,10 +2824,8 @@
resetNetdAndBpfMaps();
InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfUpstream6Map);
- LinkProperties lp = new LinkProperties();
- lp.setInterfaceName(UPSTREAM_IFACE2);
- lp.setLinkAddresses(UPSTREAM_ADDRESSES);
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE2, lp, -1);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX2, UPSTREAM_IFACE2, UPSTREAM_PREFIXES);
final Ipv6DownstreamRule ruleA2 = buildTestDownstreamRule(
UPSTREAM_IFINDEX2, NEIGH_A, MAC_A);
final Ipv6DownstreamRule ruleB2 = buildTestDownstreamRule(
@@ -2922,11 +2839,9 @@
verifyNoUpstreamIpv6ForwardingChange(inOrder);
resetNetdAndBpfMaps();
- // Upstream link addresses change result in updating the rules.
- LinkProperties lp2 = new LinkProperties();
- lp2.setInterfaceName(UPSTREAM_IFACE2);
- lp2.setLinkAddresses(UPSTREAM_ADDRESSES2);
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE2, lp2, -1);
+ // Upstream prefixes change result in updating the rules.
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX2, UPSTREAM_IFACE2, UPSTREAM_PREFIXES2);
verifyRemoveDownstreamRule(inOrder, ruleA2);
verifyRemoveDownstreamRule(inOrder, ruleB2);
verifyStopUpstreamIpv6Forwarding(inOrder, UPSTREAM_PREFIXES);
@@ -2936,7 +2851,7 @@
resetNetdAndBpfMaps();
// When the upstream is lost, rules are removed.
- dispatchTetherConnectionChanged(ipServer, null, null, 0);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer, NO_UPSTREAM, null, NO_PREFIXES);
verifyStopUpstreamIpv6Forwarding(inOrder, UPSTREAM_PREFIXES2);
verifyRemoveDownstreamRule(ruleA2);
verifyRemoveDownstreamRule(ruleB2);
@@ -2947,7 +2862,7 @@
resetNetdAndBpfMaps();
// If the upstream is IPv4-only, no IPv6 rules are added to BPF map.
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer, NO_UPSTREAM, null, NO_PREFIXES);
resetNetdAndBpfMaps();
recvNewNeigh(myIfindex, NEIGH_A, NUD_REACHABLE, MAC_A);
verifyNoUpstreamIpv6ForwardingChange(null);
@@ -2957,8 +2872,8 @@
// Rules can be added again once upstream IPv6 connectivity is available. The existing rules
// with an upstream of NO_UPSTREAM are reapplied.
- lp.setInterfaceName(UPSTREAM_IFACE);
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE, lp, -1);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
verifyAddDownstreamRule(ruleA);
recvNewNeigh(myIfindex, NEIGH_B, NUD_REACHABLE, MAC_B);
@@ -2966,23 +2881,27 @@
// If upstream IPv6 connectivity is lost, rules are removed.
resetNetdAndBpfMaps();
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE, null, 0);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer, NO_UPSTREAM, null, NO_PREFIXES);
verifyRemoveDownstreamRule(ruleA);
verifyRemoveDownstreamRule(ruleB);
verifyStopUpstreamIpv6Forwarding(null, UPSTREAM_PREFIXES);
// When upstream IPv6 connectivity comes back, upstream rules are added and downstream rules
// are reapplied.
- lp.setInterfaceName(UPSTREAM_IFACE);
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE, lp, -1);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
verifyAddDownstreamRule(ruleA);
verifyAddDownstreamRule(ruleB);
resetNetdAndBpfMaps();
// When the downstream interface goes down, rules are removed.
- ipServer.stop();
- mTestLooper.dispatchAll();
+ // Simulate receiving CMD_INTERFACE_DOWN in the BaseServingState of IpServer.
+ reset(mIpNeighborMonitor);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer, NO_UPSTREAM, null, NO_PREFIXES);
+ coordinator.tetherOffloadClientClear(mIpServer);
+ coordinator.removeIpServer(mIpServer);
+
verifyStopUpstreamIpv6Forwarding(null, UPSTREAM_PREFIXES);
verifyRemoveDownstreamRule(ruleA);
verifyRemoveDownstreamRule(ruleB);
@@ -3004,7 +2923,8 @@
// [1] Enable BPF offload.
// A neighbor that is added or deleted causes the rule to be added or removed.
final BpfCoordinator coordinator = makeBpfCoordinator();
- final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
resetNetdAndBpfMaps();
recvNewNeigh(myIfindex, NEIGH_A, NUD_REACHABLE, MAC_A);
@@ -3019,10 +2939,8 @@
resetNetdAndBpfMaps();
// Upstream IPv6 connectivity change causes upstream rules change.
- LinkProperties lp2 = new LinkProperties();
- lp2.setInterfaceName(UPSTREAM_IFACE2);
- lp2.setLinkAddresses(UPSTREAM_ADDRESSES2);
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE2, lp2, 0);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX2, UPSTREAM_IFACE2, UPSTREAM_PREFIXES2);
verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2);
resetNetdAndBpfMaps();
@@ -3030,7 +2948,6 @@
// A neighbor that is added or deleted doesn’t cause the rule to be added or removed.
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(false);
final BpfCoordinator coordinator2 = makeBpfCoordinator();
- final IpServer ipServer2 = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator2);
verifyNoUpstreamIpv6ForwardingChange(null);
resetNetdAndBpfMaps();
@@ -3043,7 +2960,8 @@
resetNetdAndBpfMaps();
// Upstream IPv6 connectivity change doesn't cause the rule to be added or removed.
- dispatchTetherConnectionChanged(ipServer2, UPSTREAM_IFACE2, lp2, 0);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer2, UPSTREAM_IFINDEX2, UPSTREAM_IFACE2, NO_PREFIXES);
verifyNoUpstreamIpv6ForwardingChange(null);
verifyNeverRemoveDownstreamRule();
resetNetdAndBpfMaps();
@@ -3053,7 +2971,6 @@
public void doesNotStartIpNeighborMonitorIfBpfOffloadDisabled() throws Exception {
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(false);
final BpfCoordinator coordinator = makeBpfCoordinator();
- final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator);
// IP neighbor monitor doesn't start if BPF offload is disabled.
verify(mIpNeighborMonitor, never()).start();
@@ -3062,15 +2979,10 @@
@Test
public void testSkipVirtualNetworkInBpf() throws Exception {
final BpfCoordinator coordinator = makeBpfCoordinator();
- final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator);
- final LinkProperties v6Only = new LinkProperties();
- v6Only.setInterfaceName(IPSEC_IFACE);
- v6Only.setLinkAddresses(UPSTREAM_ADDRESSES);
resetNetdAndBpfMaps();
- dispatchTetherConnectionChanged(ipServer, IPSEC_IFACE, v6Only, 0);
- verify(mNetd).tetherAddForward(DOWNSTREAM_IFACE, IPSEC_IFACE);
- verify(mNetd).ipfwdAddInterfaceForward(DOWNSTREAM_IFACE, IPSEC_IFACE);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, IPSEC_IFINDEX, IPSEC_IFACE, UPSTREAM_PREFIXES);
verifyNeverAddUpstreamRule();
recvNewNeigh(DOWNSTREAM_IFINDEX, NEIGH_A, NUD_REACHABLE, MAC_A);
@@ -3080,7 +2992,6 @@
@Test
public void addRemoveTetherClient() throws Exception {
final BpfCoordinator coordinator = makeBpfCoordinator();
- final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator);
final int myIfindex = DOWNSTREAM_IFINDEX;
final int notMyIfindex = myIfindex - 1;
@@ -3089,33 +3000,36 @@
final InetAddress neighLL = InetAddresses.parseNumericAddress("169.254.0.1");
final InetAddress neighMC = InetAddresses.parseNumericAddress("224.0.0.1");
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
+
// Events on other interfaces are ignored.
recvNewNeigh(notMyIfindex, neighA, NUD_REACHABLE, MAC_A);
- assertNull(mTetherClients.get(ipServer));
+ assertNull(mTetherClients.get(mIpServer));
// Events on this interface are received and sent to BpfCoordinator.
recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, MAC_A);
- assertClientInfoExists(ipServer,
+ assertClientInfoExists(mIpServer,
new ClientInfo(myIfindex, DOWNSTREAM_MAC, (Inet4Address) neighA, MAC_A));
recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, MAC_B);
- assertClientInfoExists(ipServer,
+ assertClientInfoExists(mIpServer,
new ClientInfo(myIfindex, DOWNSTREAM_MAC, (Inet4Address) neighB, MAC_B));
// Link-local and multicast neighbors are ignored.
recvNewNeigh(myIfindex, neighLL, NUD_REACHABLE, MAC_A);
- assertClientInfoDoesNotExist(ipServer, (Inet4Address) neighLL);
+ assertClientInfoDoesNotExist(mIpServer, (Inet4Address) neighLL);
recvNewNeigh(myIfindex, neighMC, NUD_REACHABLE, MAC_A);
- assertClientInfoDoesNotExist(ipServer, (Inet4Address) neighMC);
+ assertClientInfoDoesNotExist(mIpServer, (Inet4Address) neighMC);
// A neighbor that is no longer valid causes the client to be removed.
// NUD_FAILED events do not have a MAC address.
recvNewNeigh(myIfindex, neighA, NUD_FAILED, null);
- assertClientInfoDoesNotExist(ipServer, (Inet4Address) neighA);
+ assertClientInfoDoesNotExist(mIpServer, (Inet4Address) neighA);
// A neighbor that is deleted causes the client to be removed.
recvDelNeigh(myIfindex, neighB, NUD_STALE, MAC_B);
// When last client information is deleted, IpServer will be removed from mTetherClients
- assertNull(mTetherClients.get(ipServer));
+ assertNull(mTetherClients.get(mIpServer));
}
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
index da81bda..c0d7ad4 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
@@ -45,6 +45,7 @@
import android.net.ITetheringConnector;
import android.net.ITetheringEventCallback;
import android.net.TetheringManager;
+import android.net.TetheringManager.TetheringRequest;
import android.net.TetheringRequestParcel;
import android.net.ip.IpServer;
import android.os.Bundle;
@@ -311,7 +312,8 @@
result);
verify(mTethering).isTetheringSupported();
verify(mTethering).isTetheringAllowed();
- verify(mTethering).startTethering(eq(request), eq(TEST_CALLER_PKG), eq(result));
+ verify(mTethering).startTethering(
+ eq(new TetheringRequest(request)), eq(TEST_CALLER_PKG), eq(result));
}
@Test
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 9f430af..6ba5d48 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -142,7 +142,6 @@
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.RouteInfo;
-import android.net.RoutingCoordinatorManager;
import android.net.TetherStatesParcel;
import android.net.TetheredClient;
import android.net.TetheredClient.AddressInfo;
@@ -150,7 +149,7 @@
import android.net.TetheringConfigurationParcel;
import android.net.TetheringInterface;
import android.net.TetheringManager;
-import android.net.TetheringRequestParcel;
+import android.net.TetheringManager.TetheringRequest;
import android.net.dhcp.DhcpLeaseParcelable;
import android.net.dhcp.DhcpServerCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
@@ -191,7 +190,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.InterfaceParams;
-import com.android.net.module.util.SdkUtil.LateSdk;
+import com.android.net.module.util.RoutingCoordinatorManager;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.ip.IpNeighborMonitor;
import com.android.networkstack.apishim.common.BluetoothPanShim;
@@ -292,6 +291,7 @@
@Mock private BluetoothPanShim mBluetoothPanShim;
@Mock private TetheredInterfaceRequestShim mTetheredInterfaceRequestShim;
@Mock private TetheringMetrics mTetheringMetrics;
+ @Mock private RoutingCoordinatorManager mRoutingCoordinatorManager;
private final MockIpServerDependencies mIpServerDependencies =
spy(new MockIpServerDependencies());
@@ -484,10 +484,10 @@
return mEntitleMgr;
}
- @Nullable
@Override
- public LateSdk<RoutingCoordinatorManager> getRoutingCoordinator(final Context context) {
- return new LateSdk<>(null);
+ public RoutingCoordinatorManager getRoutingCoordinator(final Context context,
+ SharedLog log) {
+ return mRoutingCoordinatorManager;
}
@Override
@@ -498,7 +498,7 @@
}
@Override
- public INetd getINetd(Context context) {
+ public INetd getINetd(Context context, SharedLog log) {
return mNetd;
}
@@ -528,7 +528,7 @@
}
@Override
- public TetheringMetrics makeTetheringMetrics() {
+ public TetheringMetrics makeTetheringMetrics(Context ctx) {
return mTetheringMetrics;
}
@@ -743,22 +743,21 @@
doReturn(upstreamState).when(mUpstreamNetworkMonitor).selectPreferredUpstreamType(any());
}
- private TetheringRequestParcel createTetheringRequestParcel(final int type) {
- return createTetheringRequestParcel(type, null, null, false, CONNECTIVITY_SCOPE_GLOBAL);
+ private TetheringRequest createTetheringRequest(final int type) {
+ return createTetheringRequest(type, null, null, false, CONNECTIVITY_SCOPE_GLOBAL);
}
- private TetheringRequestParcel createTetheringRequestParcel(final int type,
- final LinkAddress serverAddr, final LinkAddress clientAddr, final boolean exempt,
- final int scope) {
- final TetheringRequestParcel request = new TetheringRequestParcel();
- request.tetheringType = type;
- request.localIPv4Address = serverAddr;
- request.staticClientAddress = clientAddr;
- request.exemptFromEntitlementCheck = exempt;
- request.showProvisioningUi = false;
- request.connectivityScope = scope;
-
- return request;
+ private TetheringRequest createTetheringRequest(final int type,
+ final LinkAddress localIPv4Address, final LinkAddress staticClientAddress,
+ final boolean exempt, final int scope) {
+ TetheringRequest.Builder builder = new TetheringRequest.Builder(type)
+ .setExemptFromEntitlementCheck(exempt)
+ .setConnectivityScope(scope)
+ .setShouldShowEntitlementUi(false);
+ if (localIPv4Address != null && staticClientAddress != null) {
+ builder.setStaticIpv4Addresses(localIPv4Address, staticClientAddress);
+ }
+ return builder.build();
}
@NonNull
@@ -911,7 +910,7 @@
private void prepareNcmTethering() {
// Emulate startTethering(TETHERING_NCM) called
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_NCM), TEST_CALLER_PKG,
null);
mLooper.dispatchAll();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NCM);
@@ -919,7 +918,7 @@
private void prepareUsbTethering() {
// Emulate pressing the USB tethering button in Settings UI.
- final TetheringRequestParcel request = createTetheringRequestParcel(TETHERING_USB);
+ final TetheringRequest request = createTetheringRequest(TETHERING_USB);
mTethering.startTethering(request, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
@@ -1083,8 +1082,8 @@
UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
runUsbTethering(upstreamState);
- verify(mNetd, times(1)).tetherAddForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mRoutingCoordinatorManager, times(1))
+ .addInterfaceForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
sendIPv6TetherUpdates(upstreamState);
assertSetIfaceToDadProxy(0 /* numOfCalls */, "" /* ifaceName */);
@@ -1112,8 +1111,8 @@
UpstreamNetworkState upstreamState = buildMobileIPv6UpstreamState();
runUsbTethering(upstreamState);
- verify(mNetd, times(1)).tetherAddForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mRoutingCoordinatorManager, times(1))
+ .addInterfaceForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
sendIPv6TetherUpdates(upstreamState);
// TODO: add interfaceParams to compare in verify.
@@ -1128,8 +1127,8 @@
UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
runUsbTethering(upstreamState);
- verify(mNetd, times(1)).tetherAddForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mRoutingCoordinatorManager, times(1))
+ .addInterfaceForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
verify(mRouterAdvertisementDaemon, times(1)).start();
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
@@ -1146,13 +1145,12 @@
UpstreamNetworkState upstreamState = buildMobile464xlatUpstreamState();
runUsbTethering(upstreamState);
- verify(mNetd, times(1)).tetherAddForward(TEST_RNDIS_IFNAME, TEST_XLAT_MOBILE_IFNAME);
- verify(mNetd, times(1)).tetherAddForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mRoutingCoordinatorManager, times(1))
+ .addInterfaceForward(TEST_RNDIS_IFNAME, TEST_XLAT_MOBILE_IFNAME);
+ verify(mRoutingCoordinatorManager, times(1))
+ .addInterfaceForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_RNDIS_IFNAME,
- TEST_XLAT_MOBILE_IFNAME);
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
sendIPv6TetherUpdates(upstreamState);
assertSetIfaceToDadProxy(1 /* numOfCalls */, TEST_MOBILE_IFNAME /* ifaceName */);
@@ -1173,10 +1171,10 @@
UpstreamNetworkState upstreamState = buildMobileIPv6UpstreamState();
runUsbTethering(upstreamState);
- verify(mNetd, times(1)).tetherAddForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mRoutingCoordinatorManager, times(1))
+ .addInterfaceForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
// Then 464xlat comes up
upstreamState = buildMobile464xlatUpstreamState();
@@ -1188,12 +1186,11 @@
mLooper.dispatchAll();
// Forwarding is added for 464xlat
- verify(mNetd, times(1)).tetherAddForward(TEST_NCM_IFNAME, TEST_XLAT_MOBILE_IFNAME);
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_NCM_IFNAME,
- TEST_XLAT_MOBILE_IFNAME);
+ verify(mRoutingCoordinatorManager, times(1))
+ .addInterfaceForward(TEST_NCM_IFNAME, TEST_XLAT_MOBILE_IFNAME);
// Forwarding was not re-added for v6 (still times(1))
- verify(mNetd, times(1)).tetherAddForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mRoutingCoordinatorManager, times(1))
+ .addInterfaceForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
// DHCP not restarted on downstream (still times(1))
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
@@ -1909,7 +1906,7 @@
when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
// Emulate pressing the WiFi tethering button.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
null);
mLooper.dispatchAll();
verify(mWifiManager, times(1)).startTetheredHotspot(null);
@@ -1938,7 +1935,7 @@
when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
// Emulate pressing the WiFi tethering button.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
null);
mLooper.dispatchAll();
verify(mWifiManager, times(1)).startTetheredHotspot(null);
@@ -1988,7 +1985,7 @@
doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME);
// Emulate pressing the WiFi tethering button.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
null);
mLooper.dispatchAll();
verify(mWifiManager, times(1)).startTetheredHotspot(null);
@@ -2334,7 +2331,7 @@
tetherState = callback.pollTetherStatesChanged();
assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
null);
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
tetherState = callback.pollTetherStatesChanged();
@@ -2430,11 +2427,11 @@
initTetheringOnTestThread();
final TetheredInterfaceRequest mockRequest = mock(TetheredInterfaceRequest.class);
when(mEm.requestTetheredInterface(any(), any())).thenReturn(mockRequest);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_ETHERNET), TEST_CALLER_PKG,
null);
mLooper.dispatchAll();
verify(mEm, times(1)).requestTetheredInterface(any(), any());
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_ETHERNET), TEST_CALLER_PKG,
null);
mLooper.dispatchAll();
verifyNoMoreInteractions(mEm);
@@ -2644,7 +2641,7 @@
final ResultListener thirdResult = new ResultListener(TETHER_ERROR_NO_ERROR);
// Enable USB tethering and check that Tethering starts USB.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_USB), TEST_CALLER_PKG,
firstResult);
mLooper.dispatchAll();
firstResult.assertHasResult();
@@ -2652,7 +2649,7 @@
verifyNoMoreInteractions(mUsbManager);
// Enable USB tethering again with the same request and expect no change to USB.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_USB), TEST_CALLER_PKG,
secondResult);
mLooper.dispatchAll();
secondResult.assertHasResult();
@@ -2661,7 +2658,7 @@
// Enable USB tethering with a different request and expect that USB is stopped and
// started.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
+ mTethering.startTethering(createTetheringRequest(TETHERING_USB,
serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL),
TEST_CALLER_PKG, thirdResult);
mLooper.dispatchAll();
@@ -2692,7 +2689,7 @@
final int clientAddrParceled = 0xc0a8002a;
final ArgumentCaptor<DhcpServingParamsParcel> dhcpParamsCaptor =
ArgumentCaptor.forClass(DhcpServingParamsParcel.class);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
+ mTethering.startTethering(createTetheringRequest(TETHERING_USB,
serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL),
TEST_CALLER_PKG, null);
mLooper.dispatchAll();
@@ -2820,8 +2817,8 @@
public void testExemptFromEntitlementCheck() throws Exception {
initTetheringOnTestThread();
setupForRequiredProvisioning();
- final TetheringRequestParcel wifiNotExemptRequest =
- createTetheringRequestParcel(TETHERING_WIFI, null, null, false,
+ final TetheringRequest wifiNotExemptRequest =
+ createTetheringRequest(TETHERING_WIFI, null, null, false,
CONNECTIVITY_SCOPE_GLOBAL);
mTethering.startTethering(wifiNotExemptRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
@@ -2834,8 +2831,8 @@
reset(mEntitleMgr);
setupForRequiredProvisioning();
- final TetheringRequestParcel wifiExemptRequest =
- createTetheringRequestParcel(TETHERING_WIFI, null, null, true,
+ final TetheringRequest wifiExemptRequest =
+ createTetheringRequest(TETHERING_WIFI, null, null, true,
CONNECTIVITY_SCOPE_GLOBAL);
mTethering.startTethering(wifiExemptRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
@@ -2954,7 +2951,7 @@
when(mEm.requestTetheredInterface(any(), any())).thenReturn(mockRequest);
final ArgumentCaptor<TetheredInterfaceCallback> callbackCaptor =
ArgumentCaptor.forClass(TetheredInterfaceCallback.class);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET),
+ mTethering.startTethering(createTetheringRequest(TETHERING_ETHERNET),
TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mEm).requestTetheredInterface(any(), callbackCaptor.capture());
@@ -3235,7 +3232,7 @@
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
+ mTethering.startTethering(createTetheringRequest(TETHERING_BLUETOOTH),
TEST_CALLER_PKG, result);
mLooper.dispatchAll();
verifySetBluetoothTethering(true /* enable */, true /* bindToPanService */);
@@ -3272,7 +3269,7 @@
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
+ mTethering.startTethering(createTetheringRequest(TETHERING_BLUETOOTH),
TEST_CALLER_PKG, result);
mLooper.dispatchAll();
verifySetBluetoothTethering(true /* enable */, true /* bindToPanService */);
@@ -3294,7 +3291,7 @@
// already bound.
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
final ResultListener secondResult = new ResultListener(TETHER_ERROR_NO_ERROR);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
+ mTethering.startTethering(createTetheringRequest(TETHERING_BLUETOOTH),
TEST_CALLER_PKG, secondResult);
mLooper.dispatchAll();
verifySetBluetoothTethering(true /* enable */, false /* bindToPanService */);
@@ -3317,7 +3314,7 @@
initTetheringOnTestThread();
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
+ mTethering.startTethering(createTetheringRequest(TETHERING_BLUETOOTH),
TEST_CALLER_PKG, result);
mLooper.dispatchAll();
ServiceListener panListener = verifySetBluetoothTethering(true /* enable */,
@@ -3436,8 +3433,7 @@
runUsbTethering(upstreamState);
verify(mNetd).interfaceGetList();
- verify(mNetd).tetherAddForward(expectedIface, TEST_MOBILE_IFNAME);
- verify(mNetd).ipfwdAddInterfaceForward(expectedIface, TEST_MOBILE_IFNAME);
+ verify(mRoutingCoordinatorManager).addInterfaceForward(expectedIface, TEST_MOBILE_IFNAME);
verify(mRouterAdvertisementDaemon).start();
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
@@ -3487,7 +3483,7 @@
// If TETHERING_USB is forced to use ncm function, TETHERING_NCM would no longer be
// available.
final ResultListener ncmResult = new ResultListener(TETHER_ERROR_SERVICE_UNAVAIL);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_NCM), TEST_CALLER_PKG,
ncmResult);
mLooper.dispatchAll();
ncmResult.assertHasResult();
@@ -3638,7 +3634,7 @@
when(mWifiManager.startTetheredHotspot(any())).thenReturn(true);
// Emulate pressing the WiFi tethering button.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
null);
mLooper.dispatchAll();
verify(mWifiManager).startTetheredHotspot(null);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
index e2c924c..7cef9cb 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
@@ -46,10 +46,11 @@
import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED;
import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
+import android.content.Context;
import android.net.NetworkCapabilities;
import android.stats.connectivity.DownstreamType;
import android.stats.connectivity.ErrorCode;
@@ -60,10 +61,12 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.networkstack.tethering.UpstreamNetworkState;
+import com.android.networkstack.tethering.metrics.TetheringMetrics.Dependencies;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
@@ -76,28 +79,23 @@
private static final long TEST_START_TIME = 1670395936033L;
private static final long SECOND_IN_MILLIS = 1_000L;
+ @Mock private Context mContext;
+ @Mock private Dependencies mDeps;
+
private TetheringMetrics mTetheringMetrics;
private final NetworkTetheringReported.Builder mStatsBuilder =
NetworkTetheringReported.newBuilder();
private long mElapsedRealtime;
- private class MockTetheringMetrics extends TetheringMetrics {
- @Override
- public void write(final NetworkTetheringReported reported) {}
- @Override
- public long timeNow() {
- return currentTimeMillis();
- }
- }
-
private long currentTimeMillis() {
return TEST_START_TIME + mElapsedRealtime;
}
private void incrementCurrentTime(final long duration) {
mElapsedRealtime += duration;
- mTetheringMetrics.timeNow();
+ final long currentTimeMillis = currentTimeMillis();
+ doReturn(currentTimeMillis).when(mDeps).timeNow();
}
private long getElapsedRealtime() {
@@ -106,12 +104,14 @@
private void clearElapsedRealtime() {
mElapsedRealtime = 0;
+ doReturn(TEST_START_TIME).when(mDeps).timeNow();
}
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mTetheringMetrics = spy(new MockTetheringMetrics());
+ doReturn(TEST_START_TIME).when(mDeps).timeNow();
+ mTetheringMetrics = new TetheringMetrics(mContext, mDeps);
mElapsedRealtime = 0L;
}
@@ -126,7 +126,7 @@
.setUpstreamEvents(upstreamEvents)
.setDurationMillis(duration)
.build();
- verify(mTetheringMetrics).write(expectedReport);
+ verify(mDeps).write(expectedReport);
}
private void updateErrorAndSendReport(final int downstream, final int error) {
@@ -162,6 +162,7 @@
private void runDownstreamTypesTest(final int type, final DownstreamType expectedResult)
throws Exception {
+ mTetheringMetrics = new TetheringMetrics(mContext, mDeps);
mTetheringMetrics.createBuilder(type, TEST_CALLER_PKG);
final long duration = 2 * SECOND_IN_MILLIS;
incrementCurrentTime(duration);
@@ -172,9 +173,7 @@
verifyReport(expectedResult, ErrorCode.EC_NO_ERROR, UserType.USER_UNKNOWN,
upstreamEvents, getElapsedRealtime());
- reset(mTetheringMetrics);
clearElapsedRealtime();
- mTetheringMetrics.cleanup();
}
@Test
@@ -189,6 +188,7 @@
private void runErrorCodesTest(final int errorCode, final ErrorCode expectedResult)
throws Exception {
+ mTetheringMetrics = new TetheringMetrics(mContext, mDeps);
mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG);
mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_WIFI));
final long duration = 2 * SECOND_IN_MILLIS;
@@ -199,9 +199,7 @@
addUpstreamEvent(upstreamEvents, UpstreamType.UT_WIFI, duration, 0L, 0L);
verifyReport(DownstreamType.DS_TETHERING_WIFI, expectedResult, UserType.USER_UNKNOWN,
upstreamEvents, getElapsedRealtime());
- reset(mTetheringMetrics);
clearElapsedRealtime();
- mTetheringMetrics.cleanup();
}
@Test
@@ -231,6 +229,7 @@
private void runUserTypesTest(final String callerPkg, final UserType expectedResult)
throws Exception {
+ mTetheringMetrics = new TetheringMetrics(mContext, mDeps);
mTetheringMetrics.createBuilder(TETHERING_WIFI, callerPkg);
final long duration = 1 * SECOND_IN_MILLIS;
incrementCurrentTime(duration);
@@ -241,9 +240,7 @@
addUpstreamEvent(upstreamEvents, UpstreamType.UT_NO_NETWORK, duration, 0L, 0L);
verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR, expectedResult,
upstreamEvents, getElapsedRealtime());
- reset(mTetheringMetrics);
clearElapsedRealtime();
- mTetheringMetrics.cleanup();
}
@Test
@@ -256,6 +253,7 @@
private void runUpstreamTypesTest(final UpstreamNetworkState ns,
final UpstreamType expectedResult) throws Exception {
+ mTetheringMetrics = new TetheringMetrics(mContext, mDeps);
mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG);
mTetheringMetrics.maybeUpdateUpstreamType(ns);
final long duration = 2 * SECOND_IN_MILLIS;
@@ -266,9 +264,7 @@
addUpstreamEvent(upstreamEvents, expectedResult, duration, 0L, 0L);
verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR,
UserType.USER_UNKNOWN, upstreamEvents, getElapsedRealtime());
- reset(mTetheringMetrics);
clearElapsedRealtime();
- mTetheringMetrics.cleanup();
}
@Test
@@ -379,4 +375,21 @@
UserType.USER_SETTINGS, upstreamEvents,
currentTimeMillis() - wifiTetheringStartTime);
}
+
+ private void runUsageSupportedForUpstreamTypeTest(final UpstreamType upstreamType,
+ final boolean isSupported) {
+ final boolean result = TetheringMetrics.isUsageSupportedForUpstreamType(upstreamType);
+ assertEquals(isSupported, result);
+ }
+
+ @Test
+ public void testUsageSupportedForUpstreamTypeTest() {
+ runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_CELLULAR, true /* isSupported */);
+ runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_WIFI, true /* isSupported */);
+ runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_BLUETOOTH, true /* isSupported */);
+ runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_ETHERNET, true /* isSupported */);
+ runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_WIFI_AWARE, false /* isSupported */);
+ runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_LOWPAN, false /* isSupported */);
+ runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_UNKNOWN, false /* isSupported */);
+ }
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt
index f8e98e3..2417385 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt
@@ -19,9 +19,10 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.util.State
+import com.android.net.module.util.SyncStateMachine
+import com.android.net.module.util.SyncStateMachine.StateInfo
import com.android.networkstack.tethering.util.StateMachineShim.AsyncStateMachine
import com.android.networkstack.tethering.util.StateMachineShim.Dependencies
-import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo
import kotlin.test.assertFailsWith
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/TetheringUtilsTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/TetheringUtilsTest.java
index 94ce2b6..f0770f9 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/TetheringUtilsTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/TetheringUtilsTest.java
@@ -15,8 +15,6 @@
*/
package com.android.networkstack.tethering.util;
-import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
-import static android.net.TetheringManager.TETHERING_USB;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.system.OsConstants.AF_UNIX;
import static android.system.OsConstants.EAGAIN;
@@ -25,8 +23,6 @@
import static android.system.OsConstants.SOCK_NONBLOCK;
import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
import android.net.LinkAddress;
import android.net.MacAddress;
@@ -43,9 +39,7 @@
import com.android.net.module.util.structs.EthernetHeader;
import com.android.net.module.util.structs.Icmpv6Header;
import com.android.net.module.util.structs.Ipv6Header;
-import com.android.testutils.MiscAsserts;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -61,13 +55,6 @@
private static final LinkAddress TEST_CLIENT_ADDR = new LinkAddress("192.168.43.5/24");
private static final int PACKET_SIZE = 1500;
- private TetheringRequestParcel mTetheringRequest;
-
- @Before
- public void setUp() {
- mTetheringRequest = makeTetheringRequestParcel();
- }
-
public TetheringRequestParcel makeTetheringRequestParcel() {
final TetheringRequestParcel request = new TetheringRequestParcel();
request.tetheringType = TETHERING_WIFI;
@@ -78,40 +65,6 @@
return request;
}
- @Test
- public void testIsTetheringRequestEquals() {
- TetheringRequestParcel request = makeTetheringRequestParcel();
-
- assertTrue(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, mTetheringRequest));
- assertTrue(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
- assertTrue(TetheringUtils.isTetheringRequestEquals(null, null));
- assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, null));
- assertFalse(TetheringUtils.isTetheringRequestEquals(null, mTetheringRequest));
-
- request = makeTetheringRequestParcel();
- request.tetheringType = TETHERING_USB;
- assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
-
- request = makeTetheringRequestParcel();
- request.localIPv4Address = null;
- request.staticClientAddress = null;
- assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
-
- request = makeTetheringRequestParcel();
- request.exemptFromEntitlementCheck = true;
- assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
-
- request = makeTetheringRequestParcel();
- request.showProvisioningUi = false;
- assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
-
- request = makeTetheringRequestParcel();
- request.connectivityScope = CONNECTIVITY_SCOPE_LOCAL;
- assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
-
- MiscAsserts.assertFieldCountEquals(6, TetheringRequestParcel.class);
- }
-
// Writes the specified packet to a filedescriptor, skipping the Ethernet header.
// Needed because the Ipv6Utils methods for building packets always include the Ethernet header,
// but socket filters applied by TetheringUtils expect the packet to start from the IP header.
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/VersionedBroadcastListenerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/VersionedBroadcastListenerTest.java
index b7dc66e..ed4f3da 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/VersionedBroadcastListenerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/VersionedBroadcastListenerTest.java
@@ -16,6 +16,8 @@
package com.android.networkstack.tethering.util;
+import static com.android.testutils.HandlerUtils.waitForIdle;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.reset;
@@ -23,7 +25,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
-import android.os.Looper;
+import android.os.HandlerThread;
import android.os.UserHandle;
import androidx.test.filters.SmallTest;
@@ -33,7 +35,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -44,9 +45,11 @@
public class VersionedBroadcastListenerTest {
private static final String TAG = VersionedBroadcastListenerTest.class.getSimpleName();
private static final String ACTION_TEST = "action.test.happy.broadcasts";
+ private static final long TEST_TIMEOUT_MS = 10_000L;
@Mock private Context mContext;
private BroadcastInterceptingContext mServiceContext;
+ private HandlerThread mHandlerThread;
private Handler mHandler;
private VersionedBroadcastListener mListener;
private int mCallbackCount;
@@ -61,18 +64,13 @@
}
}
- @BeforeClass
- public static void setUpBeforeClass() throws Exception {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
- }
-
@Before public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
reset(mContext);
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
mServiceContext = new MockContext(mContext);
- mHandler = new Handler(Looper.myLooper());
mCallbackCount = 0;
final IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_TEST);
@@ -85,11 +83,15 @@
mListener.stopListening();
mListener = null;
}
+ mHandlerThread.quitSafely();
+ mHandlerThread.join(TEST_TIMEOUT_MS);
}
private void sendBroadcast() {
final Intent intent = new Intent(ACTION_TEST);
mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ // Sending the broadcast is synchronous, but the receiver just posts on the handler
+ waitForIdle(mHandler, TEST_TIMEOUT_MS);
}
@Test
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index 9e67415..f6717c5 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -38,6 +38,7 @@
cflags: [
"-Wall",
"-Werror",
+ "-Wextra",
],
sdk_version: "30",
min_sdk_version: "30",
@@ -65,83 +66,46 @@
bpf {
name: "block.o",
srcs: ["block.c"],
- btf: true,
- cflags: [
- "-Wall",
- "-Werror",
- ],
sub_dir: "net_shared",
}
bpf {
name: "dscpPolicy.o",
srcs: ["dscpPolicy.c"],
- btf: true,
- cflags: [
- "-Wall",
- "-Werror",
- ],
sub_dir: "net_shared",
}
-bpf {
- name: "gentle.o",
- srcs: ["gentle.c"],
- cflags: [
- "-Wall",
- "-Werror",
- ],
- sub_dir: "net_shared",
-}
-
+// Ships to Android S, the bpfloader of which fails to parse BTF enabled .o's.
bpf {
name: "offload.o",
srcs: ["offload.c"],
- cflags: [
- "-Wall",
- "-Werror",
- ],
+ btf: false,
}
+// This version ships to Android T+ which uses mainline netbpfload.
bpf {
name: "offload@mainline.o",
srcs: ["offload@mainline.c"],
- btf: true,
- cflags: [
- "-Wall",
- "-Werror",
- "-DMAINLINE",
- ],
+ cflags: ["-DMAINLINE"],
}
+// Ships to Android S, the bpfloader of which fails to parse BTF enabled .o's.
bpf {
name: "test.o",
srcs: ["test.c"],
- cflags: [
- "-Wall",
- "-Werror",
- ],
+ btf: false,
}
+// This version ships to Android T+ which uses mainline netbpfload.
bpf {
name: "test@mainline.o",
srcs: ["test@mainline.c"],
- btf: true,
- cflags: [
- "-Wall",
- "-Werror",
- "-DMAINLINE",
- ],
+ cflags: ["-DMAINLINE"],
}
bpf {
name: "clatd.o",
srcs: ["clatd.c"],
- btf: true,
- cflags: [
- "-Wall",
- "-Werror",
- ],
sub_dir: "net_shared",
}
@@ -149,11 +113,6 @@
// WARNING: Android T's non-updatable netd depends on 'netd' string for xt_bpf programs it loads
name: "netd.o",
srcs: ["netd.c"],
- btf: true,
- cflags: [
- "-Wall",
- "-Werror",
- ],
// WARNING: Android T's non-updatable netd depends on 'netd_shared' string for xt_bpf programs
sub_dir: "netd_shared",
}
diff --git a/bpf_progs/block.c b/bpf_progs/block.c
index 353525e..152dda6 100644
--- a/bpf_progs/block.c
+++ b/bpf_progs/block.c
@@ -20,7 +20,7 @@
#include <stdint.h>
// The resulting .o needs to load on Android T+
-#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
+#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
#include "bpf_helpers.h"
@@ -76,4 +76,3 @@
LICENSE("Apache 2.0");
CRITICAL("ConnectivityNative");
DISABLE_BTF_ON_USER_BUILDS();
-DISABLE_ON_MAINLINE_BEFORE_U_QPR3();
diff --git a/bpf_progs/bpf_net_helpers.h b/bpf_progs/bpf_net_helpers.h
index 1511ee5..ba2f26b 100644
--- a/bpf_progs/bpf_net_helpers.h
+++ b/bpf_progs/bpf_net_helpers.h
@@ -83,7 +83,7 @@
// try to make the first 'len' header bytes readable/writable via direct packet access
// (note: AFAIK there is no way to ask for only direct packet read without also getting write)
-static inline __always_inline void try_make_writable(struct __sk_buff* skb, int len) {
+static inline __always_inline void try_make_writable(struct __sk_buff* skb, unsigned len) {
if (len > skb->len) len = skb->len;
if (skb->data_end - skb->data < len) bpf_skb_pull_data(skb, len);
}
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index 95e9a4c..da6ccbf 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -31,7 +31,7 @@
#include <linux/udp.h>
// The resulting .o needs to load on Android T+
-#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
+#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
#include "bpf_helpers.h"
#include "bpf_net_helpers.h"
@@ -177,7 +177,7 @@
// Calculate the IPv4 one's complement checksum of the IPv4 header.
__wsum sum4 = 0;
- for (int i = 0; i < sizeof(ip) / sizeof(__u16); ++i) {
+ for (unsigned i = 0; i < sizeof(ip) / sizeof(__u16); ++i) {
sum4 += ((__u16*)&ip)[i];
}
// Note that sum4 is guaranteed to be non-zero by virtue of ip.version == 4
@@ -188,7 +188,7 @@
// Calculate the *negative* IPv6 16-bit one's complement checksum of the IPv6 header.
__wsum sum6 = 0;
// We'll end up with a non-zero sum due to ip6->version == 6 (which has '0' bits)
- for (int i = 0; i < sizeof(*ip6) / sizeof(__u16); ++i) {
+ for (unsigned i = 0; i < sizeof(*ip6) / sizeof(__u16); ++i) {
sum6 += ~((__u16*)ip6)[i]; // note the bitwise negation
}
@@ -321,7 +321,7 @@
// Calculate the IPv4 one's complement checksum of the IPv4 header.
__wsum sum4 = 0;
- for (int i = 0; i < sizeof(*ip4) / sizeof(__u16); ++i) {
+ for (unsigned i = 0; i < sizeof(*ip4) / sizeof(__u16); ++i) {
sum4 += ((__u16*)ip4)[i];
}
// Note that sum4 is guaranteed to be non-zero by virtue of ip4->version == 4
@@ -387,7 +387,7 @@
// Calculate the IPv6 16-bit one's complement checksum of the IPv6 header.
__wsum sum6 = 0;
// We'll end up with a non-zero sum due to ip6.version == 6
- for (int i = 0; i < sizeof(ip6) / sizeof(__u16); ++i) {
+ for (unsigned i = 0; i < sizeof(ip6) / sizeof(__u16); ++i) {
sum6 += ((__u16*)&ip6)[i];
}
@@ -431,4 +431,3 @@
LICENSE("Apache 2.0");
CRITICAL("Connectivity");
DISABLE_BTF_ON_USER_BUILDS();
-DISABLE_ON_MAINLINE_BEFORE_U_QPR3();
diff --git a/bpf_progs/dscpPolicy.c b/bpf_progs/dscpPolicy.c
index 8bb8ad5..ed114e4 100644
--- a/bpf_progs/dscpPolicy.c
+++ b/bpf_progs/dscpPolicy.c
@@ -28,7 +28,7 @@
#include <string.h>
// The resulting .o needs to load on Android T+
-#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
+#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
#include "bpf_helpers.h"
#include "dscpPolicy.h"
@@ -239,4 +239,3 @@
LICENSE("Apache 2.0");
CRITICAL("Connectivity");
DISABLE_BTF_ON_USER_BUILDS();
-DISABLE_ON_MAINLINE_BEFORE_U_QPR3();
diff --git a/bpf_progs/gentle.c b/bpf_progs/gentle.c
deleted file mode 100644
index ab9620e..0000000
--- a/bpf_progs/gentle.c
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2024 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.
- */
-
-#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
-
-#include "bpf_helpers.h"
-#include "bpf_net_helpers.h"
-
-DEFINE_BPF_MAP_GRW(test, ARRAY, int, uint64_t, 1, AID_SYSTEM)
-
-DEFINE_BPF_PROG("skfilter/accept", AID_ROOT, AID_SYSTEM, accept)
-(struct __sk_buff *skb) {
- return 1;
-}
-
-LICENSE("Apache 2.0");
-DISABLE_BTF_ON_USER_BUILDS();
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 5ce2227..f08b007 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -15,7 +15,7 @@
*/
// The resulting .o needs to load on Android T+
-#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
+#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
#include <bpf_helpers.h>
#include <linux/bpf.h>
@@ -106,13 +106,13 @@
// A single-element configuration array, packet tracing is enabled when 'true'.
DEFINE_BPF_MAP_EXT(packet_trace_enabled_map, ARRAY, uint32_t, bool, 1,
AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", PRIVATE,
- BPFLOADER_U_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
+ BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
LOAD_ON_USER, LOAD_ON_USERDEBUG)
// A ring buffer on which packet information is pushed.
DEFINE_BPF_RINGBUF_EXT(packet_trace_ringbuf, PacketTrace, PACKET_TRACE_BUF_SIZE,
AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", PRIVATE,
- BPFLOADER_U_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
+ BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
LOAD_ON_USER, LOAD_ON_USERDEBUG);
DEFINE_BPF_MAP_RO_NETD(data_saver_enabled_map, ARRAY, uint32_t, bool,
@@ -184,7 +184,7 @@
static __always_inline inline void update_##the_stats_map(const struct __sk_buff* const skb, \
const TypeOfKey* const key, \
const struct egress_bool egress, \
- const struct kver_uint kver) { \
+ __unused const struct kver_uint kver) { \
StatsValue* value = bpf_##the_stats_map##_lookup_elem(key); \
if (!value) { \
StatsValue newValue = {}; \
@@ -524,22 +524,12 @@
return match;
}
-// This program is optional, and enables tracing on Android U+, 5.8+ on user builds.
-DEFINE_BPF_PROG_EXT("cgroupskb/ingress/stats$trace_user", AID_ROOT, AID_SYSTEM,
- bpf_cgroup_ingress_trace_user, KVER_5_8, KVER_INF,
- BPFLOADER_U_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
- "fs_bpf_netd_readonly", "",
- IGNORE_ON_ENG, LOAD_ON_USER, IGNORE_ON_USERDEBUG)
-(struct __sk_buff* skb) {
- return bpf_traffic_account(skb, INGRESS, TRACE_ON, KVER_5_8);
-}
-
-// This program is required, and enables tracing on Android U+, 5.8+, userdebug/eng.
+// Tracing on Android U+ 5.8+
DEFINE_BPF_PROG_EXT("cgroupskb/ingress/stats$trace", AID_ROOT, AID_SYSTEM,
bpf_cgroup_ingress_trace, KVER_5_8, KVER_INF,
- BPFLOADER_U_VERSION, BPFLOADER_MAX_VER, MANDATORY,
+ BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, MANDATORY,
"fs_bpf_netd_readonly", "",
- LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG)
+ LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG)
(struct __sk_buff* skb) {
return bpf_traffic_account(skb, INGRESS, TRACE_ON, KVER_5_8);
}
@@ -556,22 +546,12 @@
return bpf_traffic_account(skb, INGRESS, TRACE_OFF, KVER_NONE);
}
-// This program is optional, and enables tracing on Android U+, 5.8+ on user builds.
-DEFINE_BPF_PROG_EXT("cgroupskb/egress/stats$trace_user", AID_ROOT, AID_SYSTEM,
- bpf_cgroup_egress_trace_user, KVER_5_8, KVER_INF,
- BPFLOADER_U_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
- "fs_bpf_netd_readonly", "",
- IGNORE_ON_ENG, LOAD_ON_USER, IGNORE_ON_USERDEBUG)
-(struct __sk_buff* skb) {
- return bpf_traffic_account(skb, EGRESS, TRACE_ON, KVER_5_8);
-}
-
-// This program is required, and enables tracing on Android U+, 5.8+, userdebug/eng.
+// Tracing on Android U+ 5.8+
DEFINE_BPF_PROG_EXT("cgroupskb/egress/stats$trace", AID_ROOT, AID_SYSTEM,
bpf_cgroup_egress_trace, KVER_5_8, KVER_INF,
- BPFLOADER_U_VERSION, BPFLOADER_MAX_VER, MANDATORY,
+ BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, MANDATORY,
"fs_bpf_netd_readonly", "",
- LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG)
+ LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG)
(struct __sk_buff* skb) {
return bpf_traffic_account(skb, EGRESS, TRACE_ON, KVER_5_8);
}
@@ -676,7 +656,7 @@
DEFINE_NETD_BPF_PROG_KVER("cgroupsock/inet_create", AID_ROOT, AID_ROOT, inet_socket_create,
KVER_4_14)
-(struct bpf_sock* sk) {
+(__unused struct bpf_sock* sk) {
// A return value of 1 means allow, everything else means deny.
return (get_app_permissions() & BPF_PERMISSION_INTERNET) ? 1 : 0;
}
@@ -690,7 +670,7 @@
return 1;
}
-static __always_inline inline int check_localhost(struct bpf_sock_addr *ctx) {
+static __always_inline inline int check_localhost(__unused struct bpf_sock_addr *ctx) {
// See include/uapi/linux/bpf.h:
//
// struct bpf_sock_addr {
@@ -757,4 +737,3 @@
LICENSE("Apache 2.0");
CRITICAL("Connectivity and netd");
DISABLE_BTF_ON_USER_BUILDS();
-DISABLE_ON_MAINLINE_BEFORE_U_QPR3();
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index c7ac059..4d908d2 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -28,11 +28,11 @@
// BTF is incompatible with bpfloaders < v0.10, hence for S (v0.2) we must
// ship a different file than for later versions, but we need bpfloader v0.25+
// for obj@ver.o support
-#define BPFLOADER_MIN_VER BPFLOADER_OBJ_AT_VER_VERSION
+#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
#else /* MAINLINE */
-// The resulting .o needs to load on the Android S & T bpfloaders
+// The resulting .o needs to load on the Android S bpfloader
#define BPFLOADER_MIN_VER BPFLOADER_S_VERSION
-#define BPFLOADER_MAX_VER BPFLOADER_OBJ_AT_VER_VERSION
+#define BPFLOADER_MAX_VER BPFLOADER_T_VERSION
#endif /* MAINLINE */
// Warning: values other than AID_ROOT don't work for map uid on BpfLoader < v0.21
@@ -127,7 +127,7 @@
static inline __always_inline int do_forward6(struct __sk_buff* skb,
const struct rawip_bool rawip,
const struct stream_bool stream,
- const struct kver_uint kver) {
+ __unused const struct kver_uint kver) {
const bool is_ethernet = !rawip.rawip;
// Must be meta-ethernet IPv6 frame
@@ -343,13 +343,13 @@
// and define no-op stubs for pre-4.14 kernels.
DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream6_rawip$stub", TETHERING_UID, TETHERING_GID,
sched_cls_tether_downstream6_rawip_stub, KVER_NONE, KVER_4_14)
-(struct __sk_buff* skb) {
+(__unused struct __sk_buff* skb) {
return TC_ACT_PIPE;
}
DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream6_rawip$stub", TETHERING_UID, TETHERING_GID,
sched_cls_tether_upstream6_rawip_stub, KVER_NONE, KVER_4_14)
-(struct __sk_buff* skb) {
+(__unused struct __sk_buff* skb) {
return TC_ACT_PIPE;
}
@@ -363,7 +363,7 @@
const int l2_header_size, void* data, const void* data_end,
struct ethhdr* eth, struct iphdr* ip, const struct rawip_bool rawip,
const struct stream_bool stream, const struct updatetime_bool updatetime,
- const bool is_tcp, const struct kver_uint kver) {
+ const bool is_tcp, __unused const struct kver_uint kver) {
const bool is_ethernet = !rawip.rawip;
struct tcphdr* tcph = is_tcp ? (void*)(ip + 1) : NULL;
struct udphdr* udph = is_tcp ? NULL : (void*)(ip + 1);
@@ -593,7 +593,7 @@
// Calculate the IPv4 one's complement checksum of the IPv4 header.
__wsum sum4 = 0;
- for (int i = 0; i < sizeof(*ip) / sizeof(__u16); ++i) {
+ for (unsigned i = 0; i < sizeof(*ip) / sizeof(__u16); ++i) {
sum4 += ((__u16*)ip)[i];
}
// Note that sum4 is guaranteed to be non-zero by virtue of ip4->version == 4
@@ -780,13 +780,13 @@
DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$stub", TETHERING_UID, TETHERING_GID,
sched_cls_tether_downstream4_rawip_stub, KVER_NONE, KVER_5_4)
-(struct __sk_buff* skb) {
+(__unused struct __sk_buff* skb) {
return TC_ACT_PIPE;
}
DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$stub", TETHERING_UID, TETHERING_GID,
sched_cls_tether_upstream4_rawip_stub, KVER_NONE, KVER_5_4)
-(struct __sk_buff* skb) {
+(__unused struct __sk_buff* skb) {
return TC_ACT_PIPE;
}
@@ -794,13 +794,13 @@
DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$stub", TETHERING_UID, TETHERING_GID,
sched_cls_tether_downstream4_ether_stub, KVER_NONE, KVER_4_14)
-(struct __sk_buff* skb) {
+(__unused struct __sk_buff* skb) {
return TC_ACT_PIPE;
}
DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$stub", TETHERING_UID, TETHERING_GID,
sched_cls_tether_upstream4_ether_stub, KVER_NONE, KVER_4_14)
-(struct __sk_buff* skb) {
+(__unused struct __sk_buff* skb) {
return TC_ACT_PIPE;
}
@@ -808,13 +808,13 @@
DEFINE_BPF_MAP_GRW(tether_dev_map, DEVMAP_HASH, uint32_t, uint32_t, 64, TETHERING_GID)
-static inline __always_inline int do_xdp_forward6(struct xdp_md *ctx, const struct rawip_bool rawip,
- const struct stream_bool stream) {
+static inline __always_inline int do_xdp_forward6(__unused struct xdp_md *ctx,
+ __unused const struct rawip_bool rawip, __unused const struct stream_bool stream) {
return XDP_PASS;
}
-static inline __always_inline int do_xdp_forward4(struct xdp_md *ctx, const struct rawip_bool rawip,
- const struct stream_bool stream) {
+static inline __always_inline int do_xdp_forward4(__unused struct xdp_md *ctx,
+ __unused const struct rawip_bool rawip, __unused const struct stream_bool stream) {
return XDP_PASS;
}
@@ -878,4 +878,3 @@
LICENSE("Apache 2.0");
CRITICAL("Connectivity (Tethering)");
DISABLE_BTF_ON_USER_BUILDS();
-DISABLE_ON_MAINLINE_BEFORE_U_QPR3();
diff --git a/common/FlaggedApi.bp b/common/FlaggedApi.bp
index 21be1d3..fef9ac3 100644
--- a/common/FlaggedApi.bp
+++ b/common/FlaggedApi.bp
@@ -22,6 +22,16 @@
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
+java_aconfig_library {
+ name: "com.android.net.flags-aconfig-java",
+ aconfig_declarations: "com.android.net.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+ min_sdk_version: "30",
+ apex_available: [
+ "com.android.tethering",
+ ],
+}
+
aconfig_declarations {
name: "com.android.net.thread.flags-aconfig",
package: "com.android.net.thread.flags",
@@ -37,3 +47,20 @@
srcs: ["nearby_flags.aconfig"],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
+
+aconfig_declarations {
+ name: "com.android.networksecurity.flags-aconfig",
+ package: "com.android.net.ct.flags",
+ container: "com.android.tethering",
+ srcs: ["networksecurity_flags.aconfig"],
+ visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
+
+java_aconfig_library {
+ name: "networksecurity_flags_java_lib",
+ aconfig_declarations: "com.android.networksecurity.flags-aconfig",
+ min_sdk_version: "30",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+ apex_available: ["com.android.tethering"],
+ visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
diff --git a/common/flags.aconfig b/common/flags.aconfig
index bc4168b..1b0da4e 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -115,3 +115,19 @@
description: "Flag for NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED API"
bug: "343823469"
}
+
+flag {
+ name: "tethering_request_virtual"
+ is_exported: true
+ namespace: "android_core_networking"
+ description: "Flag for introducing TETHERING_VIRTUAL type"
+ bug: "340376953"
+}
+
+flag {
+ name: "netstats_add_entries"
+ is_exported: true
+ namespace: "android_core_networking"
+ description: "Flag for NetworkStats#addEntries API"
+ bug: "335680025"
+}
diff --git a/common/networksecurity_flags.aconfig b/common/networksecurity_flags.aconfig
new file mode 100644
index 0000000..ef8ffcd
--- /dev/null
+++ b/common/networksecurity_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.net.ct.flags"
+container: "com.android.tethering"
+flag {
+ name: "certificate_transparency_service"
+ is_exported: true
+ namespace: "network_security"
+ description: "Enable service for certificate transparency log list data"
+ bug: "319829948"
+}
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index bc919ac..ac78d09 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -137,6 +137,10 @@
// framework-connectivity-pre-jarjar match at runtime.
jarjar_rules: ":framework-connectivity-jarjar-rules",
stub_only_libs: [
+ // static_libs is not used to compile stubs. So libs which have
+ // been included in static_libs might still need to
+ // be in stub_only_libs to be usable when generating the API stubs.
+ "com.android.net.flags-aconfig-java",
// Use prebuilt framework-connectivity stubs to avoid circular dependencies
"sdk_module-lib_current_framework-connectivity",
],
@@ -201,6 +205,9 @@
"com.android.net.thread.flags-aconfig",
"nearby_flags",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// This rule is not used anymore(b/268440216).
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index 1f1953c..2354882 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -310,6 +310,7 @@
public final class NetworkStats implements java.lang.Iterable<android.net.NetworkStats.Entry> android.os.Parcelable {
ctor public NetworkStats(long, int);
method @NonNull public android.net.NetworkStats add(@NonNull android.net.NetworkStats);
+ method @FlaggedApi("com.android.net.flags.netstats_add_entries") @NonNull public android.net.NetworkStats addEntries(@NonNull java.util.List<android.net.NetworkStats.Entry>);
method @NonNull public android.net.NetworkStats addEntry(@NonNull android.net.NetworkStats.Entry);
method public android.net.NetworkStats clone();
method public int describeContents();
@@ -497,16 +498,26 @@
field @NonNull public static final android.os.Parcelable.Creator<android.net.thread.PendingOperationalDataset> CREATOR;
}
+ @FlaggedApi("com.android.net.thread.flags.configuration_enabled") public final class ThreadConfiguration implements android.os.Parcelable {
+ method public int describeContents();
+ method public boolean isDhcpv6PdEnabled();
+ method public boolean isNat64Enabled();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.thread.ThreadConfiguration> CREATOR;
+ }
+
@FlaggedApi("com.android.net.thread.flags.thread_enabled") public final class ThreadNetworkController {
method public void createRandomizedDataset(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.net.thread.ActiveOperationalDataset,android.net.thread.ThreadNetworkException>);
method public int getThreadVersion();
method public static boolean isAttached(int);
method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void join(@NonNull android.net.thread.ActiveOperationalDataset, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void leave(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
+ method @FlaggedApi("com.android.net.thread.flags.configuration_enabled") @RequiresPermission(android.Manifest.permission.THREAD_NETWORK_PRIVILEGED) public void registerConfigurationCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.net.thread.ThreadConfiguration>);
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_NETWORK_STATE, "android.permission.THREAD_NETWORK_PRIVILEGED"}) public void registerOperationalDatasetCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.thread.ThreadNetworkController.OperationalDatasetCallback);
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.thread.ThreadNetworkController.StateCallback);
method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void scheduleMigration(@NonNull android.net.thread.PendingOperationalDataset, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void setEnabled(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
+ method @FlaggedApi("com.android.net.thread.flags.configuration_enabled") @RequiresPermission(android.Manifest.permission.THREAD_NETWORK_PRIVILEGED) public void unregisterConfigurationCallback(@NonNull java.util.function.Consumer<android.net.thread.ThreadConfiguration>);
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_NETWORK_STATE, "android.permission.THREAD_NETWORK_PRIVILEGED"}) public void unregisterOperationalDatasetCallback(@NonNull android.net.thread.ThreadNetworkController.OperationalDatasetCallback);
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void unregisterStateCallback(@NonNull android.net.thread.ThreadNetworkController.StateCallback);
field public static final int DEVICE_ROLE_CHILD = 2; // 0x2
diff --git a/framework-t/lint-baseline.xml b/framework-t/lint-baseline.xml
new file mode 100644
index 0000000..4e206ed
--- /dev/null
+++ b/framework-t/lint-baseline.xml
@@ -0,0 +1,1313 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha08" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha08">
+
+ <issue
+ id="FlaggedApi"
+ message="Field `SERVICE_NAME` is a flagged API and should be inside an `if (ThreadNetworkFlags.threadEnabled())` check (or annotate the surrounding method `registerServiceWrappers` with `@FlaggedApi(ThreadNetworkFlags.FLAG_THREAD_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" ThreadNetworkManager.SERVICE_NAME,"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java"
+ line="101"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Class `ThreadNetworkManager` is a flagged API and should be inside an `if (ThreadNetworkFlags.threadEnabled())` check (or annotate the surrounding method `registerServiceWrappers` with `@FlaggedApi(ThreadNetworkFlags.FLAG_THREAD_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" ThreadNetworkManager.class,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java"
+ line="102"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `ThreadNetworkManager()` is a flagged API and should be inside an `if (ThreadNetworkFlags.threadEnabled())` check (or annotate the surrounding method `registerServiceWrappers` with `@FlaggedApi(ThreadNetworkFlags.FLAG_THREAD_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" return new ThreadNetworkManager(context, managerService);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java"
+ line="106"
+ column="28"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="87"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="87"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="87"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="529"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="573"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="605"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="This is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `handleMessage` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" final String s = getNsdServiceInfoType((DiscoveryRequest) obj);"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1076"
+ column="61"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getServiceType()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `getNsdServiceInfoType` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" return r.getServiceType();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1236"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `discoverServices` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" DiscoveryRequest request = new DiscoveryRequest.Builder(protocolType, serviceType)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1477"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `discoverServices` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" DiscoveryRequest request = new DiscoveryRequest.Builder(protocolType, serviceType)"
+ errorLine2=" ^">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1477"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setNetwork()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `discoverServices` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" DiscoveryRequest request = new DiscoveryRequest.Builder(protocolType, serviceType)"
+ errorLine2=" ^">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1477"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `discoverServices()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `discoverServices` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" discoverServices(request, executor, listener);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1479"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `discoverServices` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" new DiscoveryRequest.Builder(protocolType, serviceType).build();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1566"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `discoverServices` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" new DiscoveryRequest.Builder(protocolType, serviceType).build();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1566"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getSubtypes()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `NsdServiceInfo` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" mSubtypes = new ArraySet<>(other.getSubtypes());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdServiceInfo.java"
+ line="106"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setSubtypes()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `createFromParcel` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" info.setSubtypes(new ArraySet<>(in.createStringArrayList()));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdServiceInfo.java"
+ line="673"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+</issues>
diff --git a/framework-t/src/android/net/NetworkStats.java b/framework-t/src/android/net/NetworkStats.java
index e9a3f58..a2c4fc3 100644
--- a/framework-t/src/android/net/NetworkStats.java
+++ b/framework-t/src/android/net/NetworkStats.java
@@ -18,6 +18,7 @@
import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -33,6 +34,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
+import com.android.net.flags.Flags;
import com.android.net.module.util.CollectionUtils;
import libcore.util.EmptyArray;
@@ -845,6 +847,21 @@
}
/**
+ * Adds multiple entries to a copy of this NetworkStats instance.
+ *
+ * @param entries The entries to add.
+ * @return A new NetworkStats instance with the added entries.
+ */
+ @FlaggedApi(Flags.FLAG_NETSTATS_ADD_ENTRIES)
+ public @NonNull NetworkStats addEntries(@NonNull final List<Entry> entries) {
+ final NetworkStats newStats = this.clone();
+ for (final Entry entry : Objects.requireNonNull(entries)) {
+ newStats.combineValues(entry);
+ }
+ return newStats;
+ }
+
+ /**
* Add given values with an existing row, or create a new row if
* {@link #findIndex(String, int, int, int, int, int, int)} is unable to find match. Can
* also be used to subtract values from existing rows.
diff --git a/framework-t/src/android/net/nsd/NsdServiceInfo.java b/framework-t/src/android/net/nsd/NsdServiceInfo.java
index 2f675a9..d8cccb2 100644
--- a/framework-t/src/android/net/nsd/NsdServiceInfo.java
+++ b/framework-t/src/android/net/nsd/NsdServiceInfo.java
@@ -180,8 +180,18 @@
return new ArrayList<>(mHostAddresses);
}
- /** Set the host addresses */
+ /**
+ * Set the host addresses.
+ *
+ * <p>When registering hosts/services, there can only be one registration including address
+ * records for a given hostname.
+ *
+ * <p>For example, if a client registers a service with the hostname "MyHost" and the address
+ * records of 192.168.1.1 and 192.168.1.2, then other registrations for the hostname "MyHost"
+ * must not have any address record, otherwise there will be a conflict.
+ */
public void setHostAddresses(@NonNull List<InetAddress> addresses) {
+ // TODO: b/284905335 - Notify the client when there is a conflict.
mHostAddresses.clear();
mHostAddresses.addAll(addresses);
}
diff --git a/framework/Android.bp b/framework/Android.bp
index deb1c5a..4c4f792 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -63,6 +63,7 @@
":framework-connectivity-sources",
":net-utils-framework-common-srcs",
":framework-connectivity-api-shared-srcs",
+ ":framework-networksecurity-sources",
],
aidl: {
generate_get_transaction_name: true,
@@ -86,17 +87,18 @@
"framework-wifi.stubs.module_lib",
],
static_libs: [
+ "com.android.net.flags-aconfig-java",
// Not using the latest stable version because all functions in the latest version of
// mdns_aidl_interface are deprecated.
"mdns_aidl_interface-V1-java",
"modules-utils-backgroundthread",
"modules-utils-build",
"modules-utils-preconditions",
+ "networksecurity_flags_java_lib",
"framework-connectivity-javastream-protos",
],
impl_only_static_libs: [
- "net-utils-device-common-bpf",
- "net-utils-device-common-struct-base",
+ "net-utils-framework-connectivity",
],
libs: [
"androidx.annotation_annotation",
@@ -109,22 +111,20 @@
],
}
+// Library to allow Cronet to use hidden APIs
java_library {
- name: "framework-connectivity-pre-jarjar",
+ name: "framework-connectivity-pre-jarjar-without-cronet",
defaults: [
"framework-connectivity-defaults",
],
static_libs: [
- "httpclient_api",
- "httpclient_impl",
// Framework-connectivity-pre-jarjar is identical to framework-connectivity
// implementation, but without the jarjar rules. However, framework-connectivity
// is not based on framework-connectivity-pre-jarjar, it's rebuilt from source
// to generate the SDK stubs.
// Even if the library is included in "impl_only_static_libs" of defaults. This is still
// needed because java_library which doesn't understand "impl_only_static_libs".
- "net-utils-device-common-bpf",
- "net-utils-device-common-struct-base",
+ "net-utils-framework-connectivity",
],
libs: [
// This cannot be in the defaults clause above because if it were, it would be used
@@ -135,6 +135,21 @@
"framework-tethering.impl",
"framework-wifi.stubs.module_lib",
],
+ visibility: ["//external/cronet:__subpackages__"],
+}
+
+java_library {
+ name: "framework-connectivity-pre-jarjar",
+ defaults: ["framework-module-defaults"],
+ min_sdk_version: "30",
+ static_libs: [
+ "framework-connectivity-pre-jarjar-without-cronet",
+ "httpclient_api",
+ "httpclient_impl",
+ ],
+ apex_available: [
+ "com.android.tethering",
+ ],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
@@ -194,6 +209,7 @@
},
aconfig_declarations: [
"com.android.net.flags-aconfig",
+ "com.android.networksecurity.flags-aconfig",
],
}
@@ -294,6 +310,7 @@
srcs: [
":framework-connectivity-sources",
":framework-connectivity-tiramisu-updatable-sources",
+ ":framework-networksecurity-sources",
":framework-nearby-java-sources",
":framework-thread-sources",
],
@@ -332,7 +349,6 @@
srcs: [
// Files listed here MUST all be annotated with @RequiresApi(Build.VERSION_CODES.S)
// or above as appropriate so that API checks are enforced for R+ users of this library
- "src/android/net/RoutingCoordinatorManager.java",
"src/android/net/connectivity/ConnectivityInternalApiUtil.java",
],
visibility: [
diff --git a/framework/lint-baseline.xml b/framework/lint-baseline.xml
index 2c0b15f..dddabef 100644
--- a/framework/lint-baseline.xml
+++ b/framework/lint-baseline.xml
@@ -375,4 +375,279 @@
column="34"/>
</issue>
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_BACKGROUND` is a flagged API and should be inside an `if (Flags.basicBackgroundRestrictionsEnabled())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.BASIC_BACKGROUND_RESTRICTIONS_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" FIREWALL_CHAIN_BACKGROUND"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsConstants.java"
+ line="115"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_ALLOW` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" FIREWALL_CHAIN_METERED_ALLOW"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsConstants.java"
+ line="137"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_DENY_USER` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" FIREWALL_CHAIN_METERED_DENY_USER,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsConstants.java"
+ line="146"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_DENY_ADMIN` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" FIREWALL_CHAIN_METERED_DENY_ADMIN"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsConstants.java"
+ line="147"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_BACKGROUND` is a flagged API and should be inside an `if (Flags.basicBackgroundRestrictionsEnabled())` check (or annotate the surrounding method `getMatchByFirewallChain` with `@FlaggedApi(Flags.BASIC_BACKGROUND_RESTRICTIONS_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" case FIREWALL_CHAIN_BACKGROUND:"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsUtils.java"
+ line="133"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_ALLOW` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `getMatchByFirewallChain` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" case FIREWALL_CHAIN_METERED_ALLOW:"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsUtils.java"
+ line="143"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_DENY_USER` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `getMatchByFirewallChain` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" case FIREWALL_CHAIN_METERED_DENY_USER:"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsUtils.java"
+ line="145"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_DENY_ADMIN` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `getMatchByFirewallChain` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" case FIREWALL_CHAIN_METERED_DENY_ADMIN:"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsUtils.java"
+ line="147"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `BLOCKED_REASON_APP_BACKGROUND` is a flagged API and should be inside an `if (Flags.basicBackgroundRestrictionsEnabled())` check (or annotate the surrounding method `getUidNetworkingBlockedReasons` with `@FlaggedApi(Flags.BASIC_BACKGROUND_RESTRICTIONS_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" blockedReasons |= BLOCKED_REASON_APP_BACKGROUND;"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsUtils.java"
+ line="293"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `BLOCKED_REASON_OEM_DENY` is a flagged API and should be inside an `if (Flags.blockedReasonOemDenyChains())` check (or annotate the surrounding method `getUidNetworkingBlockedReasons` with `@FlaggedApi(Flags.BLOCKED_REASON_OEM_DENY_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" blockedReasons |= BLOCKED_REASON_OEM_DENY;"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsUtils.java"
+ line="296"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_ALLOW` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `addUidToMeteredNetworkAllowList` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" mService.setUidFirewallRule(FIREWALL_CHAIN_METERED_ALLOW, uid, FIREWALL_RULE_ALLOW);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
+ line="6191"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_ALLOW` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `removeUidFromMeteredNetworkAllowList` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" mService.setUidFirewallRule(FIREWALL_CHAIN_METERED_ALLOW, uid, FIREWALL_RULE_DENY);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
+ line="6214"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_DENY_USER` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `addUidToMeteredNetworkDenyList` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" mService.setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, uid, FIREWALL_RULE_DENY);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
+ line="6243"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_DENY_USER` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `removeUidFromMeteredNetworkDenyList` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" mService.setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, uid, FIREWALL_RULE_ALLOW);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
+ line="6273"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED` is a flagged API and should be inside an `if (Flags.netCapabilityNotBandwidthConstrained())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED) to transfer requirement to caller`)"
+ errorLine1=" private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED;"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="767"
+ column="51"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED` is a flagged API and should be inside an `if (Flags.netCapabilityNotBandwidthConstrained())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED) to transfer requirement to caller`)"
+ errorLine1=" defaultCapabilities |= (1L << NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="818"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED` is a flagged API and should be inside an `if (Flags.netCapabilityNotBandwidthConstrained())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED) to transfer requirement to caller`)"
+ errorLine1=" (1L << NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="849"
+ column="20"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getSubscriptionIds()` is a flagged API and should be inside an `if (Flags.requestRestrictedWifi())` check (or annotate the surrounding method `restrictCapabilitiesForTestNetwork` with `@FlaggedApi(Flags.REQUEST_RESTRICTED_WIFI) to transfer requirement to caller`)"
+ errorLine1=" final Set<Integer> originalSubIds = getSubscriptionIds();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="1254"
+ column="45"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `TRANSPORT_SATELLITE` is a flagged API and should be inside an `if (Flags.supportTransportSatellite())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.SUPPORT_TRANSPORT_SATELLITE) to transfer requirement to caller`)"
+ errorLine1=" public static final int MAX_TRANSPORT = TRANSPORT_SATELLITE;"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="1383"
+ column="45"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `TRANSPORT_SATELLITE` is a flagged API and should be inside an `if (Flags.supportTransportSatellite())` check (or annotate the surrounding method `specifierAcceptableForMultipleTransports` with `@FlaggedApi(Flags.SUPPORT_TRANSPORT_SATELLITE) to transfer requirement to caller`)"
+ errorLine1=" == (1 << TRANSPORT_CELLULAR | 1 << TRANSPORT_SATELLITE);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="1836"
+ column="52"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `NET_CAPABILITY_LOCAL_NETWORK` is a flagged API and should be inside an `if (Flags.netCapabilityLocalNetwork())` check (or annotate the surrounding method `capabilityNameOf` with `@FlaggedApi(Flags.FLAG_NET_CAPABILITY_LOCAL_NETWORK) to transfer requirement to caller`)"
+ errorLine1=" case NET_CAPABILITY_LOCAL_NETWORK: return "LOCAL_NETWORK";"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="2637"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED` is a flagged API and should be inside an `if (Flags.netCapabilityNotBandwidthConstrained())` check (or annotate the surrounding method `capabilityNameOf` with `@FlaggedApi(Flags.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED) to transfer requirement to caller`)"
+ errorLine1=" case NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED: return "NOT_BANDWIDTH_CONSTRAINED";"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="2638"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `TRANSPORT_SATELLITE` is a flagged API and should be inside an `if (Flags.supportTransportSatellite())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.SUPPORT_TRANSPORT_SATELLITE) to transfer requirement to caller`)"
+ errorLine1=" TRANSPORT_SATELLITE"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java"
+ line="80"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED` is a flagged API and should be inside an `if (Flags.netCapabilityNotBandwidthConstrained())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED) to transfer requirement to caller`)"
+ errorLine1=" NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkRequest.java"
+ line="291"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getSubscriptionIds()` is a flagged API and should be inside an `if (Flags.requestRestrictedWifi())` check (or annotate the surrounding method `getSubscriptionIds` with `@FlaggedApi(Flags.REQUEST_RESTRICTED_WIFI) to transfer requirement to caller`)"
+ errorLine1=" return networkCapabilities.getSubscriptionIds();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkRequest.java"
+ line="887"
+ column="16"/>
+ </issue>
+
</issues>
\ No newline at end of file
diff --git a/framework/src/android/net/BpfNetMapsUtils.java b/framework/src/android/net/BpfNetMapsUtils.java
index 282a11e..1760fa7 100644
--- a/framework/src/android/net/BpfNetMapsUtils.java
+++ b/framework/src/android/net/BpfNetMapsUtils.java
@@ -261,6 +261,12 @@
IBpfMap<S32, UidOwnerValue> uidOwnerMap,
IBpfMap<S32, U8> dataSaverEnabledMap
) {
+ // System uids are not blocked by firewall chains, see bpf_progs/netd.c
+ // TODO: b/348513058 - use UserHandle.isCore() once it is accessible
+ if (UserHandle.getAppId(uid) < Process.FIRST_APPLICATION_UID) {
+ return BLOCKED_REASON_NONE;
+ }
+
final long uidRuleConfig;
final long uidMatch;
try {
@@ -331,12 +337,6 @@
) {
throwIfPreT("isUidBlockedByFirewallChains is not available on pre-T devices");
- // System uids are not blocked by firewall chains, see bpf_progs/netd.c
- // TODO: b/348513058 - use UserHandle.isCore() once it is accessible
- if (UserHandle.getAppId(uid) < Process.FIRST_APPLICATION_UID) {
- return false;
- }
-
final int blockedReasons = getUidNetworkingBlockedReasons(
uid,
configurationMap,
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 4eaf973..8cf6e04 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -71,23 +71,29 @@
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.LruCache;
import android.util.Range;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import libcore.net.event.NetworkEventDispatcher;
import java.io.IOException;
import java.io.UncheckedIOException;
+import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@@ -1231,6 +1237,19 @@
@GuardedBy("mTetheringEventCallbacks")
private TetheringManager mTetheringManager;
+ // Cache of the most recently used NetworkCallback classes (not instances) -> method flags.
+ // 100 is chosen kind arbitrarily as an unlikely number of different types of NetworkCallback
+ // overrides that a process may have, and should generally not be reached (for example, the
+ // system server services.jar has been observed with dexdump to have only 16 when this was
+ // added, and a very large system services app only had 18).
+ // If this number is exceeded, the code will still function correctly, but re-registering
+ // using a network callback class that was used before, but 100+ other classes have been used in
+ // the meantime, will be a bit slower (as slow as the first registration) because
+ // getDeclaredMethodsFlag must re-examine the callback class to determine what methods it
+ // overrides.
+ private static final LruCache<Class<? extends NetworkCallback>, Integer> sMethodFlagsCache =
+ new LruCache<>(100);
+
private final Object mEnabledConnectivityManagerFeaturesLock = new Object();
// mEnabledConnectivityManagerFeatures is lazy-loaded in this ConnectivityManager instance, but
// fetched from ConnectivityService, where it is loaded in ConnectivityService startup, so it
@@ -3996,6 +4015,55 @@
*/
public static class NetworkCallback {
/**
+ * Bitmask of method flags with all flags set.
+ * @hide
+ */
+ public static final int DECLARED_METHODS_ALL = ~0;
+
+ /**
+ * Bitmask of method flags with no flag set.
+ * @hide
+ */
+ public static final int DECLARED_METHODS_NONE = 0;
+
+ // Tracks whether an instance was created via reflection without calling the constructor.
+ private final boolean mConstructorWasCalled;
+
+ /**
+ * Annotation for NetworkCallback methods to verify filtering is configured properly.
+ *
+ * This is only used in tests to ensure that tests fail when a new callback is added, or
+ * callbacks are modified, without updating
+ * {@link NetworkCallbackMethodsHolder#NETWORK_CB_METHODS} properly.
+ * @hide
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ @VisibleForTesting
+ public @interface FilteredCallback {
+ /**
+ * The NetworkCallback.METHOD_* ID of this method.
+ */
+ int methodId();
+
+ /**
+ * The ConnectivityManager.CALLBACK_* message that this method is directly called by.
+ *
+ * If this method is not called by any message, this should be
+ * {@link #CALLBACK_TRANSITIVE_CALLS_ONLY}.
+ */
+ int calledByCallbackId();
+
+ /**
+ * If this method may call other NetworkCallback methods, an array of methods it calls.
+ *
+ * Only direct calls (not transitive calls) should be included. The IDs must be
+ * NetworkCallback.METHOD_* IDs.
+ */
+ int[] mayCall() default {};
+ }
+
+ /**
* No flags associated with this callback.
* @hide
*/
@@ -4058,6 +4126,7 @@
throw new IllegalArgumentException("Invalid flags");
}
mFlags = flags;
+ mConstructorWasCalled = true;
}
/**
@@ -4075,7 +4144,9 @@
*
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONPRECHECK, calledByCallbackId = CALLBACK_PRECHECK)
public void onPreCheck(@NonNull Network network) {}
+ private static final int METHOD_ONPRECHECK = 1;
/**
* Called when the framework connects and has declared a new network ready for use.
@@ -4090,6 +4161,13 @@
* @param blocked Whether access to the {@link Network} is blocked due to system policy.
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONAVAILABLE_5ARGS,
+ calledByCallbackId = CALLBACK_AVAILABLE,
+ // If this list is modified, ConnectivityService#addAvailableStateUpdateCallbacks
+ // needs to be updated too.
+ mayCall = { METHOD_ONAVAILABLE_4ARGS,
+ METHOD_ONLOCALNETWORKINFOCHANGED,
+ METHOD_ONBLOCKEDSTATUSCHANGED_INT })
public final void onAvailable(@NonNull Network network,
@NonNull NetworkCapabilities networkCapabilities,
@NonNull LinkProperties linkProperties,
@@ -4102,6 +4180,7 @@
if (null != localInfo) onLocalNetworkInfoChanged(network, localInfo);
onBlockedStatusChanged(network, blocked);
}
+ private static final int METHOD_ONAVAILABLE_5ARGS = 2;
/**
* Legacy variant of onAvailable that takes a boolean blocked reason.
@@ -4114,6 +4193,15 @@
*
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONAVAILABLE_4ARGS,
+ calledByCallbackId = CALLBACK_TRANSITIVE_CALLS_ONLY,
+ // If this list is modified, ConnectivityService#addAvailableStateUpdateCallbacks
+ // needs to be updated too.
+ mayCall = { METHOD_ONAVAILABLE_1ARG,
+ METHOD_ONNETWORKSUSPENDED,
+ METHOD_ONCAPABILITIESCHANGED,
+ METHOD_ONLINKPROPERTIESCHANGED
+ })
public void onAvailable(@NonNull Network network,
@NonNull NetworkCapabilities networkCapabilities,
@NonNull LinkProperties linkProperties,
@@ -4127,6 +4215,7 @@
onLinkPropertiesChanged(network, linkProperties);
// No call to onBlockedStatusChanged here. That is done by the caller.
}
+ private static final int METHOD_ONAVAILABLE_4ARGS = 3;
/**
* Called when the framework connects and has declared a new network ready for use.
@@ -4157,7 +4246,10 @@
*
* @param network The {@link Network} of the satisfying network.
*/
+ @FilteredCallback(methodId = METHOD_ONAVAILABLE_1ARG,
+ calledByCallbackId = CALLBACK_TRANSITIVE_CALLS_ONLY)
public void onAvailable(@NonNull Network network) {}
+ private static final int METHOD_ONAVAILABLE_1ARG = 4;
/**
* Called when the network is about to be lost, typically because there are no outstanding
@@ -4176,7 +4268,9 @@
* connected for graceful handover; note that the network may still
* suffer a hard loss at any time.
*/
+ @FilteredCallback(methodId = METHOD_ONLOSING, calledByCallbackId = CALLBACK_LOSING)
public void onLosing(@NonNull Network network, int maxMsToLive) {}
+ private static final int METHOD_ONLOSING = 5;
/**
* Called when a network disconnects or otherwise no longer satisfies this request or
@@ -4197,7 +4291,9 @@
*
* @param network The {@link Network} lost.
*/
+ @FilteredCallback(methodId = METHOD_ONLOST, calledByCallbackId = CALLBACK_LOST)
public void onLost(@NonNull Network network) {}
+ private static final int METHOD_ONLOST = 6;
/**
* Called if no network is found within the timeout time specified in
@@ -4207,7 +4303,9 @@
* {@link NetworkRequest} will have already been removed and released, as if
* {@link #unregisterNetworkCallback(NetworkCallback)} had been called.
*/
+ @FilteredCallback(methodId = METHOD_ONUNAVAILABLE, calledByCallbackId = CALLBACK_UNAVAIL)
public void onUnavailable() {}
+ private static final int METHOD_ONUNAVAILABLE = 7;
/**
* Called when the network corresponding to this request changes capabilities but still
@@ -4224,8 +4322,11 @@
* @param networkCapabilities The new {@link NetworkCapabilities} for this
* network.
*/
+ @FilteredCallback(methodId = METHOD_ONCAPABILITIESCHANGED,
+ calledByCallbackId = CALLBACK_CAP_CHANGED)
public void onCapabilitiesChanged(@NonNull Network network,
@NonNull NetworkCapabilities networkCapabilities) {}
+ private static final int METHOD_ONCAPABILITIESCHANGED = 8;
/**
* Called when the network corresponding to this request changes {@link LinkProperties}.
@@ -4240,8 +4341,11 @@
* @param network The {@link Network} whose link properties have changed.
* @param linkProperties The new {@link LinkProperties} for this network.
*/
+ @FilteredCallback(methodId = METHOD_ONLINKPROPERTIESCHANGED,
+ calledByCallbackId = CALLBACK_IP_CHANGED)
public void onLinkPropertiesChanged(@NonNull Network network,
@NonNull LinkProperties linkProperties) {}
+ private static final int METHOD_ONLINKPROPERTIESCHANGED = 9;
/**
* Called when there is a change in the {@link LocalNetworkInfo} for this network.
@@ -4253,8 +4357,11 @@
* @param localNetworkInfo the new {@link LocalNetworkInfo} for this network.
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONLOCALNETWORKINFOCHANGED,
+ calledByCallbackId = CALLBACK_LOCAL_NETWORK_INFO_CHANGED)
public void onLocalNetworkInfoChanged(@NonNull Network network,
@NonNull LocalNetworkInfo localNetworkInfo) {}
+ private static final int METHOD_ONLOCALNETWORKINFOCHANGED = 10;
/**
* Called when the network the framework connected to for this request suspends data
@@ -4273,7 +4380,10 @@
*
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONNETWORKSUSPENDED,
+ calledByCallbackId = CALLBACK_SUSPENDED)
public void onNetworkSuspended(@NonNull Network network) {}
+ private static final int METHOD_ONNETWORKSUSPENDED = 11;
/**
* Called when the network the framework connected to for this request
@@ -4287,7 +4397,9 @@
*
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONNETWORKRESUMED, calledByCallbackId = CALLBACK_RESUMED)
public void onNetworkResumed(@NonNull Network network) {}
+ private static final int METHOD_ONNETWORKRESUMED = 12;
/**
* Called when access to the specified network is blocked or unblocked.
@@ -4300,7 +4412,10 @@
* @param network The {@link Network} whose blocked status has changed.
* @param blocked The blocked status of this {@link Network}.
*/
+ @FilteredCallback(methodId = METHOD_ONBLOCKEDSTATUSCHANGED_BOOL,
+ calledByCallbackId = CALLBACK_TRANSITIVE_CALLS_ONLY)
public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) {}
+ private static final int METHOD_ONBLOCKEDSTATUSCHANGED_BOOL = 13;
/**
* Called when access to the specified network is blocked or unblocked, or the reason for
@@ -4318,10 +4433,14 @@
* @param blocked The blocked status of this {@link Network}.
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONBLOCKEDSTATUSCHANGED_INT,
+ calledByCallbackId = CALLBACK_BLK_CHANGED,
+ mayCall = { METHOD_ONBLOCKEDSTATUSCHANGED_BOOL })
@SystemApi(client = MODULE_LIBRARIES)
public void onBlockedStatusChanged(@NonNull Network network, @BlockedReason int blocked) {
onBlockedStatusChanged(network, blocked != 0);
}
+ private static final int METHOD_ONBLOCKEDSTATUSCHANGED_INT = 14;
private NetworkRequest networkRequest;
private final int mFlags;
@@ -4349,6 +4468,7 @@
}
}
+ private static final int CALLBACK_TRANSITIVE_CALLS_ONLY = 0;
/** @hide */
public static final int CALLBACK_PRECHECK = 1;
/** @hide */
@@ -4373,10 +4493,13 @@
public static final int CALLBACK_BLK_CHANGED = 11;
/** @hide */
public static final int CALLBACK_LOCAL_NETWORK_INFO_CHANGED = 12;
+ // When adding new IDs, note CallbackQueue assumes callback IDs are at most 16 bits.
+
/** @hide */
public static String getCallbackName(int whichCallback) {
switch (whichCallback) {
+ case CALLBACK_TRANSITIVE_CALLS_ONLY: return "CALLBACK_TRANSITIVE_CALLS_ONLY";
case CALLBACK_PRECHECK: return "CALLBACK_PRECHECK";
case CALLBACK_AVAILABLE: return "CALLBACK_AVAILABLE";
case CALLBACK_LOSING: return "CALLBACK_LOSING";
@@ -4394,6 +4517,68 @@
}
}
+ /** @hide */
+ @VisibleForTesting
+ public static class NetworkCallbackMethod {
+ @NonNull
+ public final String mName;
+ @NonNull
+ public final Class<?>[] mParameterTypes;
+ // Bitmask of CALLBACK_* that may transitively call this method.
+ public final int mCallbacksCallingThisMethod;
+
+ public NetworkCallbackMethod(@NonNull String name, @NonNull Class<?>[] parameterTypes,
+ int callbacksCallingThisMethod) {
+ mName = name;
+ mParameterTypes = parameterTypes;
+ mCallbacksCallingThisMethod = callbacksCallingThisMethod;
+ }
+ }
+
+ // Holder class for the list of NetworkCallbackMethod. This ensures the list is only created
+ // once on first usage, and not just on ConnectivityManager class initialization.
+ /** @hide */
+ @VisibleForTesting
+ public static class NetworkCallbackMethodsHolder {
+ public static final NetworkCallbackMethod[] NETWORK_CB_METHODS =
+ new NetworkCallbackMethod[] {
+ method("onPreCheck", 1 << CALLBACK_PRECHECK, Network.class),
+ // Note the final overload of onAvailable is not included, since it cannot
+ // match any overridden method.
+ method("onAvailable", 1 << CALLBACK_AVAILABLE, Network.class),
+ method("onAvailable", 1 << CALLBACK_AVAILABLE,
+ Network.class, NetworkCapabilities.class,
+ LinkProperties.class, boolean.class),
+ method("onLosing", 1 << CALLBACK_LOSING, Network.class, int.class),
+ method("onLost", 1 << CALLBACK_LOST, Network.class),
+ method("onUnavailable", 1 << CALLBACK_UNAVAIL),
+ method("onCapabilitiesChanged",
+ 1 << CALLBACK_CAP_CHANGED | 1 << CALLBACK_AVAILABLE,
+ Network.class, NetworkCapabilities.class),
+ method("onLinkPropertiesChanged",
+ 1 << CALLBACK_IP_CHANGED | 1 << CALLBACK_AVAILABLE,
+ Network.class, LinkProperties.class),
+ method("onLocalNetworkInfoChanged",
+ 1 << CALLBACK_LOCAL_NETWORK_INFO_CHANGED | 1 << CALLBACK_AVAILABLE,
+ Network.class, LocalNetworkInfo.class),
+ method("onNetworkSuspended",
+ 1 << CALLBACK_SUSPENDED | 1 << CALLBACK_AVAILABLE, Network.class),
+ method("onNetworkResumed",
+ 1 << CALLBACK_RESUMED, Network.class),
+ method("onBlockedStatusChanged",
+ 1 << CALLBACK_BLK_CHANGED | 1 << CALLBACK_AVAILABLE,
+ Network.class, boolean.class),
+ method("onBlockedStatusChanged",
+ 1 << CALLBACK_BLK_CHANGED | 1 << CALLBACK_AVAILABLE,
+ Network.class, int.class),
+ };
+
+ private static NetworkCallbackMethod method(
+ String name, int callbacksCallingThisMethod, Class<?>... args) {
+ return new NetworkCallbackMethod(name, args, callbacksCallingThisMethod);
+ }
+ }
+
private static class CallbackHandler extends Handler {
private static final String TAG = "ConnectivityManager.CallbackHandler";
private static final boolean DBG = false;
@@ -4513,6 +4698,14 @@
if (reqType != TRACK_DEFAULT && reqType != TRACK_SYSTEM_DEFAULT && need == null) {
throw new IllegalArgumentException("null NetworkCapabilities");
}
+
+
+ final boolean useDeclaredMethods = isFeatureEnabled(
+ FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS);
+ // Set all bits if the feature is disabled
+ int declaredMethodsFlag = useDeclaredMethods
+ ? tryGetDeclaredMethodsFlag(callback)
+ : NetworkCallback.DECLARED_METHODS_ALL;
final NetworkRequest request;
final String callingPackageName = mContext.getOpPackageName();
try {
@@ -4529,11 +4722,12 @@
if (reqType == LISTEN) {
request = mService.listenForNetwork(
need, messenger, binder, callbackFlags, callingPackageName,
- getAttributionTag());
+ getAttributionTag(), declaredMethodsFlag);
} else {
request = mService.requestNetwork(
asUid, need, reqType.ordinal(), messenger, timeoutMs, binder,
- legacyType, callbackFlags, callingPackageName, getAttributionTag());
+ legacyType, callbackFlags, callingPackageName, getAttributionTag(),
+ declaredMethodsFlag);
}
if (request != null) {
sCallbacks.put(request, callback);
@@ -4548,6 +4742,108 @@
return request;
}
+ private int tryGetDeclaredMethodsFlag(@NonNull NetworkCallback cb) {
+ if (!cb.mConstructorWasCalled) {
+ // Do not use the optimization if the callback was created via reflection or mocking,
+ // as for example with dexmaker-mockito-inline methods will be instrumented without
+ // using subclasses. This does not catch all cases as it is still possible to call the
+ // constructor when creating mocks, but by default constructors are not called in that
+ // case.
+ return NetworkCallback.DECLARED_METHODS_ALL;
+ }
+ try {
+ return getDeclaredMethodsFlag(cb.getClass());
+ } catch (LinkageError e) {
+ // This may happen if some methods reference inaccessible classes in their arguments
+ // (for example b/261807130).
+ Log.w(TAG, "Could not get methods from NetworkCallback class", e);
+ // Fall through
+ } catch (Throwable e) {
+ // Log.wtf would be best but this is in app process, so the TerribleFailureHandler may
+ // have unknown effects, possibly crashing the app (default behavior on eng builds or
+ // if the WTF_IS_FATAL setting is set).
+ Log.e(TAG, "Unexpected error while getting methods from NetworkCallback class", e);
+ // Fall through
+ }
+ return NetworkCallback.DECLARED_METHODS_ALL;
+ }
+
+ private static int getDeclaredMethodsFlag(@NonNull Class<? extends NetworkCallback> clazz) {
+ final Integer cachedFlags = sMethodFlagsCache.get(clazz);
+ // As this is not synchronized, it is possible that this method will calculate the
+ // flags for a given class multiple times, but that is fine. LruCache itself is thread-safe.
+ if (cachedFlags != null) {
+ return cachedFlags;
+ }
+
+ int flag = 0;
+ // This uses getMethods instead of getDeclaredMethods, to make sure that if A overrides B
+ // that overrides NetworkCallback, A.getMethods also returns methods declared by B.
+ for (Method classMethod : clazz.getMethods()) {
+ final Class<?> declaringClass = classMethod.getDeclaringClass();
+ if (declaringClass == NetworkCallback.class) {
+ // The callback is as defined by NetworkCallback and not overridden
+ continue;
+ }
+ if (declaringClass == Object.class) {
+ // Optimization: no need to try to match callbacks for methods declared by Object
+ continue;
+ }
+ flag |= getCallbackIdsCallingThisMethod(classMethod);
+ }
+
+ if (flag == 0) {
+ // dexmaker-mockito-inline (InlineDexmakerMockMaker), for example for mockito-extended,
+ // modifies bytecode of classes in-place to add hooks instead of creating subclasses,
+ // which would not be detected. When no method is found, fall back to enabling callbacks
+ // for all methods.
+ // This will not catch the case where both NetworkCallback bytecode is modified and a
+ // subclass of NetworkCallback that has some overridden methods are used. But this kind
+ // of bytecode injection is only possible in debuggable processes, with a JVMTI debug
+ // agent attached, so it should not cause real issues.
+ // There may be legitimate cases where an empty callback is filed with no method
+ // overridden, for example requestNetwork(requestForCell, new NetworkCallback()) which
+ // would ensure that one cell network stays up. But there is no way to differentiate
+ // such NetworkCallbacks from a mock that called the constructor, so this code will
+ // register the callback with DECLARED_METHODS_ALL and turn off the optimization in that
+ // case. Apps are not expected to do this often anyway since the usefulness is very
+ // limited.
+ flag = NetworkCallback.DECLARED_METHODS_ALL;
+ }
+ sMethodFlagsCache.put(clazz, flag);
+ return flag;
+ }
+
+ /**
+ * Find out which of the base methods in NetworkCallback will call this method.
+ *
+ * For example, in the case of onLinkPropertiesChanged, this will be
+ * (1 << CALLBACK_IP_CHANGED) | (1 << CALLBACK_AVAILABLE).
+ */
+ private static int getCallbackIdsCallingThisMethod(@NonNull Method method) {
+ for (NetworkCallbackMethod baseMethod : NetworkCallbackMethodsHolder.NETWORK_CB_METHODS) {
+ if (!baseMethod.mName.equals(method.getName())) {
+ continue;
+ }
+ Class<?>[] methodParams = method.getParameterTypes();
+
+ // As per JLS 8.4.8.1., a method m1 must have a subsignature of method m2 to override
+ // it. And as per JLS 8.4.2, this means the erasure of the signature of m2 must be the
+ // same as the signature of m1. Since type erasure is done at compile time, with
+ // reflection the erased types are already observed, so the (erased) parameter types
+ // must be equal.
+ // So for example a method that is identical to a NetworkCallback method, except with
+ // one parameter being a subclass of the parameter in the original method, will never
+ // be called since it is not an override (the erasure of the arguments are not the same)
+ // Therefore, the method is an override only if methodParams is exactly equal to
+ // the base method's parameter types.
+ if (Arrays.equals(baseMethod.mParameterTypes, methodParams)) {
+ return baseMethod.mCallbacksCallingThisMethod;
+ }
+ }
+ return 0;
+ }
+
private boolean isFeatureEnabled(@ConnectivityManagerFeature long connectivityManagerFeature) {
synchronized (mEnabledConnectivityManagerFeaturesLock) {
if (mEnabledConnectivityManagerFeatures == null) {
@@ -6446,21 +6742,11 @@
}
}
- private static final Object sRoutingCoordinatorManagerLock = new Object();
- @GuardedBy("sRoutingCoordinatorManagerLock")
- private static RoutingCoordinatorManager sRoutingCoordinatorManager = null;
/** @hide */
@RequiresApi(Build.VERSION_CODES.S)
- public RoutingCoordinatorManager getRoutingCoordinatorManager() {
+ public IBinder getRoutingCoordinatorService() {
try {
- synchronized (sRoutingCoordinatorManagerLock) {
- if (null == sRoutingCoordinatorManager) {
- sRoutingCoordinatorManager = new RoutingCoordinatorManager(mContext,
- IRoutingCoordinator.Stub.asInterface(
- mService.getRoutingCoordinatorService()));
- }
- return sRoutingCoordinatorManager;
- }
+ return mService.getRoutingCoordinatorService();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index f9de8ed..988cc92 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -153,7 +153,8 @@
NetworkRequest requestNetwork(int uid, in NetworkCapabilities networkCapabilities, int reqType,
in Messenger messenger, int timeoutSec, in IBinder binder, int legacy,
- int callbackFlags, String callingPackageName, String callingAttributionTag);
+ int callbackFlags, String callingPackageName, String callingAttributionTag,
+ int declaredMethodsFlag);
NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities,
in PendingIntent operation, String callingPackageName, String callingAttributionTag);
@@ -162,7 +163,7 @@
NetworkRequest listenForNetwork(in NetworkCapabilities networkCapabilities,
in Messenger messenger, in IBinder binder, int callbackFlags, String callingPackageName,
- String callingAttributionTag);
+ String callingAttributionTag, int declaredMethodsFlag);
void pendingListenForNetwork(in NetworkCapabilities networkCapabilities,
in PendingIntent operation, String callingPackageName,
diff --git a/framework/src/android/net/connectivity/ConnectivityInternalApiUtil.java b/framework/src/android/net/connectivity/ConnectivityInternalApiUtil.java
index 79f1f65..6e87ed3 100644
--- a/framework/src/android/net/connectivity/ConnectivityInternalApiUtil.java
+++ b/framework/src/android/net/connectivity/ConnectivityInternalApiUtil.java
@@ -18,7 +18,6 @@
import android.content.Context;
import android.net.ConnectivityManager;
-import android.net.RoutingCoordinatorManager;
import android.os.Build;
import android.os.IBinder;
@@ -54,8 +53,8 @@
* @return an instance of the coordinator manager
*/
@RequiresApi(Build.VERSION_CODES.S)
- public static RoutingCoordinatorManager getRoutingCoordinatorManager(Context ctx) {
+ public static IBinder getRoutingCoordinator(Context ctx) {
final ConnectivityManager cm = ctx.getSystemService(ConnectivityManager.class);
- return cm.getRoutingCoordinatorManager();
+ return cm.getRoutingCoordinatorService();
}
}
diff --git a/nearby/framework/Android.bp b/nearby/framework/Android.bp
index 4be102c..41a28a0 100644
--- a/nearby/framework/Android.bp
+++ b/nearby/framework/Android.bp
@@ -58,4 +58,7 @@
visibility: [
"//packages/modules/Connectivity/nearby/tests:__subpackages__",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/nearby/framework/lint-baseline.xml b/nearby/framework/lint-baseline.xml
new file mode 100644
index 0000000..e1081ee
--- /dev/null
+++ b/nearby/framework/lint-baseline.xml
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha08" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha08">
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="87"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="87"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="87"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="529"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="573"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="605"
+ column="17"/>
+ </issue>
+
+</issues>
diff --git a/netbpfload/Android.bp b/netbpfload/Android.bp
index 908bb13..b8c0ce7 100644
--- a/netbpfload/Android.bp
+++ b/netbpfload/Android.bp
@@ -48,10 +48,7 @@
"libbase",
"liblog",
],
- srcs: [
- "loader.cpp",
- "NetBpfLoad.cpp",
- ],
+ srcs: ["NetBpfLoad.cpp"],
apex_available: [
"com.android.tethering",
"//apex_available:platform",
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index e9c6d8a..afb44cc 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017-2023 The Android Open Source Project
+ * Copyright (C) 2018-2024 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.
@@ -14,49 +14,1218 @@
* limitations under the License.
*/
-#ifndef LOG_TAG
#define LOG_TAG "NetBpfLoad"
-#endif
#include <arpa/inet.h>
+#include <cstdlib>
#include <dirent.h>
#include <elf.h>
+#include <errno.h>
#include <error.h>
#include <fcntl.h>
+#include <fstream>
#include <inttypes.h>
+#include <iostream>
#include <linux/bpf.h>
+#include <linux/elf.h>
#include <linux/unistd.h>
+#include <log/log.h>
#include <net/if.h>
+#include <optional>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <unistd.h>
-
+#include <string>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
+#include <sys/utsname.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <unordered_map>
+#include <vector>
-#include <android/api-level.h>
+#include <android-base/cmsg.h>
+#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/macros.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
-#include <log/log.h>
+#include <android/api-level.h>
#include "BpfSyscallWrappers.h"
#include "bpf/BpfUtils.h"
-#include "loader.h"
+#include "bpf/bpf_map_def.h"
+
+using android::base::EndsWith;
+using android::base::StartsWith;
+using android::base::unique_fd;
+using std::ifstream;
+using std::ios;
+using std::optional;
+using std::string;
+using std::vector;
namespace android {
namespace bpf {
-using base::StartsWith;
-using base::EndsWith;
-using std::string;
+// Bpf programs may specify per-program & per-map selinux_context and pin_subdir.
+//
+// The BpfLoader needs to convert these bpf.o specified strings into an enum
+// for internal use (to check that valid values were specified for the specific
+// location of the bpf.o file).
+//
+// It also needs to map selinux_context's into pin_subdir's.
+// This is because of how selinux_context is actually implemented via pin+rename.
+//
+// Thus 'domain' enumerates all selinux_context's/pin_subdir's that the BpfLoader
+// is aware of. Thus there currently needs to be a 1:1 mapping between the two.
+//
+enum class domain : int {
+ unspecified = 0, // means just use the default for that specific pin location
+ tethering, // (S+) fs_bpf_tethering /sys/fs/bpf/tethering
+ net_private, // (T+) fs_bpf_net_private /sys/fs/bpf/net_private
+ net_shared, // (T+) fs_bpf_net_shared /sys/fs/bpf/net_shared
+ netd_readonly, // (T+) fs_bpf_netd_readonly /sys/fs/bpf/netd_readonly
+ netd_shared, // (T+) fs_bpf_netd_shared /sys/fs/bpf/netd_shared
+};
+
+static constexpr domain AllDomains[] = {
+ domain::unspecified,
+ domain::tethering,
+ domain::net_private,
+ domain::net_shared,
+ domain::netd_readonly,
+ domain::netd_shared,
+};
+
+static constexpr bool specified(domain d) {
+ return d != domain::unspecified;
+}
+
+struct Location {
+ const char* const dir = "";
+ const char* const prefix = "";
+};
+
+// Returns the build type string (from ro.build.type).
+const std::string& getBuildType() {
+ static std::string t = android::base::GetProperty("ro.build.type", "unknown");
+ return t;
+}
+
+// The following functions classify the 3 Android build types.
+inline bool isEng() {
+ return getBuildType() == "eng";
+}
+
+inline bool isUser() {
+ return getBuildType() == "user";
+}
+
+inline bool isUserdebug() {
+ return getBuildType() == "userdebug";
+}
+
+#define BPF_FS_PATH "/sys/fs/bpf/"
+
+// Size of the BPF log buffer for verifier logging
+#define BPF_LOAD_LOG_SZ 0xfffff
+
+static unsigned int page_size = static_cast<unsigned int>(getpagesize());
+
+constexpr const char* lookupSelinuxContext(const domain d) {
+ switch (d) {
+ case domain::unspecified: return "";
+ case domain::tethering: return "fs_bpf_tethering";
+ case domain::net_private: return "fs_bpf_net_private";
+ case domain::net_shared: return "fs_bpf_net_shared";
+ case domain::netd_readonly: return "fs_bpf_netd_readonly";
+ case domain::netd_shared: return "fs_bpf_netd_shared";
+ }
+}
+
+domain getDomainFromSelinuxContext(const char s[BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE]) {
+ for (domain d : AllDomains) {
+ // Not sure how to enforce this at compile time, so abort() bpfloader at boot instead
+ if (strlen(lookupSelinuxContext(d)) >= BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE) abort();
+ if (!strncmp(s, lookupSelinuxContext(d), BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE)) return d;
+ }
+ ALOGE("unrecognized selinux_context '%-32s'", s);
+ // Note: we *can* just abort() here as we only load bpf .o files shipped
+ // in the same mainline module / apex as NetBpfLoad itself.
+ abort();
+}
+
+constexpr const char* lookupPinSubdir(const domain d, const char* const unspecified = "") {
+ switch (d) {
+ case domain::unspecified: return unspecified;
+ case domain::tethering: return "tethering/";
+ case domain::net_private: return "net_private/";
+ case domain::net_shared: return "net_shared/";
+ case domain::netd_readonly: return "netd_readonly/";
+ case domain::netd_shared: return "netd_shared/";
+ }
+};
+
+domain getDomainFromPinSubdir(const char s[BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE]) {
+ for (domain d : AllDomains) {
+ // Not sure how to enforce this at compile time, so abort() bpfloader at boot instead
+ if (strlen(lookupPinSubdir(d)) >= BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE) abort();
+ if (!strncmp(s, lookupPinSubdir(d), BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE)) return d;
+ }
+ ALOGE("unrecognized pin_subdir '%-32s'", s);
+ // Note: we *can* just abort() here as we only load bpf .o files shipped
+ // in the same mainline module / apex as NetBpfLoad itself.
+ abort();
+}
+
+static string pathToObjName(const string& path) {
+ // extract everything after the final slash, ie. this is the filename 'foo@1.o' or 'bar.o'
+ string filename = android::base::Split(path, "/").back();
+ // strip off everything from the final period onwards (strip '.o' suffix), ie. 'foo@1' or 'bar'
+ string name = filename.substr(0, filename.find_last_of('.'));
+ // strip any potential @1 suffix, this will leave us with just 'foo' or 'bar'
+ // this can be used to provide duplicate programs (mux based on the bpfloader version)
+ return name.substr(0, name.find_last_of('@'));
+}
+
+typedef struct {
+ const char* name;
+ enum bpf_prog_type type;
+ enum bpf_attach_type attach_type;
+} sectionType;
+
+/*
+ * Map section name prefixes to program types, the section name will be:
+ * SECTION(<prefix>/<name-of-program>)
+ * For example:
+ * SECTION("tracepoint/sched_switch_func") where sched_switch_funcs
+ * is the name of the program, and tracepoint is the type.
+ *
+ * However, be aware that you should not be directly using the SECTION() macro.
+ * Instead use the DEFINE_(BPF|XDP)_(PROG|MAP)... & LICENSE/CRITICAL macros.
+ *
+ * Programs shipped inside the tethering apex should be limited to networking stuff,
+ * as KPROBE, PERF_EVENT, TRACEPOINT are dangerous to use from mainline updatable code,
+ * since they are less stable abi/api and may conflict with platform uses of bpf.
+ */
+sectionType sectionNameTypes[] = {
+ {"bind4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_BIND},
+ {"bind6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_BIND},
+ {"cgroupskb/", BPF_PROG_TYPE_CGROUP_SKB},
+ {"cgroupsock/", BPF_PROG_TYPE_CGROUP_SOCK},
+ {"cgroupsockcreate/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET_SOCK_CREATE},
+ {"cgroupsockrelease/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET_SOCK_RELEASE},
+ {"connect4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_CONNECT},
+ {"connect6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_CONNECT},
+ {"egress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_EGRESS},
+ {"getsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_GETSOCKOPT},
+ {"ingress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_INGRESS},
+ {"postbind4/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET4_POST_BIND},
+ {"postbind6/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET6_POST_BIND},
+ {"recvmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_RECVMSG},
+ {"recvmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_RECVMSG},
+ {"schedact/", BPF_PROG_TYPE_SCHED_ACT},
+ {"schedcls/", BPF_PROG_TYPE_SCHED_CLS},
+ {"sendmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_SENDMSG},
+ {"sendmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_SENDMSG},
+ {"setsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT},
+ {"skfilter/", BPF_PROG_TYPE_SOCKET_FILTER},
+ {"sockops/", BPF_PROG_TYPE_SOCK_OPS, BPF_CGROUP_SOCK_OPS},
+ {"sysctl", BPF_PROG_TYPE_CGROUP_SYSCTL, BPF_CGROUP_SYSCTL},
+ {"xdp/", BPF_PROG_TYPE_XDP},
+};
+
+typedef struct {
+ enum bpf_prog_type type;
+ enum bpf_attach_type attach_type;
+ string name;
+ vector<char> data;
+ vector<char> rel_data;
+ optional<struct bpf_prog_def> prog_def;
+
+ unique_fd prog_fd; /* fd after loading */
+} codeSection;
+
+static int readElfHeader(ifstream& elfFile, Elf64_Ehdr* eh) {
+ elfFile.seekg(0);
+ if (elfFile.fail()) return -1;
+
+ if (!elfFile.read((char*)eh, sizeof(*eh))) return -1;
+
+ return 0;
+}
+
+/* Reads all section header tables into an Shdr array */
+static int readSectionHeadersAll(ifstream& elfFile, vector<Elf64_Shdr>& shTable) {
+ Elf64_Ehdr eh;
+ int ret = 0;
+
+ ret = readElfHeader(elfFile, &eh);
+ if (ret) return ret;
+
+ elfFile.seekg(eh.e_shoff);
+ if (elfFile.fail()) return -1;
+
+ /* Read shdr table entries */
+ shTable.resize(eh.e_shnum);
+
+ if (!elfFile.read((char*)shTable.data(), (eh.e_shnum * eh.e_shentsize))) return -ENOMEM;
+
+ return 0;
+}
+
+/* Read a section by its index - for ex to get sec hdr strtab blob */
+static int readSectionByIdx(ifstream& elfFile, int id, vector<char>& sec) {
+ vector<Elf64_Shdr> shTable;
+ int ret = readSectionHeadersAll(elfFile, shTable);
+ if (ret) return ret;
+
+ elfFile.seekg(shTable[id].sh_offset);
+ if (elfFile.fail()) return -1;
+
+ sec.resize(shTable[id].sh_size);
+ if (!elfFile.read(sec.data(), shTable[id].sh_size)) return -1;
+
+ return 0;
+}
+
+/* Read whole section header string table */
+static int readSectionHeaderStrtab(ifstream& elfFile, vector<char>& strtab) {
+ Elf64_Ehdr eh;
+ int ret = readElfHeader(elfFile, &eh);
+ if (ret) return ret;
+
+ ret = readSectionByIdx(elfFile, eh.e_shstrndx, strtab);
+ if (ret) return ret;
+
+ return 0;
+}
+
+/* Get name from offset in strtab */
+static int getSymName(ifstream& elfFile, int nameOff, string& name) {
+ int ret;
+ vector<char> secStrTab;
+
+ ret = readSectionHeaderStrtab(elfFile, secStrTab);
+ if (ret) return ret;
+
+ if (nameOff >= (int)secStrTab.size()) return -1;
+
+ name = string((char*)secStrTab.data() + nameOff);
+ return 0;
+}
+
+/* Reads a full section by name - example to get the GPL license */
+static int readSectionByName(const char* name, ifstream& elfFile, vector<char>& data) {
+ vector<char> secStrTab;
+ vector<Elf64_Shdr> shTable;
+ int ret;
+
+ ret = readSectionHeadersAll(elfFile, shTable);
+ if (ret) return ret;
+
+ ret = readSectionHeaderStrtab(elfFile, secStrTab);
+ if (ret) return ret;
+
+ for (int i = 0; i < (int)shTable.size(); i++) {
+ char* secname = secStrTab.data() + shTable[i].sh_name;
+ if (!secname) continue;
+
+ if (!strcmp(secname, name)) {
+ vector<char> dataTmp;
+ dataTmp.resize(shTable[i].sh_size);
+
+ elfFile.seekg(shTable[i].sh_offset);
+ if (elfFile.fail()) return -1;
+
+ if (!elfFile.read((char*)dataTmp.data(), shTable[i].sh_size)) return -1;
+
+ data = dataTmp;
+ return 0;
+ }
+ }
+ return -2;
+}
+
+unsigned int readSectionUint(const char* name, ifstream& elfFile, unsigned int defVal) {
+ vector<char> theBytes;
+ int ret = readSectionByName(name, elfFile, theBytes);
+ if (ret) {
+ ALOGD("Couldn't find section %s (defaulting to %u [0x%x]).", name, defVal, defVal);
+ return defVal;
+ } else if (theBytes.size() < sizeof(unsigned int)) {
+ ALOGE("Section %s too short (defaulting to %u [0x%x]).", name, defVal, defVal);
+ return defVal;
+ } else {
+ // decode first 4 bytes as LE32 uint, there will likely be more bytes due to alignment.
+ unsigned int value = static_cast<unsigned char>(theBytes[3]);
+ value <<= 8;
+ value += static_cast<unsigned char>(theBytes[2]);
+ value <<= 8;
+ value += static_cast<unsigned char>(theBytes[1]);
+ value <<= 8;
+ value += static_cast<unsigned char>(theBytes[0]);
+ ALOGI("Section %s value is %u [0x%x]", name, value, value);
+ return value;
+ }
+}
+
+static int readSectionByType(ifstream& elfFile, int type, vector<char>& data) {
+ int ret;
+ vector<Elf64_Shdr> shTable;
+
+ ret = readSectionHeadersAll(elfFile, shTable);
+ if (ret) return ret;
+
+ for (int i = 0; i < (int)shTable.size(); i++) {
+ if ((int)shTable[i].sh_type != type) continue;
+
+ vector<char> dataTmp;
+ dataTmp.resize(shTable[i].sh_size);
+
+ elfFile.seekg(shTable[i].sh_offset);
+ if (elfFile.fail()) return -1;
+
+ if (!elfFile.read((char*)dataTmp.data(), shTable[i].sh_size)) return -1;
+
+ data = dataTmp;
+ return 0;
+ }
+ return -2;
+}
+
+static bool symCompare(Elf64_Sym a, Elf64_Sym b) {
+ return (a.st_value < b.st_value);
+}
+
+static int readSymTab(ifstream& elfFile, int sort, vector<Elf64_Sym>& data) {
+ int ret, numElems;
+ Elf64_Sym* buf;
+ vector<char> secData;
+
+ ret = readSectionByType(elfFile, SHT_SYMTAB, secData);
+ if (ret) return ret;
+
+ buf = (Elf64_Sym*)secData.data();
+ numElems = (secData.size() / sizeof(Elf64_Sym));
+ data.assign(buf, buf + numElems);
+
+ if (sort) std::sort(data.begin(), data.end(), symCompare);
+ return 0;
+}
+
+static enum bpf_prog_type getSectionType(string& name) {
+ for (auto& snt : sectionNameTypes)
+ if (StartsWith(name, snt.name)) return snt.type;
+
+ return BPF_PROG_TYPE_UNSPEC;
+}
+
+/*
+static string getSectionName(enum bpf_prog_type type)
+{
+ for (auto& snt : sectionNameTypes)
+ if (snt.type == type)
+ return string(snt.name);
+
+ return "UNKNOWN SECTION NAME " + std::to_string(type);
+}
+*/
+
+static int readProgDefs(ifstream& elfFile, vector<struct bpf_prog_def>& pd,
+ size_t sizeOfBpfProgDef) {
+ vector<char> pdData;
+ int ret = readSectionByName("progs", elfFile, pdData);
+ if (ret) return ret;
+
+ if (pdData.size() % sizeOfBpfProgDef) {
+ ALOGE("readProgDefs failed due to improper sized progs section, %zu %% %zu != 0",
+ pdData.size(), sizeOfBpfProgDef);
+ return -1;
+ };
+
+ int progCount = pdData.size() / sizeOfBpfProgDef;
+ pd.resize(progCount);
+ size_t trimmedSize = std::min(sizeOfBpfProgDef, sizeof(struct bpf_prog_def));
+
+ const char* dataPtr = pdData.data();
+ for (auto& p : pd) {
+ // First we zero initialize
+ memset(&p, 0, sizeof(p));
+ // Then we set non-zero defaults
+ p.bpfloader_max_ver = DEFAULT_BPFLOADER_MAX_VER; // v1.0
+ // Then we copy over the structure prefix from the ELF file.
+ memcpy(&p, dataPtr, trimmedSize);
+ // Move to next struct in the ELF file
+ dataPtr += sizeOfBpfProgDef;
+ }
+ return 0;
+}
+
+static int getSectionSymNames(ifstream& elfFile, const string& sectionName, vector<string>& names,
+ optional<unsigned> symbolType = std::nullopt) {
+ int ret;
+ string name;
+ vector<Elf64_Sym> symtab;
+ vector<Elf64_Shdr> shTable;
+
+ ret = readSymTab(elfFile, 1 /* sort */, symtab);
+ if (ret) return ret;
+
+ /* Get index of section */
+ ret = readSectionHeadersAll(elfFile, shTable);
+ if (ret) return ret;
+
+ int sec_idx = -1;
+ for (int i = 0; i < (int)shTable.size(); i++) {
+ ret = getSymName(elfFile, shTable[i].sh_name, name);
+ if (ret) return ret;
+
+ if (!name.compare(sectionName)) {
+ sec_idx = i;
+ break;
+ }
+ }
+
+ /* No section found with matching name*/
+ if (sec_idx == -1) {
+ ALOGW("No %s section could be found in elf object", sectionName.c_str());
+ return -1;
+ }
+
+ for (int i = 0; i < (int)symtab.size(); i++) {
+ if (symbolType.has_value() && ELF_ST_TYPE(symtab[i].st_info) != symbolType) continue;
+
+ if (symtab[i].st_shndx == sec_idx) {
+ string s;
+ ret = getSymName(elfFile, symtab[i].st_name, s);
+ if (ret) return ret;
+ names.push_back(s);
+ }
+ }
+
+ return 0;
+}
+
+/* Read a section by its index - for ex to get sec hdr strtab blob */
+static int readCodeSections(ifstream& elfFile, vector<codeSection>& cs, size_t sizeOfBpfProgDef) {
+ vector<Elf64_Shdr> shTable;
+ int entries, ret = 0;
+
+ ret = readSectionHeadersAll(elfFile, shTable);
+ if (ret) return ret;
+ entries = shTable.size();
+
+ vector<struct bpf_prog_def> pd;
+ ret = readProgDefs(elfFile, pd, sizeOfBpfProgDef);
+ if (ret) return ret;
+ vector<string> progDefNames;
+ ret = getSectionSymNames(elfFile, "progs", progDefNames);
+ if (!pd.empty() && ret) return ret;
+
+ for (int i = 0; i < entries; i++) {
+ string name;
+ codeSection cs_temp;
+ cs_temp.type = BPF_PROG_TYPE_UNSPEC;
+
+ ret = getSymName(elfFile, shTable[i].sh_name, name);
+ if (ret) return ret;
+
+ enum bpf_prog_type ptype = getSectionType(name);
+
+ if (ptype == BPF_PROG_TYPE_UNSPEC) continue;
+
+ // This must be done before '/' is replaced with '_'.
+ for (auto& snt : sectionNameTypes)
+ if (StartsWith(name, snt.name)) cs_temp.attach_type = snt.attach_type;
+
+ string oldName = name;
+
+ // convert all slashes to underscores
+ std::replace(name.begin(), name.end(), '/', '_');
+
+ cs_temp.type = ptype;
+ cs_temp.name = name;
+
+ ret = readSectionByIdx(elfFile, i, cs_temp.data);
+ if (ret) return ret;
+ ALOGV("Loaded code section %d (%s)", i, name.c_str());
+
+ vector<string> csSymNames;
+ ret = getSectionSymNames(elfFile, oldName, csSymNames, STT_FUNC);
+ if (ret || !csSymNames.size()) return ret;
+ for (size_t i = 0; i < progDefNames.size(); ++i) {
+ if (!progDefNames[i].compare(csSymNames[0] + "_def")) {
+ cs_temp.prog_def = pd[i];
+ break;
+ }
+ }
+
+ /* Check for rel section */
+ if (cs_temp.data.size() > 0 && i < entries) {
+ ret = getSymName(elfFile, shTable[i + 1].sh_name, name);
+ if (ret) return ret;
+
+ if (name == (".rel" + oldName)) {
+ ret = readSectionByIdx(elfFile, i + 1, cs_temp.rel_data);
+ if (ret) return ret;
+ ALOGV("Loaded relo section %d (%s)", i, name.c_str());
+ }
+ }
+
+ if (cs_temp.data.size() > 0) {
+ cs.push_back(std::move(cs_temp));
+ ALOGV("Adding section %d to cs list", i);
+ }
+ }
+ return 0;
+}
+
+static int getSymNameByIdx(ifstream& elfFile, int index, string& name) {
+ vector<Elf64_Sym> symtab;
+ int ret = 0;
+
+ ret = readSymTab(elfFile, 0 /* !sort */, symtab);
+ if (ret) return ret;
+
+ if (index >= (int)symtab.size()) return -1;
+
+ return getSymName(elfFile, symtab[index].st_name, name);
+}
+
+static bool mapMatchesExpectations(const unique_fd& fd, const string& mapName,
+ const struct bpf_map_def& mapDef, const enum bpf_map_type type) {
+ // bpfGetFd... family of functions require at minimum a 4.14 kernel,
+ // so on 4.9-T kernels just pretend the map matches our expectations.
+ // Additionally we'll get almost equivalent test coverage on newer devices/kernels.
+ // This is because the primary failure mode we're trying to detect here
+ // is either a source code misconfiguration (which is likely kernel independent)
+ // or a newly introduced kernel feature/bug (which is unlikely to get backported to 4.9).
+ if (!isAtLeastKernelVersion(4, 14, 0)) return true;
+
+ // Assuming fd is a valid Bpf Map file descriptor then
+ // all the following should always succeed on a 4.14+ kernel.
+ // If they somehow do fail, they'll return -1 (and set errno),
+ // which should then cause (among others) a key_size mismatch.
+ int fd_type = bpfGetFdMapType(fd);
+ int fd_key_size = bpfGetFdKeySize(fd);
+ int fd_value_size = bpfGetFdValueSize(fd);
+ int fd_max_entries = bpfGetFdMaxEntries(fd);
+ int fd_map_flags = bpfGetFdMapFlags(fd);
+
+ // DEVMAPs are readonly from the bpf program side's point of view, as such
+ // the kernel in kernel/bpf/devmap.c dev_map_init_map() will set the flag
+ int desired_map_flags = (int)mapDef.map_flags;
+ if (type == BPF_MAP_TYPE_DEVMAP || type == BPF_MAP_TYPE_DEVMAP_HASH)
+ desired_map_flags |= BPF_F_RDONLY_PROG;
+
+ // The .h file enforces that this is a power of two, and page size will
+ // also always be a power of two, so this logic is actually enough to
+ // force it to be a multiple of the page size, as required by the kernel.
+ unsigned int desired_max_entries = mapDef.max_entries;
+ if (type == BPF_MAP_TYPE_RINGBUF) {
+ if (desired_max_entries < page_size) desired_max_entries = page_size;
+ }
+
+ // The following checks should *never* trigger, if one of them somehow does,
+ // it probably means a bpf .o file has been changed/replaced at runtime
+ // and bpfloader was manually rerun (normally it should only run *once*
+ // early during the boot process).
+ // Another possibility is that something is misconfigured in the code:
+ // most likely a shared map is declared twice differently.
+ // But such a change should never be checked into the source tree...
+ if ((fd_type == type) &&
+ (fd_key_size == (int)mapDef.key_size) &&
+ (fd_value_size == (int)mapDef.value_size) &&
+ (fd_max_entries == (int)desired_max_entries) &&
+ (fd_map_flags == desired_map_flags)) {
+ return true;
+ }
+
+ ALOGE("bpf map name %s mismatch: desired/found: "
+ "type:%d/%d key:%u/%d value:%u/%d entries:%u/%d flags:%u/%d",
+ mapName.c_str(), type, fd_type, mapDef.key_size, fd_key_size, mapDef.value_size,
+ fd_value_size, mapDef.max_entries, fd_max_entries, desired_map_flags, fd_map_flags);
+ return false;
+}
+
+static int createMaps(const char* elfPath, ifstream& elfFile, vector<unique_fd>& mapFds,
+ const char* prefix, const size_t sizeOfBpfMapDef,
+ const unsigned int bpfloader_ver) {
+ int ret;
+ vector<char> mdData;
+ vector<struct bpf_map_def> md;
+ vector<string> mapNames;
+ string objName = pathToObjName(string(elfPath));
+
+ ret = readSectionByName("maps", elfFile, mdData);
+ if (ret == -2) return 0; // no maps to read
+ if (ret) return ret;
+
+ if (mdData.size() % sizeOfBpfMapDef) {
+ ALOGE("createMaps failed due to improper sized maps section, %zu %% %zu != 0",
+ mdData.size(), sizeOfBpfMapDef);
+ return -1;
+ };
+
+ int mapCount = mdData.size() / sizeOfBpfMapDef;
+ md.resize(mapCount);
+ size_t trimmedSize = std::min(sizeOfBpfMapDef, sizeof(struct bpf_map_def));
+
+ const char* dataPtr = mdData.data();
+ for (auto& m : md) {
+ // First we zero initialize
+ memset(&m, 0, sizeof(m));
+ // Then we set non-zero defaults
+ m.bpfloader_max_ver = DEFAULT_BPFLOADER_MAX_VER; // v1.0
+ m.max_kver = 0xFFFFFFFFu; // matches KVER_INF from bpf_helpers.h
+ // Then we copy over the structure prefix from the ELF file.
+ memcpy(&m, dataPtr, trimmedSize);
+ // Move to next struct in the ELF file
+ dataPtr += sizeOfBpfMapDef;
+ }
+
+ ret = getSectionSymNames(elfFile, "maps", mapNames);
+ if (ret) return ret;
+
+ unsigned kvers = kernelVersion();
+
+ for (int i = 0; i < (int)mapNames.size(); i++) {
+ if (md[i].zero != 0) abort();
+
+ if (bpfloader_ver < md[i].bpfloader_min_ver) {
+ ALOGI("skipping map %s which requires bpfloader min ver 0x%05x", mapNames[i].c_str(),
+ md[i].bpfloader_min_ver);
+ mapFds.push_back(unique_fd());
+ continue;
+ }
+
+ if (bpfloader_ver >= md[i].bpfloader_max_ver) {
+ ALOGI("skipping map %s which requires bpfloader max ver 0x%05x", mapNames[i].c_str(),
+ md[i].bpfloader_max_ver);
+ mapFds.push_back(unique_fd());
+ continue;
+ }
+
+ if (kvers < md[i].min_kver) {
+ ALOGI("skipping map %s which requires kernel version 0x%x >= 0x%x",
+ mapNames[i].c_str(), kvers, md[i].min_kver);
+ mapFds.push_back(unique_fd());
+ continue;
+ }
+
+ if (kvers >= md[i].max_kver) {
+ ALOGI("skipping map %s which requires kernel version 0x%x < 0x%x",
+ mapNames[i].c_str(), kvers, md[i].max_kver);
+ mapFds.push_back(unique_fd());
+ continue;
+ }
+
+ if ((md[i].ignore_on_eng && isEng()) || (md[i].ignore_on_user && isUser()) ||
+ (md[i].ignore_on_userdebug && isUserdebug())) {
+ ALOGI("skipping map %s which is ignored on %s builds", mapNames[i].c_str(),
+ getBuildType().c_str());
+ mapFds.push_back(unique_fd());
+ continue;
+ }
+
+ if ((isArm() && isKernel32Bit() && md[i].ignore_on_arm32) ||
+ (isArm() && isKernel64Bit() && md[i].ignore_on_aarch64) ||
+ (isX86() && isKernel32Bit() && md[i].ignore_on_x86_32) ||
+ (isX86() && isKernel64Bit() && md[i].ignore_on_x86_64) ||
+ (isRiscV() && md[i].ignore_on_riscv64)) {
+ ALOGI("skipping map %s which is ignored on %s", mapNames[i].c_str(),
+ describeArch());
+ mapFds.push_back(unique_fd());
+ continue;
+ }
+
+ enum bpf_map_type type = md[i].type;
+ if (type == BPF_MAP_TYPE_DEVMAP && !isAtLeastKernelVersion(4, 14, 0)) {
+ // On Linux Kernels older than 4.14 this map type doesn't exist, but it can kind
+ // of be approximated: ARRAY has the same userspace api, though it is not usable
+ // by the same ebpf programs. However, that's okay because the bpf_redirect_map()
+ // helper doesn't exist on 4.9-T anyway (so the bpf program would fail to load,
+ // and thus needs to be tagged as 4.14+ either way), so there's nothing useful you
+ // could do with a DEVMAP anyway (that isn't already provided by an ARRAY)...
+ // Hence using an ARRAY instead of a DEVMAP simply makes life easier for userspace.
+ type = BPF_MAP_TYPE_ARRAY;
+ }
+ if (type == BPF_MAP_TYPE_DEVMAP_HASH && !isAtLeastKernelVersion(5, 4, 0)) {
+ // On Linux Kernels older than 5.4 this map type doesn't exist, but it can kind
+ // of be approximated: HASH has the same userspace visible api.
+ // However it cannot be used by ebpf programs in the same way.
+ // Since bpf_redirect_map() only requires 4.14, a program using a DEVMAP_HASH map
+ // would fail to load (due to trying to redirect to a HASH instead of DEVMAP_HASH).
+ // One must thus tag any BPF_MAP_TYPE_DEVMAP_HASH + bpf_redirect_map() using
+ // programs as being 5.4+...
+ type = BPF_MAP_TYPE_HASH;
+ }
+
+ // The .h file enforces that this is a power of two, and page size will
+ // also always be a power of two, so this logic is actually enough to
+ // force it to be a multiple of the page size, as required by the kernel.
+ unsigned int max_entries = md[i].max_entries;
+ if (type == BPF_MAP_TYPE_RINGBUF) {
+ if (max_entries < page_size) max_entries = page_size;
+ }
+
+ domain selinux_context = getDomainFromSelinuxContext(md[i].selinux_context);
+ if (specified(selinux_context)) {
+ ALOGI("map %s selinux_context [%-32s] -> %d -> '%s' (%s)", mapNames[i].c_str(),
+ md[i].selinux_context, static_cast<int>(selinux_context),
+ lookupSelinuxContext(selinux_context), lookupPinSubdir(selinux_context));
+ }
+
+ domain pin_subdir = getDomainFromPinSubdir(md[i].pin_subdir);
+ if (specified(pin_subdir)) {
+ ALOGI("map %s pin_subdir [%-32s] -> %d -> '%s'", mapNames[i].c_str(), md[i].pin_subdir,
+ static_cast<int>(pin_subdir), lookupPinSubdir(pin_subdir));
+ }
+
+ // Format of pin location is /sys/fs/bpf/<pin_subdir|prefix>map_<objName>_<mapName>
+ // except that maps shared across .o's have empty <objName>
+ // Note: <objName> refers to the extension-less basename of the .o file (without @ suffix).
+ string mapPinLoc = string(BPF_FS_PATH) + lookupPinSubdir(pin_subdir, prefix) + "map_" +
+ (md[i].shared ? "" : objName) + "_" + mapNames[i];
+ bool reuse = false;
+ unique_fd fd;
+ int saved_errno;
+
+ if (access(mapPinLoc.c_str(), F_OK) == 0) {
+ fd.reset(mapRetrieveRO(mapPinLoc.c_str()));
+ saved_errno = errno;
+ ALOGD("bpf_create_map reusing map %s, ret: %d", mapNames[i].c_str(), fd.get());
+ reuse = true;
+ } else {
+ union bpf_attr req = {
+ .map_type = type,
+ .key_size = md[i].key_size,
+ .value_size = md[i].value_size,
+ .max_entries = max_entries,
+ .map_flags = md[i].map_flags,
+ };
+ if (isAtLeastKernelVersion(4, 15, 0))
+ strlcpy(req.map_name, mapNames[i].c_str(), sizeof(req.map_name));
+ fd.reset(bpf(BPF_MAP_CREATE, req));
+ saved_errno = errno;
+ ALOGD("bpf_create_map name %s, ret: %d", mapNames[i].c_str(), fd.get());
+ }
+
+ if (!fd.ok()) return -saved_errno;
+
+ // When reusing a pinned map, we need to check the map type/sizes/etc match, but for
+ // safety (since reuse code path is rare) run these checks even if we just created it.
+ // We assume failure is due to pinned map mismatch, hence the 'NOT UNIQUE' return code.
+ if (!mapMatchesExpectations(fd, mapNames[i], md[i], type)) return -ENOTUNIQ;
+
+ if (!reuse) {
+ if (specified(selinux_context)) {
+ string createLoc = string(BPF_FS_PATH) + lookupPinSubdir(selinux_context) +
+ "tmp_map_" + objName + "_" + mapNames[i];
+ ret = bpfFdPin(fd, createLoc.c_str());
+ if (ret) {
+ int err = errno;
+ ALOGE("create %s -> %d [%d:%s]", createLoc.c_str(), ret, err, strerror(err));
+ return -err;
+ }
+ ret = renameat2(AT_FDCWD, createLoc.c_str(),
+ AT_FDCWD, mapPinLoc.c_str(), RENAME_NOREPLACE);
+ if (ret) {
+ int err = errno;
+ ALOGE("rename %s %s -> %d [%d:%s]", createLoc.c_str(), mapPinLoc.c_str(), ret,
+ err, strerror(err));
+ return -err;
+ }
+ } else {
+ ret = bpfFdPin(fd, mapPinLoc.c_str());
+ if (ret) {
+ int err = errno;
+ ALOGE("pin %s -> %d [%d:%s]", mapPinLoc.c_str(), ret, err, strerror(err));
+ return -err;
+ }
+ }
+ ret = chmod(mapPinLoc.c_str(), md[i].mode);
+ if (ret) {
+ int err = errno;
+ ALOGE("chmod(%s, 0%o) = %d [%d:%s]", mapPinLoc.c_str(), md[i].mode, ret, err,
+ strerror(err));
+ return -err;
+ }
+ ret = chown(mapPinLoc.c_str(), (uid_t)md[i].uid, (gid_t)md[i].gid);
+ if (ret) {
+ int err = errno;
+ ALOGE("chown(%s, %u, %u) = %d [%d:%s]", mapPinLoc.c_str(), md[i].uid, md[i].gid,
+ ret, err, strerror(err));
+ return -err;
+ }
+ }
+
+ int mapId = bpfGetFdMapId(fd);
+ if (mapId == -1) {
+ ALOGE("bpfGetFdMapId failed, ret: %d [%d]", mapId, errno);
+ } else {
+ ALOGI("map %s id %d", mapPinLoc.c_str(), mapId);
+ }
+
+ mapFds.push_back(std::move(fd));
+ }
+
+ return ret;
+}
+
+/* For debugging, dump all instructions */
+static void dumpIns(char* ins, int size) {
+ for (int row = 0; row < size / 8; row++) {
+ ALOGE("%d: ", row);
+ for (int j = 0; j < 8; j++) {
+ ALOGE("%3x ", ins[(row * 8) + j]);
+ }
+ ALOGE("\n");
+ }
+}
+
+/* For debugging, dump all code sections from cs list */
+static void dumpAllCs(vector<codeSection>& cs) {
+ for (int i = 0; i < (int)cs.size(); i++) {
+ ALOGE("Dumping cs %d, name %s", int(i), cs[i].name.c_str());
+ dumpIns((char*)cs[i].data.data(), cs[i].data.size());
+ ALOGE("-----------");
+ }
+}
+
+static void applyRelo(void* insnsPtr, Elf64_Addr offset, int fd) {
+ int insnIndex;
+ struct bpf_insn *insn, *insns;
+
+ insns = (struct bpf_insn*)(insnsPtr);
+
+ insnIndex = offset / sizeof(struct bpf_insn);
+ insn = &insns[insnIndex];
+
+ // Occasionally might be useful for relocation debugging, but pretty spammy
+ if (0) {
+ ALOGV("applying relo to instruction at byte offset: %llu, "
+ "insn offset %d, insn %llx",
+ (unsigned long long)offset, insnIndex, *(unsigned long long*)insn);
+ }
+
+ if (insn->code != (BPF_LD | BPF_IMM | BPF_DW)) {
+ ALOGE("Dumping all instructions till ins %d", insnIndex);
+ ALOGE("invalid relo for insn %d: code 0x%x", insnIndex, insn->code);
+ dumpIns((char*)insnsPtr, (insnIndex + 3) * 8);
+ return;
+ }
+
+ insn->imm = fd;
+ insn->src_reg = BPF_PSEUDO_MAP_FD;
+}
+
+static void applyMapRelo(ifstream& elfFile, vector<unique_fd> &mapFds, vector<codeSection>& cs) {
+ vector<string> mapNames;
+
+ int ret = getSectionSymNames(elfFile, "maps", mapNames);
+ if (ret) return;
+
+ for (int k = 0; k != (int)cs.size(); k++) {
+ Elf64_Rel* rel = (Elf64_Rel*)(cs[k].rel_data.data());
+ int n_rel = cs[k].rel_data.size() / sizeof(*rel);
+
+ for (int i = 0; i < n_rel; i++) {
+ int symIndex = ELF64_R_SYM(rel[i].r_info);
+ string symName;
+
+ ret = getSymNameByIdx(elfFile, symIndex, symName);
+ if (ret) return;
+
+ /* Find the map fd and apply relo */
+ for (int j = 0; j < (int)mapNames.size(); j++) {
+ if (!mapNames[j].compare(symName)) {
+ applyRelo(cs[k].data.data(), rel[i].r_offset, mapFds[j]);
+ break;
+ }
+ }
+ }
+ }
+}
+
+static int loadCodeSections(const char* elfPath, vector<codeSection>& cs, const string& license,
+ const char* prefix, const unsigned int bpfloader_ver) {
+ unsigned kvers = kernelVersion();
+
+ if (!kvers) {
+ ALOGE("unable to get kernel version");
+ return -EINVAL;
+ }
+
+ string objName = pathToObjName(string(elfPath));
+
+ for (int i = 0; i < (int)cs.size(); i++) {
+ unique_fd& fd = cs[i].prog_fd;
+ int ret;
+ string name = cs[i].name;
+
+ if (!cs[i].prog_def.has_value()) {
+ ALOGE("[%d] '%s' missing program definition! bad bpf.o build?", i, name.c_str());
+ return -EINVAL;
+ }
+
+ unsigned min_kver = cs[i].prog_def->min_kver;
+ unsigned max_kver = cs[i].prog_def->max_kver;
+ ALOGD("cs[%d].name:%s min_kver:%x .max_kver:%x (kvers:%x)", i, name.c_str(), min_kver,
+ max_kver, kvers);
+ if (kvers < min_kver) continue;
+ if (kvers >= max_kver) continue;
+
+ unsigned bpfMinVer = cs[i].prog_def->bpfloader_min_ver;
+ unsigned bpfMaxVer = cs[i].prog_def->bpfloader_max_ver;
+ domain selinux_context = getDomainFromSelinuxContext(cs[i].prog_def->selinux_context);
+ domain pin_subdir = getDomainFromPinSubdir(cs[i].prog_def->pin_subdir);
+
+ ALOGD("cs[%d].name:%s requires bpfloader version [0x%05x,0x%05x)", i, name.c_str(),
+ bpfMinVer, bpfMaxVer);
+ if (bpfloader_ver < bpfMinVer) continue;
+ if (bpfloader_ver >= bpfMaxVer) continue;
+
+ if ((cs[i].prog_def->ignore_on_eng && isEng()) ||
+ (cs[i].prog_def->ignore_on_user && isUser()) ||
+ (cs[i].prog_def->ignore_on_userdebug && isUserdebug())) {
+ ALOGD("cs[%d].name:%s is ignored on %s builds", i, name.c_str(),
+ getBuildType().c_str());
+ continue;
+ }
+
+ if ((isArm() && isKernel32Bit() && cs[i].prog_def->ignore_on_arm32) ||
+ (isArm() && isKernel64Bit() && cs[i].prog_def->ignore_on_aarch64) ||
+ (isX86() && isKernel32Bit() && cs[i].prog_def->ignore_on_x86_32) ||
+ (isX86() && isKernel64Bit() && cs[i].prog_def->ignore_on_x86_64) ||
+ (isRiscV() && cs[i].prog_def->ignore_on_riscv64)) {
+ ALOGD("cs[%d].name:%s is ignored on %s", i, name.c_str(), describeArch());
+ continue;
+ }
+
+ if (specified(selinux_context)) {
+ ALOGI("prog %s selinux_context [%-32s] -> %d -> '%s' (%s)", name.c_str(),
+ cs[i].prog_def->selinux_context, static_cast<int>(selinux_context),
+ lookupSelinuxContext(selinux_context), lookupPinSubdir(selinux_context));
+ }
+
+ if (specified(pin_subdir)) {
+ ALOGI("prog %s pin_subdir [%-32s] -> %d -> '%s'", name.c_str(),
+ cs[i].prog_def->pin_subdir, static_cast<int>(pin_subdir),
+ lookupPinSubdir(pin_subdir));
+ }
+
+ // strip any potential $foo suffix
+ // this can be used to provide duplicate programs
+ // conditionally loaded based on running kernel version
+ name = name.substr(0, name.find_last_of('$'));
+
+ bool reuse = false;
+ // Format of pin location is
+ // /sys/fs/bpf/<prefix>prog_<objName>_<progName>
+ string progPinLoc = string(BPF_FS_PATH) + lookupPinSubdir(pin_subdir, prefix) + "prog_" +
+ objName + '_' + string(name);
+ if (access(progPinLoc.c_str(), F_OK) == 0) {
+ fd.reset(retrieveProgram(progPinLoc.c_str()));
+ ALOGD("New bpf prog load reusing prog %s, ret: %d (%s)", progPinLoc.c_str(), fd.get(),
+ (!fd.ok() ? std::strerror(errno) : "no error"));
+ reuse = true;
+ } else {
+ vector<char> log_buf(BPF_LOAD_LOG_SZ, 0);
+
+ union bpf_attr req = {
+ .prog_type = cs[i].type,
+ .kern_version = kvers,
+ .license = ptr_to_u64(license.c_str()),
+ .insns = ptr_to_u64(cs[i].data.data()),
+ .insn_cnt = static_cast<__u32>(cs[i].data.size() / sizeof(struct bpf_insn)),
+ .log_level = 1,
+ .log_buf = ptr_to_u64(log_buf.data()),
+ .log_size = static_cast<__u32>(log_buf.size()),
+ .expected_attach_type = cs[i].attach_type,
+ };
+ if (isAtLeastKernelVersion(4, 15, 0))
+ strlcpy(req.prog_name, cs[i].name.c_str(), sizeof(req.prog_name));
+ fd.reset(bpf(BPF_PROG_LOAD, req));
+
+ ALOGD("BPF_PROG_LOAD call for %s (%s) returned fd: %d (%s)", elfPath,
+ cs[i].name.c_str(), fd.get(), (!fd.ok() ? std::strerror(errno) : "no error"));
+
+ if (!fd.ok()) {
+ vector<string> lines = android::base::Split(log_buf.data(), "\n");
+
+ ALOGW("BPF_PROG_LOAD - BEGIN log_buf contents:");
+ for (const auto& line : lines) ALOGW("%s", line.c_str());
+ ALOGW("BPF_PROG_LOAD - END log_buf contents.");
+
+ if (cs[i].prog_def->optional) {
+ ALOGW("failed program is marked optional - continuing...");
+ continue;
+ }
+ ALOGE("non-optional program failed to load.");
+ }
+ }
+
+ if (!fd.ok()) return fd.get();
+
+ if (!reuse) {
+ if (specified(selinux_context)) {
+ string createLoc = string(BPF_FS_PATH) + lookupPinSubdir(selinux_context) +
+ "tmp_prog_" + objName + '_' + string(name);
+ ret = bpfFdPin(fd, createLoc.c_str());
+ if (ret) {
+ int err = errno;
+ ALOGE("create %s -> %d [%d:%s]", createLoc.c_str(), ret, err, strerror(err));
+ return -err;
+ }
+ ret = renameat2(AT_FDCWD, createLoc.c_str(),
+ AT_FDCWD, progPinLoc.c_str(), RENAME_NOREPLACE);
+ if (ret) {
+ int err = errno;
+ ALOGE("rename %s %s -> %d [%d:%s]", createLoc.c_str(), progPinLoc.c_str(), ret,
+ err, strerror(err));
+ return -err;
+ }
+ } else {
+ ret = bpfFdPin(fd, progPinLoc.c_str());
+ if (ret) {
+ int err = errno;
+ ALOGE("create %s -> %d [%d:%s]", progPinLoc.c_str(), ret, err, strerror(err));
+ return -err;
+ }
+ }
+ if (chmod(progPinLoc.c_str(), 0440)) {
+ int err = errno;
+ ALOGE("chmod %s 0440 -> [%d:%s]", progPinLoc.c_str(), err, strerror(err));
+ return -err;
+ }
+ if (chown(progPinLoc.c_str(), (uid_t)cs[i].prog_def->uid,
+ (gid_t)cs[i].prog_def->gid)) {
+ int err = errno;
+ ALOGE("chown %s %d %d -> [%d:%s]", progPinLoc.c_str(), cs[i].prog_def->uid,
+ cs[i].prog_def->gid, err, strerror(err));
+ return -err;
+ }
+ }
+
+ int progId = bpfGetFdProgId(fd);
+ if (progId == -1) {
+ ALOGE("bpfGetFdProgId failed, ret: %d [%d]", progId, errno);
+ } else {
+ ALOGI("prog %s id %d", progPinLoc.c_str(), progId);
+ }
+ }
+
+ return 0;
+}
+
+int loadProg(const char* const elfPath, bool* const isCritical, const unsigned int bpfloader_ver,
+ const Location& location) {
+ vector<char> license;
+ vector<char> critical;
+ vector<codeSection> cs;
+ vector<unique_fd> mapFds;
+ int ret;
+
+ if (!isCritical) return -1;
+ *isCritical = false;
+
+ ifstream elfFile(elfPath, ios::in | ios::binary);
+ if (!elfFile.is_open()) return -1;
+
+ ret = readSectionByName("critical", elfFile, critical);
+ *isCritical = !ret;
+
+ ret = readSectionByName("license", elfFile, license);
+ if (ret) {
+ ALOGE("Couldn't find license in %s", elfPath);
+ return ret;
+ } else {
+ ALOGD("Loading %s%s ELF object %s with license %s",
+ *isCritical ? "critical for " : "optional", *isCritical ? (char*)critical.data() : "",
+ elfPath, (char*)license.data());
+ }
+
+ // the following default values are for bpfloader V0.0 format which does not include them
+ unsigned int bpfLoaderMinVer =
+ readSectionUint("bpfloader_min_ver", elfFile, DEFAULT_BPFLOADER_MIN_VER);
+ unsigned int bpfLoaderMaxVer =
+ readSectionUint("bpfloader_max_ver", elfFile, DEFAULT_BPFLOADER_MAX_VER);
+ unsigned int bpfLoaderMinRequiredVer =
+ readSectionUint("bpfloader_min_required_ver", elfFile, 0);
+ size_t sizeOfBpfMapDef =
+ readSectionUint("size_of_bpf_map_def", elfFile, DEFAULT_SIZEOF_BPF_MAP_DEF);
+ size_t sizeOfBpfProgDef =
+ readSectionUint("size_of_bpf_prog_def", elfFile, DEFAULT_SIZEOF_BPF_PROG_DEF);
+
+ // inclusive lower bound check
+ if (bpfloader_ver < bpfLoaderMinVer) {
+ ALOGI("BpfLoader version 0x%05x ignoring ELF object %s with min ver 0x%05x",
+ bpfloader_ver, elfPath, bpfLoaderMinVer);
+ return 0;
+ }
+
+ // exclusive upper bound check
+ if (bpfloader_ver >= bpfLoaderMaxVer) {
+ ALOGI("BpfLoader version 0x%05x ignoring ELF object %s with max ver 0x%05x",
+ bpfloader_ver, elfPath, bpfLoaderMaxVer);
+ return 0;
+ }
+
+ if (bpfloader_ver < bpfLoaderMinRequiredVer) {
+ ALOGI("BpfLoader version 0x%05x failing due to ELF object %s with required min ver 0x%05x",
+ bpfloader_ver, elfPath, bpfLoaderMinRequiredVer);
+ return -1;
+ }
+
+ ALOGI("BpfLoader version 0x%05x processing ELF object %s with ver [0x%05x,0x%05x)",
+ bpfloader_ver, elfPath, bpfLoaderMinVer, bpfLoaderMaxVer);
+
+ if (sizeOfBpfMapDef < DEFAULT_SIZEOF_BPF_MAP_DEF) {
+ ALOGE("sizeof(bpf_map_def) of %zu is too small (< %d)", sizeOfBpfMapDef,
+ DEFAULT_SIZEOF_BPF_MAP_DEF);
+ return -1;
+ }
+
+ if (sizeOfBpfProgDef < DEFAULT_SIZEOF_BPF_PROG_DEF) {
+ ALOGE("sizeof(bpf_prog_def) of %zu is too small (< %d)", sizeOfBpfProgDef,
+ DEFAULT_SIZEOF_BPF_PROG_DEF);
+ return -1;
+ }
+
+ ret = readCodeSections(elfFile, cs, sizeOfBpfProgDef);
+ if (ret) {
+ ALOGE("Couldn't read all code sections in %s", elfPath);
+ return ret;
+ }
+
+ /* Just for future debugging */
+ if (0) dumpAllCs(cs);
+
+ ret = createMaps(elfPath, elfFile, mapFds, location.prefix, sizeOfBpfMapDef, bpfloader_ver);
+ if (ret) {
+ ALOGE("Failed to create maps: (ret=%d) in %s", ret, elfPath);
+ return ret;
+ }
+
+ for (int i = 0; i < (int)mapFds.size(); i++)
+ ALOGV("map_fd found at %d is %d in %s", i, mapFds[i].get(), elfPath);
+
+ applyMapRelo(elfFile, mapFds, cs);
+
+ ret = loadCodeSections(elfPath, cs, string(license.data()), location.prefix, bpfloader_ver);
+ if (ret) ALOGE("Failed to load programs, loadCodeSections ret=%d", ret);
+
+ return ret;
+}
static bool exists(const char* const path) {
int v = access(path, F_OK);
@@ -248,6 +1417,22 @@
return tv;
}
+static bool isWear() {
+ static string wearSdkStr = base::GetProperty("ro.cw_build.wear_sdk.version", "");
+ static int wearSdkInt = base::GetIntProperty("ro.cw_build.wear_sdk.version", 0);
+ static string buildChars = base::GetProperty("ro.build.characteristics", "");
+ static vector<string> v = base::Tokenize(buildChars, ",");
+ static bool watch = (std::find(v.begin(), v.end(), "watch") != v.end());
+ static bool wear = (wearSdkInt > 0) || watch;
+ static bool logged = false;
+ if (!logged) {
+ logged = true;
+ ALOGI("isWear(ro.cw_build.wear_sdk.version=%d[%s] ro.build.characteristics='%s'): %s",
+ wearSdkInt, wearSdkStr.c_str(), buildChars.c_str(), wear ? "true" : "false");
+ }
+ return wear;
+}
+
static int doLoad(char** argv, char * const envp[]) {
const bool runningAsRoot = !getuid(); // true iff U QPR3 or V+
@@ -402,7 +1587,8 @@
* and 32-bit userspace on 64-bit kernel bpf ringbuffer compatibility is broken.
*/
ALOGE("64-bit userspace required on 6.2+ kernels.");
- if (!isTV()) return 1;
+ // Stuff won't work reliably, but exempt TVs & Arm Wear devices
+ if (!isTV() && !(isWear() && isArm())) return 1;
}
// Ensure we can determine the Android build type.
diff --git a/netbpfload/loader.cpp b/netbpfload/loader.cpp
deleted file mode 100644
index bb7c56f..0000000
--- a/netbpfload/loader.cpp
+++ /dev/null
@@ -1,1203 +0,0 @@
-/*
- * Copyright (C) 2018-2023 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.
- */
-
-#define LOG_TAG "NetBpfLoad"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <linux/bpf.h>
-#include <linux/elf.h>
-#include <log/log.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sysexits.h>
-#include <sys/stat.h>
-#include <sys/utsname.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include "BpfSyscallWrappers.h"
-#include "bpf/BpfUtils.h"
-#include "bpf/bpf_map_def.h"
-#include "loader.h"
-
-#include <cstdlib>
-#include <fstream>
-#include <iostream>
-#include <optional>
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-#include <android-base/cmsg.h>
-#include <android-base/file.h>
-#include <android-base/properties.h>
-#include <android-base/strings.h>
-#include <android-base/unique_fd.h>
-
-#define BPF_FS_PATH "/sys/fs/bpf/"
-
-// Size of the BPF log buffer for verifier logging
-#define BPF_LOAD_LOG_SZ 0xfffff
-
-// Unspecified attach type is 0 which is BPF_CGROUP_INET_INGRESS.
-#define BPF_ATTACH_TYPE_UNSPEC BPF_CGROUP_INET_INGRESS
-
-using android::base::StartsWith;
-using android::base::unique_fd;
-using std::ifstream;
-using std::ios;
-using std::optional;
-using std::string;
-using std::vector;
-
-namespace android {
-namespace bpf {
-
-const std::string& getBuildType() {
- static std::string t = android::base::GetProperty("ro.build.type", "unknown");
- return t;
-}
-
-static unsigned int page_size = static_cast<unsigned int>(getpagesize());
-
-constexpr const char* lookupSelinuxContext(const domain d, const char* const unspecified = "") {
- switch (d) {
- case domain::unspecified: return unspecified;
- case domain::tethering: return "fs_bpf_tethering";
- case domain::net_private: return "fs_bpf_net_private";
- case domain::net_shared: return "fs_bpf_net_shared";
- case domain::netd_readonly: return "fs_bpf_netd_readonly";
- case domain::netd_shared: return "fs_bpf_netd_shared";
- default: return "(unrecognized)";
- }
-}
-
-domain getDomainFromSelinuxContext(const char s[BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE]) {
- for (domain d : AllDomains) {
- // Not sure how to enforce this at compile time, so abort() bpfloader at boot instead
- if (strlen(lookupSelinuxContext(d)) >= BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE) abort();
- if (!strncmp(s, lookupSelinuxContext(d), BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE)) return d;
- }
- ALOGW("ignoring unrecognized selinux_context '%-32s'", s);
- // We should return 'unrecognized' here, however: returning unspecified will
- // result in the system simply using the default context, which in turn
- // will allow future expansion by adding more restrictive selinux types.
- // Older bpfloader will simply ignore that, and use the less restrictive default.
- // This does mean you CANNOT later add a *less* restrictive type than the default.
- //
- // Note: we cannot just abort() here as this might be a mainline module shipped optional update
- return domain::unspecified;
-}
-
-constexpr const char* lookupPinSubdir(const domain d, const char* const unspecified = "") {
- switch (d) {
- case domain::unspecified: return unspecified;
- case domain::tethering: return "tethering/";
- case domain::net_private: return "net_private/";
- case domain::net_shared: return "net_shared/";
- case domain::netd_readonly: return "netd_readonly/";
- case domain::netd_shared: return "netd_shared/";
- default: return "(unrecognized)";
- }
-};
-
-domain getDomainFromPinSubdir(const char s[BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE]) {
- for (domain d : AllDomains) {
- // Not sure how to enforce this at compile time, so abort() bpfloader at boot instead
- if (strlen(lookupPinSubdir(d)) >= BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE) abort();
- if (!strncmp(s, lookupPinSubdir(d), BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE)) return d;
- }
- ALOGE("unrecognized pin_subdir '%-32s'", s);
- // pin_subdir affects the object's full pathname,
- // and thus using the default would change the location and thus our code's ability to find it,
- // hence this seems worth treating as a true error condition.
- //
- // Note: we cannot just abort() here as this might be a mainline module shipped optional update
- // However, our callers will treat this as an error, and stop loading the specific .o,
- // which will fail bpfloader if the .o is marked critical.
- return domain::unrecognized;
-}
-
-static string pathToObjName(const string& path) {
- // extract everything after the final slash, ie. this is the filename 'foo@1.o' or 'bar.o'
- string filename = android::base::Split(path, "/").back();
- // strip off everything from the final period onwards (strip '.o' suffix), ie. 'foo@1' or 'bar'
- string name = filename.substr(0, filename.find_last_of('.'));
- // strip any potential @1 suffix, this will leave us with just 'foo' or 'bar'
- // this can be used to provide duplicate programs (mux based on the bpfloader version)
- return name.substr(0, name.find_last_of('@'));
-}
-
-typedef struct {
- const char* name;
- enum bpf_prog_type type;
- enum bpf_attach_type expected_attach_type;
-} sectionType;
-
-/*
- * Map section name prefixes to program types, the section name will be:
- * SECTION(<prefix>/<name-of-program>)
- * For example:
- * SECTION("tracepoint/sched_switch_func") where sched_switch_funcs
- * is the name of the program, and tracepoint is the type.
- *
- * However, be aware that you should not be directly using the SECTION() macro.
- * Instead use the DEFINE_(BPF|XDP)_(PROG|MAP)... & LICENSE/CRITICAL macros.
- *
- * Programs shipped inside the tethering apex should be limited to networking stuff,
- * as KPROBE, PERF_EVENT, TRACEPOINT are dangerous to use from mainline updatable code,
- * since they are less stable abi/api and may conflict with platform uses of bpf.
- */
-sectionType sectionNameTypes[] = {
- {"bind4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_BIND},
- {"bind6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_BIND},
- {"cgroupskb/", BPF_PROG_TYPE_CGROUP_SKB, BPF_ATTACH_TYPE_UNSPEC},
- {"cgroupsock/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_ATTACH_TYPE_UNSPEC},
- {"cgroupsockcreate/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET_SOCK_CREATE},
- {"cgroupsockrelease/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET_SOCK_RELEASE},
- {"connect4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_CONNECT},
- {"connect6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_CONNECT},
- {"egress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_EGRESS},
- {"getsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_GETSOCKOPT},
- {"ingress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_INGRESS},
- {"lwt_in/", BPF_PROG_TYPE_LWT_IN, BPF_ATTACH_TYPE_UNSPEC},
- {"lwt_out/", BPF_PROG_TYPE_LWT_OUT, BPF_ATTACH_TYPE_UNSPEC},
- {"lwt_seg6local/", BPF_PROG_TYPE_LWT_SEG6LOCAL, BPF_ATTACH_TYPE_UNSPEC},
- {"lwt_xmit/", BPF_PROG_TYPE_LWT_XMIT, BPF_ATTACH_TYPE_UNSPEC},
- {"postbind4/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET4_POST_BIND},
- {"postbind6/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET6_POST_BIND},
- {"recvmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_RECVMSG},
- {"recvmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_RECVMSG},
- {"schedact/", BPF_PROG_TYPE_SCHED_ACT, BPF_ATTACH_TYPE_UNSPEC},
- {"schedcls/", BPF_PROG_TYPE_SCHED_CLS, BPF_ATTACH_TYPE_UNSPEC},
- {"sendmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_SENDMSG},
- {"sendmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_SENDMSG},
- {"setsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT},
- {"skfilter/", BPF_PROG_TYPE_SOCKET_FILTER, BPF_ATTACH_TYPE_UNSPEC},
- {"sockops/", BPF_PROG_TYPE_SOCK_OPS, BPF_CGROUP_SOCK_OPS},
- {"sysctl", BPF_PROG_TYPE_CGROUP_SYSCTL, BPF_CGROUP_SYSCTL},
- {"xdp/", BPF_PROG_TYPE_XDP, BPF_ATTACH_TYPE_UNSPEC},
-};
-
-typedef struct {
- enum bpf_prog_type type;
- enum bpf_attach_type expected_attach_type;
- string name;
- vector<char> data;
- vector<char> rel_data;
- optional<struct bpf_prog_def> prog_def;
-
- unique_fd prog_fd; /* fd after loading */
-} codeSection;
-
-static int readElfHeader(ifstream& elfFile, Elf64_Ehdr* eh) {
- elfFile.seekg(0);
- if (elfFile.fail()) return -1;
-
- if (!elfFile.read((char*)eh, sizeof(*eh))) return -1;
-
- return 0;
-}
-
-/* Reads all section header tables into an Shdr array */
-static int readSectionHeadersAll(ifstream& elfFile, vector<Elf64_Shdr>& shTable) {
- Elf64_Ehdr eh;
- int ret = 0;
-
- ret = readElfHeader(elfFile, &eh);
- if (ret) return ret;
-
- elfFile.seekg(eh.e_shoff);
- if (elfFile.fail()) return -1;
-
- /* Read shdr table entries */
- shTable.resize(eh.e_shnum);
-
- if (!elfFile.read((char*)shTable.data(), (eh.e_shnum * eh.e_shentsize))) return -ENOMEM;
-
- return 0;
-}
-
-/* Read a section by its index - for ex to get sec hdr strtab blob */
-static int readSectionByIdx(ifstream& elfFile, int id, vector<char>& sec) {
- vector<Elf64_Shdr> shTable;
- int ret = readSectionHeadersAll(elfFile, shTable);
- if (ret) return ret;
-
- elfFile.seekg(shTable[id].sh_offset);
- if (elfFile.fail()) return -1;
-
- sec.resize(shTable[id].sh_size);
- if (!elfFile.read(sec.data(), shTable[id].sh_size)) return -1;
-
- return 0;
-}
-
-/* Read whole section header string table */
-static int readSectionHeaderStrtab(ifstream& elfFile, vector<char>& strtab) {
- Elf64_Ehdr eh;
- int ret = readElfHeader(elfFile, &eh);
- if (ret) return ret;
-
- ret = readSectionByIdx(elfFile, eh.e_shstrndx, strtab);
- if (ret) return ret;
-
- return 0;
-}
-
-/* Get name from offset in strtab */
-static int getSymName(ifstream& elfFile, int nameOff, string& name) {
- int ret;
- vector<char> secStrTab;
-
- ret = readSectionHeaderStrtab(elfFile, secStrTab);
- if (ret) return ret;
-
- if (nameOff >= (int)secStrTab.size()) return -1;
-
- name = string((char*)secStrTab.data() + nameOff);
- return 0;
-}
-
-/* Reads a full section by name - example to get the GPL license */
-static int readSectionByName(const char* name, ifstream& elfFile, vector<char>& data) {
- vector<char> secStrTab;
- vector<Elf64_Shdr> shTable;
- int ret;
-
- ret = readSectionHeadersAll(elfFile, shTable);
- if (ret) return ret;
-
- ret = readSectionHeaderStrtab(elfFile, secStrTab);
- if (ret) return ret;
-
- for (int i = 0; i < (int)shTable.size(); i++) {
- char* secname = secStrTab.data() + shTable[i].sh_name;
- if (!secname) continue;
-
- if (!strcmp(secname, name)) {
- vector<char> dataTmp;
- dataTmp.resize(shTable[i].sh_size);
-
- elfFile.seekg(shTable[i].sh_offset);
- if (elfFile.fail()) return -1;
-
- if (!elfFile.read((char*)dataTmp.data(), shTable[i].sh_size)) return -1;
-
- data = dataTmp;
- return 0;
- }
- }
- return -2;
-}
-
-unsigned int readSectionUint(const char* name, ifstream& elfFile, unsigned int defVal) {
- vector<char> theBytes;
- int ret = readSectionByName(name, elfFile, theBytes);
- if (ret) {
- ALOGD("Couldn't find section %s (defaulting to %u [0x%x]).", name, defVal, defVal);
- return defVal;
- } else if (theBytes.size() < sizeof(unsigned int)) {
- ALOGE("Section %s too short (defaulting to %u [0x%x]).", name, defVal, defVal);
- return defVal;
- } else {
- // decode first 4 bytes as LE32 uint, there will likely be more bytes due to alignment.
- unsigned int value = static_cast<unsigned char>(theBytes[3]);
- value <<= 8;
- value += static_cast<unsigned char>(theBytes[2]);
- value <<= 8;
- value += static_cast<unsigned char>(theBytes[1]);
- value <<= 8;
- value += static_cast<unsigned char>(theBytes[0]);
- ALOGI("Section %s value is %u [0x%x]", name, value, value);
- return value;
- }
-}
-
-static int readSectionByType(ifstream& elfFile, int type, vector<char>& data) {
- int ret;
- vector<Elf64_Shdr> shTable;
-
- ret = readSectionHeadersAll(elfFile, shTable);
- if (ret) return ret;
-
- for (int i = 0; i < (int)shTable.size(); i++) {
- if ((int)shTable[i].sh_type != type) continue;
-
- vector<char> dataTmp;
- dataTmp.resize(shTable[i].sh_size);
-
- elfFile.seekg(shTable[i].sh_offset);
- if (elfFile.fail()) return -1;
-
- if (!elfFile.read((char*)dataTmp.data(), shTable[i].sh_size)) return -1;
-
- data = dataTmp;
- return 0;
- }
- return -2;
-}
-
-static bool symCompare(Elf64_Sym a, Elf64_Sym b) {
- return (a.st_value < b.st_value);
-}
-
-static int readSymTab(ifstream& elfFile, int sort, vector<Elf64_Sym>& data) {
- int ret, numElems;
- Elf64_Sym* buf;
- vector<char> secData;
-
- ret = readSectionByType(elfFile, SHT_SYMTAB, secData);
- if (ret) return ret;
-
- buf = (Elf64_Sym*)secData.data();
- numElems = (secData.size() / sizeof(Elf64_Sym));
- data.assign(buf, buf + numElems);
-
- if (sort) std::sort(data.begin(), data.end(), symCompare);
- return 0;
-}
-
-static enum bpf_prog_type getSectionType(string& name) {
- for (auto& snt : sectionNameTypes)
- if (StartsWith(name, snt.name)) return snt.type;
-
- return BPF_PROG_TYPE_UNSPEC;
-}
-
-static enum bpf_attach_type getExpectedAttachType(string& name) {
- for (auto& snt : sectionNameTypes)
- if (StartsWith(name, snt.name)) return snt.expected_attach_type;
- return BPF_ATTACH_TYPE_UNSPEC;
-}
-
-/*
-static string getSectionName(enum bpf_prog_type type)
-{
- for (auto& snt : sectionNameTypes)
- if (snt.type == type)
- return string(snt.name);
-
- return "UNKNOWN SECTION NAME " + std::to_string(type);
-}
-*/
-
-static int readProgDefs(ifstream& elfFile, vector<struct bpf_prog_def>& pd,
- size_t sizeOfBpfProgDef) {
- vector<char> pdData;
- int ret = readSectionByName("progs", elfFile, pdData);
- if (ret) return ret;
-
- if (pdData.size() % sizeOfBpfProgDef) {
- ALOGE("readProgDefs failed due to improper sized progs section, %zu %% %zu != 0",
- pdData.size(), sizeOfBpfProgDef);
- return -1;
- };
-
- int progCount = pdData.size() / sizeOfBpfProgDef;
- pd.resize(progCount);
- size_t trimmedSize = std::min(sizeOfBpfProgDef, sizeof(struct bpf_prog_def));
-
- const char* dataPtr = pdData.data();
- for (auto& p : pd) {
- // First we zero initialize
- memset(&p, 0, sizeof(p));
- // Then we set non-zero defaults
- p.bpfloader_max_ver = DEFAULT_BPFLOADER_MAX_VER; // v1.0
- // Then we copy over the structure prefix from the ELF file.
- memcpy(&p, dataPtr, trimmedSize);
- // Move to next struct in the ELF file
- dataPtr += sizeOfBpfProgDef;
- }
- return 0;
-}
-
-static int getSectionSymNames(ifstream& elfFile, const string& sectionName, vector<string>& names,
- optional<unsigned> symbolType = std::nullopt) {
- int ret;
- string name;
- vector<Elf64_Sym> symtab;
- vector<Elf64_Shdr> shTable;
-
- ret = readSymTab(elfFile, 1 /* sort */, symtab);
- if (ret) return ret;
-
- /* Get index of section */
- ret = readSectionHeadersAll(elfFile, shTable);
- if (ret) return ret;
-
- int sec_idx = -1;
- for (int i = 0; i < (int)shTable.size(); i++) {
- ret = getSymName(elfFile, shTable[i].sh_name, name);
- if (ret) return ret;
-
- if (!name.compare(sectionName)) {
- sec_idx = i;
- break;
- }
- }
-
- /* No section found with matching name*/
- if (sec_idx == -1) {
- ALOGW("No %s section could be found in elf object", sectionName.c_str());
- return -1;
- }
-
- for (int i = 0; i < (int)symtab.size(); i++) {
- if (symbolType.has_value() && ELF_ST_TYPE(symtab[i].st_info) != symbolType) continue;
-
- if (symtab[i].st_shndx == sec_idx) {
- string s;
- ret = getSymName(elfFile, symtab[i].st_name, s);
- if (ret) return ret;
- names.push_back(s);
- }
- }
-
- return 0;
-}
-
-/* Read a section by its index - for ex to get sec hdr strtab blob */
-static int readCodeSections(ifstream& elfFile, vector<codeSection>& cs, size_t sizeOfBpfProgDef) {
- vector<Elf64_Shdr> shTable;
- int entries, ret = 0;
-
- ret = readSectionHeadersAll(elfFile, shTable);
- if (ret) return ret;
- entries = shTable.size();
-
- vector<struct bpf_prog_def> pd;
- ret = readProgDefs(elfFile, pd, sizeOfBpfProgDef);
- if (ret) return ret;
- vector<string> progDefNames;
- ret = getSectionSymNames(elfFile, "progs", progDefNames);
- if (!pd.empty() && ret) return ret;
-
- for (int i = 0; i < entries; i++) {
- string name;
- codeSection cs_temp;
- cs_temp.type = BPF_PROG_TYPE_UNSPEC;
-
- ret = getSymName(elfFile, shTable[i].sh_name, name);
- if (ret) return ret;
-
- enum bpf_prog_type ptype = getSectionType(name);
-
- if (ptype == BPF_PROG_TYPE_UNSPEC) continue;
-
- // This must be done before '/' is replaced with '_'.
- cs_temp.expected_attach_type = getExpectedAttachType(name);
-
- string oldName = name;
-
- // convert all slashes to underscores
- std::replace(name.begin(), name.end(), '/', '_');
-
- cs_temp.type = ptype;
- cs_temp.name = name;
-
- ret = readSectionByIdx(elfFile, i, cs_temp.data);
- if (ret) return ret;
- ALOGV("Loaded code section %d (%s)", i, name.c_str());
-
- vector<string> csSymNames;
- ret = getSectionSymNames(elfFile, oldName, csSymNames, STT_FUNC);
- if (ret || !csSymNames.size()) return ret;
- for (size_t i = 0; i < progDefNames.size(); ++i) {
- if (!progDefNames[i].compare(csSymNames[0] + "_def")) {
- cs_temp.prog_def = pd[i];
- break;
- }
- }
-
- /* Check for rel section */
- if (cs_temp.data.size() > 0 && i < entries) {
- ret = getSymName(elfFile, shTable[i + 1].sh_name, name);
- if (ret) return ret;
-
- if (name == (".rel" + oldName)) {
- ret = readSectionByIdx(elfFile, i + 1, cs_temp.rel_data);
- if (ret) return ret;
- ALOGV("Loaded relo section %d (%s)", i, name.c_str());
- }
- }
-
- if (cs_temp.data.size() > 0) {
- cs.push_back(std::move(cs_temp));
- ALOGV("Adding section %d to cs list", i);
- }
- }
- return 0;
-}
-
-static int getSymNameByIdx(ifstream& elfFile, int index, string& name) {
- vector<Elf64_Sym> symtab;
- int ret = 0;
-
- ret = readSymTab(elfFile, 0 /* !sort */, symtab);
- if (ret) return ret;
-
- if (index >= (int)symtab.size()) return -1;
-
- return getSymName(elfFile, symtab[index].st_name, name);
-}
-
-static bool mapMatchesExpectations(const unique_fd& fd, const string& mapName,
- const struct bpf_map_def& mapDef, const enum bpf_map_type type) {
- // bpfGetFd... family of functions require at minimum a 4.14 kernel,
- // so on 4.9-T kernels just pretend the map matches our expectations.
- // Additionally we'll get almost equivalent test coverage on newer devices/kernels.
- // This is because the primary failure mode we're trying to detect here
- // is either a source code misconfiguration (which is likely kernel independent)
- // or a newly introduced kernel feature/bug (which is unlikely to get backported to 4.9).
- if (!isAtLeastKernelVersion(4, 14, 0)) return true;
-
- // Assuming fd is a valid Bpf Map file descriptor then
- // all the following should always succeed on a 4.14+ kernel.
- // If they somehow do fail, they'll return -1 (and set errno),
- // which should then cause (among others) a key_size mismatch.
- int fd_type = bpfGetFdMapType(fd);
- int fd_key_size = bpfGetFdKeySize(fd);
- int fd_value_size = bpfGetFdValueSize(fd);
- int fd_max_entries = bpfGetFdMaxEntries(fd);
- int fd_map_flags = bpfGetFdMapFlags(fd);
-
- // DEVMAPs are readonly from the bpf program side's point of view, as such
- // the kernel in kernel/bpf/devmap.c dev_map_init_map() will set the flag
- int desired_map_flags = (int)mapDef.map_flags;
- if (type == BPF_MAP_TYPE_DEVMAP || type == BPF_MAP_TYPE_DEVMAP_HASH)
- desired_map_flags |= BPF_F_RDONLY_PROG;
-
- // The .h file enforces that this is a power of two, and page size will
- // also always be a power of two, so this logic is actually enough to
- // force it to be a multiple of the page size, as required by the kernel.
- unsigned int desired_max_entries = mapDef.max_entries;
- if (type == BPF_MAP_TYPE_RINGBUF) {
- if (desired_max_entries < page_size) desired_max_entries = page_size;
- }
-
- // The following checks should *never* trigger, if one of them somehow does,
- // it probably means a bpf .o file has been changed/replaced at runtime
- // and bpfloader was manually rerun (normally it should only run *once*
- // early during the boot process).
- // Another possibility is that something is misconfigured in the code:
- // most likely a shared map is declared twice differently.
- // But such a change should never be checked into the source tree...
- if ((fd_type == type) &&
- (fd_key_size == (int)mapDef.key_size) &&
- (fd_value_size == (int)mapDef.value_size) &&
- (fd_max_entries == (int)desired_max_entries) &&
- (fd_map_flags == desired_map_flags)) {
- return true;
- }
-
- ALOGE("bpf map name %s mismatch: desired/found: "
- "type:%d/%d key:%u/%d value:%u/%d entries:%u/%d flags:%u/%d",
- mapName.c_str(), type, fd_type, mapDef.key_size, fd_key_size, mapDef.value_size,
- fd_value_size, mapDef.max_entries, fd_max_entries, desired_map_flags, fd_map_flags);
- return false;
-}
-
-static int createMaps(const char* elfPath, ifstream& elfFile, vector<unique_fd>& mapFds,
- const char* prefix, const size_t sizeOfBpfMapDef,
- const unsigned int bpfloader_ver) {
- int ret;
- vector<char> mdData;
- vector<struct bpf_map_def> md;
- vector<string> mapNames;
- string objName = pathToObjName(string(elfPath));
-
- ret = readSectionByName("maps", elfFile, mdData);
- if (ret == -2) return 0; // no maps to read
- if (ret) return ret;
-
- if (mdData.size() % sizeOfBpfMapDef) {
- ALOGE("createMaps failed due to improper sized maps section, %zu %% %zu != 0",
- mdData.size(), sizeOfBpfMapDef);
- return -1;
- };
-
- int mapCount = mdData.size() / sizeOfBpfMapDef;
- md.resize(mapCount);
- size_t trimmedSize = std::min(sizeOfBpfMapDef, sizeof(struct bpf_map_def));
-
- const char* dataPtr = mdData.data();
- for (auto& m : md) {
- // First we zero initialize
- memset(&m, 0, sizeof(m));
- // Then we set non-zero defaults
- m.bpfloader_max_ver = DEFAULT_BPFLOADER_MAX_VER; // v1.0
- m.max_kver = 0xFFFFFFFFu; // matches KVER_INF from bpf_helpers.h
- // Then we copy over the structure prefix from the ELF file.
- memcpy(&m, dataPtr, trimmedSize);
- // Move to next struct in the ELF file
- dataPtr += sizeOfBpfMapDef;
- }
-
- ret = getSectionSymNames(elfFile, "maps", mapNames);
- if (ret) return ret;
-
- unsigned kvers = kernelVersion();
-
- for (int i = 0; i < (int)mapNames.size(); i++) {
- if (md[i].zero != 0) abort();
-
- if (bpfloader_ver < md[i].bpfloader_min_ver) {
- ALOGI("skipping map %s which requires bpfloader min ver 0x%05x", mapNames[i].c_str(),
- md[i].bpfloader_min_ver);
- mapFds.push_back(unique_fd());
- continue;
- }
-
- if (bpfloader_ver >= md[i].bpfloader_max_ver) {
- ALOGI("skipping map %s which requires bpfloader max ver 0x%05x", mapNames[i].c_str(),
- md[i].bpfloader_max_ver);
- mapFds.push_back(unique_fd());
- continue;
- }
-
- if (kvers < md[i].min_kver) {
- ALOGI("skipping map %s which requires kernel version 0x%x >= 0x%x",
- mapNames[i].c_str(), kvers, md[i].min_kver);
- mapFds.push_back(unique_fd());
- continue;
- }
-
- if (kvers >= md[i].max_kver) {
- ALOGI("skipping map %s which requires kernel version 0x%x < 0x%x",
- mapNames[i].c_str(), kvers, md[i].max_kver);
- mapFds.push_back(unique_fd());
- continue;
- }
-
- if ((md[i].ignore_on_eng && isEng()) || (md[i].ignore_on_user && isUser()) ||
- (md[i].ignore_on_userdebug && isUserdebug())) {
- ALOGI("skipping map %s which is ignored on %s builds", mapNames[i].c_str(),
- getBuildType().c_str());
- mapFds.push_back(unique_fd());
- continue;
- }
-
- if ((isArm() && isKernel32Bit() && md[i].ignore_on_arm32) ||
- (isArm() && isKernel64Bit() && md[i].ignore_on_aarch64) ||
- (isX86() && isKernel32Bit() && md[i].ignore_on_x86_32) ||
- (isX86() && isKernel64Bit() && md[i].ignore_on_x86_64) ||
- (isRiscV() && md[i].ignore_on_riscv64)) {
- ALOGI("skipping map %s which is ignored on %s", mapNames[i].c_str(),
- describeArch());
- mapFds.push_back(unique_fd());
- continue;
- }
-
- enum bpf_map_type type = md[i].type;
- if (type == BPF_MAP_TYPE_DEVMAP && !isAtLeastKernelVersion(4, 14, 0)) {
- // On Linux Kernels older than 4.14 this map type doesn't exist, but it can kind
- // of be approximated: ARRAY has the same userspace api, though it is not usable
- // by the same ebpf programs. However, that's okay because the bpf_redirect_map()
- // helper doesn't exist on 4.9-T anyway (so the bpf program would fail to load,
- // and thus needs to be tagged as 4.14+ either way), so there's nothing useful you
- // could do with a DEVMAP anyway (that isn't already provided by an ARRAY)...
- // Hence using an ARRAY instead of a DEVMAP simply makes life easier for userspace.
- type = BPF_MAP_TYPE_ARRAY;
- }
- if (type == BPF_MAP_TYPE_DEVMAP_HASH && !isAtLeastKernelVersion(5, 4, 0)) {
- // On Linux Kernels older than 5.4 this map type doesn't exist, but it can kind
- // of be approximated: HASH has the same userspace visible api.
- // However it cannot be used by ebpf programs in the same way.
- // Since bpf_redirect_map() only requires 4.14, a program using a DEVMAP_HASH map
- // would fail to load (due to trying to redirect to a HASH instead of DEVMAP_HASH).
- // One must thus tag any BPF_MAP_TYPE_DEVMAP_HASH + bpf_redirect_map() using
- // programs as being 5.4+...
- type = BPF_MAP_TYPE_HASH;
- }
-
- // The .h file enforces that this is a power of two, and page size will
- // also always be a power of two, so this logic is actually enough to
- // force it to be a multiple of the page size, as required by the kernel.
- unsigned int max_entries = md[i].max_entries;
- if (type == BPF_MAP_TYPE_RINGBUF) {
- if (max_entries < page_size) max_entries = page_size;
- }
-
- domain selinux_context = getDomainFromSelinuxContext(md[i].selinux_context);
- if (specified(selinux_context)) {
- ALOGI("map %s selinux_context [%-32s] -> %d -> '%s' (%s)", mapNames[i].c_str(),
- md[i].selinux_context, static_cast<int>(selinux_context),
- lookupSelinuxContext(selinux_context), lookupPinSubdir(selinux_context));
- }
-
- domain pin_subdir = getDomainFromPinSubdir(md[i].pin_subdir);
- if (unrecognized(pin_subdir)) return -ENOTDIR;
- if (specified(pin_subdir)) {
- ALOGI("map %s pin_subdir [%-32s] -> %d -> '%s'", mapNames[i].c_str(), md[i].pin_subdir,
- static_cast<int>(pin_subdir), lookupPinSubdir(pin_subdir));
- }
-
- // Format of pin location is /sys/fs/bpf/<pin_subdir|prefix>map_<objName>_<mapName>
- // except that maps shared across .o's have empty <objName>
- // Note: <objName> refers to the extension-less basename of the .o file (without @ suffix).
- string mapPinLoc = string(BPF_FS_PATH) + lookupPinSubdir(pin_subdir, prefix) + "map_" +
- (md[i].shared ? "" : objName) + "_" + mapNames[i];
- bool reuse = false;
- unique_fd fd;
- int saved_errno;
-
- if (access(mapPinLoc.c_str(), F_OK) == 0) {
- fd.reset(mapRetrieveRO(mapPinLoc.c_str()));
- saved_errno = errno;
- ALOGD("bpf_create_map reusing map %s, ret: %d", mapNames[i].c_str(), fd.get());
- reuse = true;
- } else {
- union bpf_attr req = {
- .map_type = type,
- .key_size = md[i].key_size,
- .value_size = md[i].value_size,
- .max_entries = max_entries,
- .map_flags = md[i].map_flags,
- };
- if (isAtLeastKernelVersion(4, 15, 0))
- strlcpy(req.map_name, mapNames[i].c_str(), sizeof(req.map_name));
- fd.reset(bpf(BPF_MAP_CREATE, req));
- saved_errno = errno;
- ALOGD("bpf_create_map name %s, ret: %d", mapNames[i].c_str(), fd.get());
- }
-
- if (!fd.ok()) return -saved_errno;
-
- // When reusing a pinned map, we need to check the map type/sizes/etc match, but for
- // safety (since reuse code path is rare) run these checks even if we just created it.
- // We assume failure is due to pinned map mismatch, hence the 'NOT UNIQUE' return code.
- if (!mapMatchesExpectations(fd, mapNames[i], md[i], type)) return -ENOTUNIQ;
-
- if (!reuse) {
- if (specified(selinux_context)) {
- string createLoc = string(BPF_FS_PATH) + lookupPinSubdir(selinux_context) +
- "tmp_map_" + objName + "_" + mapNames[i];
- ret = bpfFdPin(fd, createLoc.c_str());
- if (ret) {
- int err = errno;
- ALOGE("create %s -> %d [%d:%s]", createLoc.c_str(), ret, err, strerror(err));
- return -err;
- }
- ret = renameat2(AT_FDCWD, createLoc.c_str(),
- AT_FDCWD, mapPinLoc.c_str(), RENAME_NOREPLACE);
- if (ret) {
- int err = errno;
- ALOGE("rename %s %s -> %d [%d:%s]", createLoc.c_str(), mapPinLoc.c_str(), ret,
- err, strerror(err));
- return -err;
- }
- } else {
- ret = bpfFdPin(fd, mapPinLoc.c_str());
- if (ret) {
- int err = errno;
- ALOGE("pin %s -> %d [%d:%s]", mapPinLoc.c_str(), ret, err, strerror(err));
- return -err;
- }
- }
- ret = chmod(mapPinLoc.c_str(), md[i].mode);
- if (ret) {
- int err = errno;
- ALOGE("chmod(%s, 0%o) = %d [%d:%s]", mapPinLoc.c_str(), md[i].mode, ret, err,
- strerror(err));
- return -err;
- }
- ret = chown(mapPinLoc.c_str(), (uid_t)md[i].uid, (gid_t)md[i].gid);
- if (ret) {
- int err = errno;
- ALOGE("chown(%s, %u, %u) = %d [%d:%s]", mapPinLoc.c_str(), md[i].uid, md[i].gid,
- ret, err, strerror(err));
- return -err;
- }
- }
-
- int mapId = bpfGetFdMapId(fd);
- if (mapId == -1) {
- ALOGE("bpfGetFdMapId failed, ret: %d [%d]", mapId, errno);
- } else {
- ALOGI("map %s id %d", mapPinLoc.c_str(), mapId);
- }
-
- mapFds.push_back(std::move(fd));
- }
-
- return ret;
-}
-
-/* For debugging, dump all instructions */
-static void dumpIns(char* ins, int size) {
- for (int row = 0; row < size / 8; row++) {
- ALOGE("%d: ", row);
- for (int j = 0; j < 8; j++) {
- ALOGE("%3x ", ins[(row * 8) + j]);
- }
- ALOGE("\n");
- }
-}
-
-/* For debugging, dump all code sections from cs list */
-static void dumpAllCs(vector<codeSection>& cs) {
- for (int i = 0; i < (int)cs.size(); i++) {
- ALOGE("Dumping cs %d, name %s", int(i), cs[i].name.c_str());
- dumpIns((char*)cs[i].data.data(), cs[i].data.size());
- ALOGE("-----------");
- }
-}
-
-static void applyRelo(void* insnsPtr, Elf64_Addr offset, int fd) {
- int insnIndex;
- struct bpf_insn *insn, *insns;
-
- insns = (struct bpf_insn*)(insnsPtr);
-
- insnIndex = offset / sizeof(struct bpf_insn);
- insn = &insns[insnIndex];
-
- // Occasionally might be useful for relocation debugging, but pretty spammy
- if (0) {
- ALOGV("applying relo to instruction at byte offset: %llu, "
- "insn offset %d, insn %llx",
- (unsigned long long)offset, insnIndex, *(unsigned long long*)insn);
- }
-
- if (insn->code != (BPF_LD | BPF_IMM | BPF_DW)) {
- ALOGE("Dumping all instructions till ins %d", insnIndex);
- ALOGE("invalid relo for insn %d: code 0x%x", insnIndex, insn->code);
- dumpIns((char*)insnsPtr, (insnIndex + 3) * 8);
- return;
- }
-
- insn->imm = fd;
- insn->src_reg = BPF_PSEUDO_MAP_FD;
-}
-
-static void applyMapRelo(ifstream& elfFile, vector<unique_fd> &mapFds, vector<codeSection>& cs) {
- vector<string> mapNames;
-
- int ret = getSectionSymNames(elfFile, "maps", mapNames);
- if (ret) return;
-
- for (int k = 0; k != (int)cs.size(); k++) {
- Elf64_Rel* rel = (Elf64_Rel*)(cs[k].rel_data.data());
- int n_rel = cs[k].rel_data.size() / sizeof(*rel);
-
- for (int i = 0; i < n_rel; i++) {
- int symIndex = ELF64_R_SYM(rel[i].r_info);
- string symName;
-
- ret = getSymNameByIdx(elfFile, symIndex, symName);
- if (ret) return;
-
- /* Find the map fd and apply relo */
- for (int j = 0; j < (int)mapNames.size(); j++) {
- if (!mapNames[j].compare(symName)) {
- applyRelo(cs[k].data.data(), rel[i].r_offset, mapFds[j]);
- break;
- }
- }
- }
- }
-}
-
-static int loadCodeSections(const char* elfPath, vector<codeSection>& cs, const string& license,
- const char* prefix, const unsigned int bpfloader_ver) {
- unsigned kvers = kernelVersion();
-
- if (!kvers) {
- ALOGE("unable to get kernel version");
- return -EINVAL;
- }
-
- string objName = pathToObjName(string(elfPath));
-
- for (int i = 0; i < (int)cs.size(); i++) {
- unique_fd& fd = cs[i].prog_fd;
- int ret;
- string name = cs[i].name;
-
- if (!cs[i].prog_def.has_value()) {
- ALOGE("[%d] '%s' missing program definition! bad bpf.o build?", i, name.c_str());
- return -EINVAL;
- }
-
- unsigned min_kver = cs[i].prog_def->min_kver;
- unsigned max_kver = cs[i].prog_def->max_kver;
- ALOGD("cs[%d].name:%s min_kver:%x .max_kver:%x (kvers:%x)", i, name.c_str(), min_kver,
- max_kver, kvers);
- if (kvers < min_kver) continue;
- if (kvers >= max_kver) continue;
-
- unsigned bpfMinVer = cs[i].prog_def->bpfloader_min_ver;
- unsigned bpfMaxVer = cs[i].prog_def->bpfloader_max_ver;
- domain selinux_context = getDomainFromSelinuxContext(cs[i].prog_def->selinux_context);
- domain pin_subdir = getDomainFromPinSubdir(cs[i].prog_def->pin_subdir);
- // Note: make sure to only check for unrecognized *after* verifying bpfloader
- // version limits include this bpfloader's version.
-
- ALOGD("cs[%d].name:%s requires bpfloader version [0x%05x,0x%05x)", i, name.c_str(),
- bpfMinVer, bpfMaxVer);
- if (bpfloader_ver < bpfMinVer) continue;
- if (bpfloader_ver >= bpfMaxVer) continue;
-
- if ((cs[i].prog_def->ignore_on_eng && isEng()) ||
- (cs[i].prog_def->ignore_on_user && isUser()) ||
- (cs[i].prog_def->ignore_on_userdebug && isUserdebug())) {
- ALOGD("cs[%d].name:%s is ignored on %s builds", i, name.c_str(),
- getBuildType().c_str());
- continue;
- }
-
- if ((isArm() && isKernel32Bit() && cs[i].prog_def->ignore_on_arm32) ||
- (isArm() && isKernel64Bit() && cs[i].prog_def->ignore_on_aarch64) ||
- (isX86() && isKernel32Bit() && cs[i].prog_def->ignore_on_x86_32) ||
- (isX86() && isKernel64Bit() && cs[i].prog_def->ignore_on_x86_64) ||
- (isRiscV() && cs[i].prog_def->ignore_on_riscv64)) {
- ALOGD("cs[%d].name:%s is ignored on %s", i, name.c_str(), describeArch());
- continue;
- }
-
- if (unrecognized(pin_subdir)) return -ENOTDIR;
-
- if (specified(selinux_context)) {
- ALOGI("prog %s selinux_context [%-32s] -> %d -> '%s' (%s)", name.c_str(),
- cs[i].prog_def->selinux_context, static_cast<int>(selinux_context),
- lookupSelinuxContext(selinux_context), lookupPinSubdir(selinux_context));
- }
-
- if (specified(pin_subdir)) {
- ALOGI("prog %s pin_subdir [%-32s] -> %d -> '%s'", name.c_str(),
- cs[i].prog_def->pin_subdir, static_cast<int>(pin_subdir),
- lookupPinSubdir(pin_subdir));
- }
-
- // strip any potential $foo suffix
- // this can be used to provide duplicate programs
- // conditionally loaded based on running kernel version
- name = name.substr(0, name.find_last_of('$'));
-
- bool reuse = false;
- // Format of pin location is
- // /sys/fs/bpf/<prefix>prog_<objName>_<progName>
- string progPinLoc = string(BPF_FS_PATH) + lookupPinSubdir(pin_subdir, prefix) + "prog_" +
- objName + '_' + string(name);
- if (access(progPinLoc.c_str(), F_OK) == 0) {
- fd.reset(retrieveProgram(progPinLoc.c_str()));
- ALOGD("New bpf prog load reusing prog %s, ret: %d (%s)", progPinLoc.c_str(), fd.get(),
- (!fd.ok() ? std::strerror(errno) : "no error"));
- reuse = true;
- } else {
- vector<char> log_buf(BPF_LOAD_LOG_SZ, 0);
-
- union bpf_attr req = {
- .prog_type = cs[i].type,
- .kern_version = kvers,
- .license = ptr_to_u64(license.c_str()),
- .insns = ptr_to_u64(cs[i].data.data()),
- .insn_cnt = static_cast<__u32>(cs[i].data.size() / sizeof(struct bpf_insn)),
- .log_level = 1,
- .log_buf = ptr_to_u64(log_buf.data()),
- .log_size = static_cast<__u32>(log_buf.size()),
- .expected_attach_type = cs[i].expected_attach_type,
- };
- if (isAtLeastKernelVersion(4, 15, 0))
- strlcpy(req.prog_name, cs[i].name.c_str(), sizeof(req.prog_name));
- fd.reset(bpf(BPF_PROG_LOAD, req));
-
- ALOGD("BPF_PROG_LOAD call for %s (%s) returned fd: %d (%s)", elfPath,
- cs[i].name.c_str(), fd.get(), (!fd.ok() ? std::strerror(errno) : "no error"));
-
- if (!fd.ok()) {
- vector<string> lines = android::base::Split(log_buf.data(), "\n");
-
- ALOGW("BPF_PROG_LOAD - BEGIN log_buf contents:");
- for (const auto& line : lines) ALOGW("%s", line.c_str());
- ALOGW("BPF_PROG_LOAD - END log_buf contents.");
-
- if (cs[i].prog_def->optional) {
- ALOGW("failed program is marked optional - continuing...");
- continue;
- }
- ALOGE("non-optional program failed to load.");
- }
- }
-
- if (!fd.ok()) return fd.get();
-
- if (!reuse) {
- if (specified(selinux_context)) {
- string createLoc = string(BPF_FS_PATH) + lookupPinSubdir(selinux_context) +
- "tmp_prog_" + objName + '_' + string(name);
- ret = bpfFdPin(fd, createLoc.c_str());
- if (ret) {
- int err = errno;
- ALOGE("create %s -> %d [%d:%s]", createLoc.c_str(), ret, err, strerror(err));
- return -err;
- }
- ret = renameat2(AT_FDCWD, createLoc.c_str(),
- AT_FDCWD, progPinLoc.c_str(), RENAME_NOREPLACE);
- if (ret) {
- int err = errno;
- ALOGE("rename %s %s -> %d [%d:%s]", createLoc.c_str(), progPinLoc.c_str(), ret,
- err, strerror(err));
- return -err;
- }
- } else {
- ret = bpfFdPin(fd, progPinLoc.c_str());
- if (ret) {
- int err = errno;
- ALOGE("create %s -> %d [%d:%s]", progPinLoc.c_str(), ret, err, strerror(err));
- return -err;
- }
- }
- if (chmod(progPinLoc.c_str(), 0440)) {
- int err = errno;
- ALOGE("chmod %s 0440 -> [%d:%s]", progPinLoc.c_str(), err, strerror(err));
- return -err;
- }
- if (chown(progPinLoc.c_str(), (uid_t)cs[i].prog_def->uid,
- (gid_t)cs[i].prog_def->gid)) {
- int err = errno;
- ALOGE("chown %s %d %d -> [%d:%s]", progPinLoc.c_str(), cs[i].prog_def->uid,
- cs[i].prog_def->gid, err, strerror(err));
- return -err;
- }
- }
-
- int progId = bpfGetFdProgId(fd);
- if (progId == -1) {
- ALOGE("bpfGetFdProgId failed, ret: %d [%d]", progId, errno);
- } else {
- ALOGI("prog %s id %d", progPinLoc.c_str(), progId);
- }
- }
-
- return 0;
-}
-
-int loadProg(const char* const elfPath, bool* const isCritical, const unsigned int bpfloader_ver,
- const Location& location) {
- vector<char> license;
- vector<char> critical;
- vector<codeSection> cs;
- vector<unique_fd> mapFds;
- int ret;
-
- if (!isCritical) return -1;
- *isCritical = false;
-
- ifstream elfFile(elfPath, ios::in | ios::binary);
- if (!elfFile.is_open()) return -1;
-
- ret = readSectionByName("critical", elfFile, critical);
- *isCritical = !ret;
-
- ret = readSectionByName("license", elfFile, license);
- if (ret) {
- ALOGE("Couldn't find license in %s", elfPath);
- return ret;
- } else {
- ALOGD("Loading %s%s ELF object %s with license %s",
- *isCritical ? "critical for " : "optional", *isCritical ? (char*)critical.data() : "",
- elfPath, (char*)license.data());
- }
-
- // the following default values are for bpfloader V0.0 format which does not include them
- unsigned int bpfLoaderMinVer =
- readSectionUint("bpfloader_min_ver", elfFile, DEFAULT_BPFLOADER_MIN_VER);
- unsigned int bpfLoaderMaxVer =
- readSectionUint("bpfloader_max_ver", elfFile, DEFAULT_BPFLOADER_MAX_VER);
- unsigned int bpfLoaderMinRequiredVer =
- readSectionUint("bpfloader_min_required_ver", elfFile, 0);
- unsigned int netBpfLoadMinVer =
- readSectionUint("netbpfload_min_ver", elfFile, 0);
- size_t sizeOfBpfMapDef =
- readSectionUint("size_of_bpf_map_def", elfFile, DEFAULT_SIZEOF_BPF_MAP_DEF);
- size_t sizeOfBpfProgDef =
- readSectionUint("size_of_bpf_prog_def", elfFile, DEFAULT_SIZEOF_BPF_PROG_DEF);
-
- // temporary hack to enable gentle enablement of mainline NetBpfLoad
- if (bpfloader_ver < netBpfLoadMinVer) {
- ALOGI("NetBpfLoad version %d ignoring ELF object %s with netbpfload min ver %d",
- bpfloader_ver, elfPath, netBpfLoadMinVer);
- return 0;
- }
-
- // inclusive lower bound check
- if (bpfloader_ver < bpfLoaderMinVer) {
- ALOGI("BpfLoader version 0x%05x ignoring ELF object %s with min ver 0x%05x",
- bpfloader_ver, elfPath, bpfLoaderMinVer);
- return 0;
- }
-
- // exclusive upper bound check
- if (bpfloader_ver >= bpfLoaderMaxVer) {
- ALOGI("BpfLoader version 0x%05x ignoring ELF object %s with max ver 0x%05x",
- bpfloader_ver, elfPath, bpfLoaderMaxVer);
- return 0;
- }
-
- if (bpfloader_ver < bpfLoaderMinRequiredVer) {
- ALOGI("BpfLoader version 0x%05x failing due to ELF object %s with required min ver 0x%05x",
- bpfloader_ver, elfPath, bpfLoaderMinRequiredVer);
- return -1;
- }
-
- ALOGI("BpfLoader version 0x%05x processing ELF object %s with ver [0x%05x,0x%05x)",
- bpfloader_ver, elfPath, bpfLoaderMinVer, bpfLoaderMaxVer);
-
- if (sizeOfBpfMapDef < DEFAULT_SIZEOF_BPF_MAP_DEF) {
- ALOGE("sizeof(bpf_map_def) of %zu is too small (< %d)", sizeOfBpfMapDef,
- DEFAULT_SIZEOF_BPF_MAP_DEF);
- return -1;
- }
-
- if (sizeOfBpfProgDef < DEFAULT_SIZEOF_BPF_PROG_DEF) {
- ALOGE("sizeof(bpf_prog_def) of %zu is too small (< %d)", sizeOfBpfProgDef,
- DEFAULT_SIZEOF_BPF_PROG_DEF);
- return -1;
- }
-
- ret = readCodeSections(elfFile, cs, sizeOfBpfProgDef);
- if (ret) {
- ALOGE("Couldn't read all code sections in %s", elfPath);
- return ret;
- }
-
- /* Just for future debugging */
- if (0) dumpAllCs(cs);
-
- ret = createMaps(elfPath, elfFile, mapFds, location.prefix, sizeOfBpfMapDef, bpfloader_ver);
- if (ret) {
- ALOGE("Failed to create maps: (ret=%d) in %s", ret, elfPath);
- return ret;
- }
-
- for (int i = 0; i < (int)mapFds.size(); i++)
- ALOGV("map_fd found at %d is %d in %s", i, mapFds[i].get(), elfPath);
-
- applyMapRelo(elfFile, mapFds, cs);
-
- ret = loadCodeSections(elfPath, cs, string(license.data()), location.prefix, bpfloader_ver);
- if (ret) ALOGE("Failed to load programs, loadCodeSections ret=%d", ret);
-
- return ret;
-}
-
-} // namespace bpf
-} // namespace android
diff --git a/netbpfload/loader.h b/netbpfload/loader.h
deleted file mode 100644
index 4da6830..0000000
--- a/netbpfload/loader.h
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2018-2023 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.
- */
-
-#pragma once
-
-#include <linux/bpf.h>
-
-#include <fstream>
-
-namespace android {
-namespace bpf {
-
-// Bpf programs may specify per-program & per-map selinux_context and pin_subdir.
-//
-// The BpfLoader needs to convert these bpf.o specified strings into an enum
-// for internal use (to check that valid values were specified for the specific
-// location of the bpf.o file).
-//
-// It also needs to map selinux_context's into pin_subdir's.
-// This is because of how selinux_context is actually implemented via pin+rename.
-//
-// Thus 'domain' enumerates all selinux_context's/pin_subdir's that the BpfLoader
-// is aware of. Thus there currently needs to be a 1:1 mapping between the two.
-//
-enum class domain : int {
- unrecognized = -1, // invalid for this version of the bpfloader
- unspecified = 0, // means just use the default for that specific pin location
- tethering, // (S+) fs_bpf_tethering /sys/fs/bpf/tethering
- net_private, // (T+) fs_bpf_net_private /sys/fs/bpf/net_private
- net_shared, // (T+) fs_bpf_net_shared /sys/fs/bpf/net_shared
- netd_readonly, // (T+) fs_bpf_netd_readonly /sys/fs/bpf/netd_readonly
- netd_shared, // (T+) fs_bpf_netd_shared /sys/fs/bpf/netd_shared
-};
-
-// Note: this does not include domain::unrecognized, but does include domain::unspecified
-static constexpr domain AllDomains[] = {
- domain::unspecified,
- domain::tethering,
- domain::net_private,
- domain::net_shared,
- domain::netd_readonly,
- domain::netd_shared,
-};
-
-static constexpr bool unrecognized(domain d) {
- return d == domain::unrecognized;
-}
-
-// Note: this doesn't handle unrecognized, handle it first.
-static constexpr bool specified(domain d) {
- return d != domain::unspecified;
-}
-
-struct Location {
- const char* const dir = "";
- const char* const prefix = "";
-};
-
-// BPF loader implementation. Loads an eBPF ELF object
-int loadProg(const char* elfPath, bool* isCritical, const unsigned int bpfloader_ver,
- const Location &location = {});
-
-// Exposed for testing
-unsigned int readSectionUint(const char* name, std::ifstream& elfFile, unsigned int defVal);
-
-// Returns the build type string (from ro.build.type).
-const std::string& getBuildType();
-
-// The following functions classify the 3 Android build types.
-inline bool isEng() {
- return getBuildType() == "eng";
-}
-inline bool isUser() {
- return getBuildType() == "user";
-}
-inline bool isUserdebug() {
- return getBuildType() == "userdebug";
-}
-
-} // namespace bpf
-} // namespace android
diff --git a/netbpfload/netbpfload.33rc b/netbpfload/netbpfload.33rc
index d269ce9..493731f 100644
--- a/netbpfload/netbpfload.33rc
+++ b/netbpfload/netbpfload.33rc
@@ -17,5 +17,5 @@
file /dev/kmsg w
rlimit memlock 1073741824 1073741824
oneshot
- # TODO: reboot_on_failure reboot,netbpfload-failed
+ reboot_on_failure reboot,netbpfload-failed
override
diff --git a/netd/BpfBaseTest.cpp b/netd/BpfBaseTest.cpp
index c979a7b..34dfbb4 100644
--- a/netd/BpfBaseTest.cpp
+++ b/netd/BpfBaseTest.cpp
@@ -56,7 +56,7 @@
TEST_F(BpfBasicTest, TestCgroupMounted) {
std::string cg2_path;
- ASSERT_EQ(true, CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &cg2_path));
+ ASSERT_EQ(true, CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &cg2_path));
ASSERT_EQ(0, access(cg2_path.c_str(), R_OK));
ASSERT_EQ(0, access((cg2_path + "/cgroup.controllers").c_str(), R_OK));
}
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index 0f84656..9682545 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -216,24 +216,18 @@
}
if (!mainlineNetBpfLoadDone()) {
- const bool enforce_mainline = false; // TODO: flip to true
-
// We're on < U QPR3 & it's the first time netd is starting up (unless crashlooping)
//
// On U QPR3+ netbpfload is guaranteed to run before the platform bpfloader,
// so waitForProgsLoaded() implies mainlineNetBpfLoadDone().
if (!base::SetProperty("ctl.start", "mdnsd_netbpfload")) {
ALOGE("Failed to set property ctl.start=mdnsd_netbpfload, see dmesg for reason.");
- if (enforce_mainline) abort();
+ abort();
}
- if (enforce_mainline) {
- ALOGI("Waiting for Networking BPF programs");
- waitForNetProgsLoaded();
- ALOGI("Networking BPF programs are loaded");
- } else {
- ALOGI("Started mdnsd_netbpfload asynchronously.");
- }
+ ALOGI("Waiting for Networking BPF programs");
+ waitForNetProgsLoaded();
+ ALOGI("Networking BPF programs are loaded");
}
ALOGI("BPF programs are loaded");
diff --git a/networksecurity/OWNERS b/networksecurity/OWNERS
new file mode 100644
index 0000000..1a4130a
--- /dev/null
+++ b/networksecurity/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 1479456
+
+sandrom@google.com
+tweek@google.com
diff --git a/tests/cts/hostside-network-policy/aidl/Android.bp b/networksecurity/framework/Android.bp
similarity index 72%
rename from tests/cts/hostside-network-policy/aidl/Android.bp
rename to networksecurity/framework/Android.bp
index b182090..2b77926 100644
--- a/tests/cts/hostside-network-policy/aidl/Android.bp
+++ b/networksecurity/framework/Android.bp
@@ -1,3 +1,4 @@
+//
// Copyright (C) 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -11,16 +12,20 @@
// 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 {
- default_team: "trendy_team_framework_backstage_power",
+ default_team: "trendy_team_platform_security",
default_applicable_licenses: ["Android-Apache-2.0"],
}
-java_test_helper_library {
- name: "CtsHostsideNetworkPolicyTestsAidl",
- sdk_version: "current",
+filegroup {
+ name: "framework-networksecurity-sources",
srcs: [
- "com/android/cts/netpolicy/hostside/*.aidl",
+ "src/**/*.java",
+ "src/**/*.aidl",
+ ],
+ path: "src",
+ visibility: [
+ "//packages/modules/Connectivity:__subpackages__",
],
}
diff --git a/networksecurity/framework/src/android/net/ct/CertificateTransparencyManager.java b/networksecurity/framework/src/android/net/ct/CertificateTransparencyManager.java
new file mode 100644
index 0000000..94521ae
--- /dev/null
+++ b/networksecurity/framework/src/android/net/ct/CertificateTransparencyManager.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 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.ct;
+
+import android.annotation.FlaggedApi;
+import android.annotation.SystemService;
+
+import com.android.net.ct.flags.Flags;
+
+/**
+ * Provides the primary API for the Certificate Transparency Manager.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_CERTIFICATE_TRANSPARENCY_SERVICE)
+@SystemService(CertificateTransparencyManager.SERVICE_NAME)
+public final class CertificateTransparencyManager {
+
+ public static final String SERVICE_NAME = "certificate_transparency";
+
+ /**
+ * Creates a new CertificateTransparencyManager instance.
+ *
+ * @hide
+ */
+ public CertificateTransparencyManager() {}
+}
diff --git a/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/NetworkCheckResult.aidl b/networksecurity/framework/src/android/net/ct/ICertificateTransparencyManager.aidl
similarity index 61%
copy from tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/NetworkCheckResult.aidl
copy to networksecurity/framework/src/android/net/ct/ICertificateTransparencyManager.aidl
index 7aac2ab..b5bce7f 100644
--- a/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/NetworkCheckResult.aidl
+++ b/networksecurity/framework/src/android/net/ct/ICertificateTransparencyManager.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
+/**
+ * Copyright (c) 2024, 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
+ * 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,
@@ -13,14 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package android.net.ct;
-package com.android.cts.netpolicy.hostside;
-
-import android.net.NetworkInfo;
-
-@JavaDerive(toString=true)
-parcelable NetworkCheckResult {
- boolean connected;
- String details;
- NetworkInfo networkInfo;
-}
\ No newline at end of file
+/**
+* Interface for communicating with CertificateTransparencyService.
+* @hide
+*/
+interface ICertificateTransparencyManager {}
diff --git a/networksecurity/service/Android.bp b/networksecurity/service/Android.bp
new file mode 100644
index 0000000..66d201a
--- /dev/null
+++ b/networksecurity/service/Android.bp
@@ -0,0 +1,40 @@
+// Copyright (C) 2024 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 {
+ default_team: "trendy_team_platform_security",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Main lib for Certificate Transparency services.
+java_library {
+ name: "service-networksecurity-pre-jarjar",
+ defaults: ["framework-system-server-module-defaults"],
+ visibility: ["//packages/modules/Connectivity:__subpackages__"],
+
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ libs: [
+ "framework-connectivity-pre-jarjar",
+ "service-connectivity-pre-jarjar",
+ ],
+
+ // This is included in service-connectivity which is 30+
+ // TODO (b/293613362): allow APEXes to have service jars with higher min_sdk than the APEX
+ // (service-connectivity is only used on 31+) and use 31 here
+ min_sdk_version: "30",
+ sdk_version: "system_server_current",
+ apex_available: ["com.android.tethering"],
+}
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
new file mode 100644
index 0000000..8c53bf7
--- /dev/null
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 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.net.ct;
+
+import android.annotation.RequiresApi;
+import android.content.Context;
+import android.net.ct.ICertificateTransparencyManager;
+import android.os.Build;
+import android.util.Log;
+
+import com.android.net.ct.flags.Flags;
+import com.android.net.module.util.DeviceConfigUtils;
+
+/** Implementation of the Certificate Transparency service. */
+@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+public class CertificateTransparencyService extends ICertificateTransparencyManager.Stub {
+
+ private static final String TAG = "CertificateTransparency";
+ private static final String CERTIFICATE_TRANSPARENCY_ENABLED =
+ "certificate_transparency_service_enabled";
+
+ /**
+ * @return true if the CertificateTransparency service is enabled.
+ */
+ public static boolean enabled(Context context) {
+ // TODO: replace isTetheringFeatureEnabled with CT namespace flag.
+ return DeviceConfigUtils.isTetheringFeatureEnabled(
+ context, CERTIFICATE_TRANSPARENCY_ENABLED)
+ && Flags.certificateTransparencyService();
+ }
+
+ /** Creates a new {@link CertificateTransparencyService} object. */
+ public CertificateTransparencyService(Context context) {}
+
+ /**
+ * Called by {@link com.android.server.ConnectivityServiceInitializer}.
+ *
+ * @see com.android.server.SystemService#onBootPhase
+ */
+ public void onBootPhase(int phase) {
+ Log.d(TAG, "CertificateTransparencyService#onBootPhase " + phase);
+ }
+}
diff --git a/service-t/Android.bp b/service-t/Android.bp
index 779f354..e00b7cf 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -59,6 +59,7 @@
"framework-wifi",
"service-connectivity-pre-jarjar",
"service-nearby-pre-jarjar",
+ "service-networksecurity-pre-jarjar",
"service-thread-pre-jarjar",
service_remoteauth_pre_jarjar_lib,
"ServiceConnectivityResources",
diff --git a/service-t/src/com/android/server/ConnectivityServiceInitializer.java b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
index 1ac2f6e..5d23fdc 100644
--- a/service-t/src/com/android/server/ConnectivityServiceInitializer.java
+++ b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
@@ -28,6 +28,7 @@
import com.android.server.ethernet.EthernetService;
import com.android.server.ethernet.EthernetServiceImpl;
import com.android.server.nearby.NearbyService;
+import com.android.server.net.ct.CertificateTransparencyService;
import com.android.server.thread.ThreadNetworkService;
/**
@@ -43,6 +44,7 @@
private final NearbyService mNearbyService;
private final EthernetServiceImpl mEthernetServiceImpl;
private final ThreadNetworkService mThreadNetworkService;
+ private final CertificateTransparencyService mCertificateTransparencyService;
public ConnectivityServiceInitializer(Context context) {
super(context);
@@ -55,6 +57,7 @@
mNsdService = createNsdService(context);
mNearbyService = createNearbyService(context);
mThreadNetworkService = createThreadNetworkService(context);
+ mCertificateTransparencyService = createCertificateTransparencyService(context);
}
@Override
@@ -111,6 +114,10 @@
if (mThreadNetworkService != null) {
mThreadNetworkService.onBootPhase(phase);
}
+
+ if (SdkLevel.isAtLeastV() && mCertificateTransparencyService != null) {
+ mCertificateTransparencyService.onBootPhase(phase);
+ }
}
/**
@@ -186,4 +193,13 @@
}
return new ThreadNetworkService(context);
}
+
+ /** Return CertificateTransparencyService instance if enable, otherwise null. */
+ @Nullable
+ private CertificateTransparencyService createCertificateTransparencyService(
+ final Context context) {
+ return SdkLevel.isAtLeastV() && CertificateTransparencyService.enabled(context)
+ ? new CertificateTransparencyService(context)
+ : null;
+ }
}
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 64624ae..5f672e7 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -93,6 +93,7 @@
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.DeviceConfigUtils;
+import com.android.net.module.util.DnsUtils;
import com.android.net.module.util.HandlerUtils;
import com.android.net.module.util.InetAddressUtils;
import com.android.net.module.util.PermissionUtils;
@@ -768,7 +769,7 @@
private Set<String> dedupSubtypeLabels(Collection<String> subtypes) {
final Map<String, String> subtypeMap = new LinkedHashMap<>(subtypes.size());
for (String subtype : subtypes) {
- subtypeMap.put(MdnsUtils.toDnsLowerCase(subtype), subtype);
+ subtypeMap.put(DnsUtils.toDnsUpperCase(subtype), subtype);
}
return new ArraySet<>(subtypeMap.values());
}
@@ -1935,6 +1936,8 @@
mContext, MdnsFeatureFlags.NSD_AGGRESSIVE_QUERY_MODE))
.setIsQueryWithKnownAnswerEnabled(mDeps.isFeatureEnabled(
mContext, MdnsFeatureFlags.NSD_QUERY_WITH_KNOWN_ANSWER))
+ .setAvoidAdvertisingEmptyTxtRecords(mDeps.isTetheringFeatureNotChickenedOut(
+ mContext, MdnsFeatureFlags.NSD_AVOID_ADVERTISING_EMPTY_TXT_RECORDS))
.setOverrideProvider(flag -> mDeps.isFeatureEnabled(
mContext, FORCE_ENABLE_FLAG_FOR_TEST_PREFIX + flag))
.build();
diff --git a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
index 54943c7..f55db93 100644
--- a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
+++ b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
@@ -24,6 +24,7 @@
import android.util.Pair;
import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.DnsUtils;
import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.util.MdnsUtils;
@@ -193,7 +194,7 @@
// Ignore any PTR records that don't match the current query.
if (!CollectionUtils.any(questions,
q -> q instanceof MdnsPointerRecord
- && MdnsUtils.equalsDnsLabelIgnoreDnsCase(
+ && DnsUtils.equalsDnsLabelIgnoreDnsCase(
q.getName(), ptrRecord.getName()))) {
continue;
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
index 42efcac..9c52eca 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -41,6 +41,7 @@
import com.android.connectivity.resources.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.DnsUtils;
import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.ConnectivityResources;
import com.android.server.connectivity.mdns.util.MdnsUtils;
@@ -272,7 +273,7 @@
return true;
}
// Check if it conflicts with the default hostname.
- return MdnsUtils.equalsIgnoreDnsCase(newInfo.getHostname(), mDeviceHostName[0]);
+ return DnsUtils.equalsIgnoreDnsCase(newInfo.getHostname(), mDeviceHostName[0]);
}
private void updateRegistrationUntilNoConflict(
@@ -436,8 +437,8 @@
continue;
}
final NsdServiceInfo other = mPendingRegistrations.valueAt(i).getServiceInfo();
- if (MdnsUtils.equalsIgnoreDnsCase(info.getServiceName(), other.getServiceName())
- && MdnsUtils.equalsIgnoreDnsCase(info.getServiceType(),
+ if (DnsUtils.equalsIgnoreDnsCase(info.getServiceName(), other.getServiceName())
+ && DnsUtils.equalsIgnoreDnsCase(info.getServiceType(),
other.getServiceType())) {
return mPendingRegistrations.keyAt(i);
}
@@ -449,7 +450,7 @@
* Get the ID of a conflicting registration due to host, or -1 if none.
*
* <p>If there's already another registration with the same hostname requested by another
- * user, this is a conflict.
+ * UID, this is a conflict.
*
* <p>If there're two registrations both containing address records using the same hostname,
* this is a conflict.
@@ -463,13 +464,13 @@
final NsdServiceInfo otherInfo = otherRegistration.getServiceInfo();
final int otherServiceId = mPendingRegistrations.keyAt(i);
if (clientUid != otherRegistration.mClientUid
- && MdnsUtils.equalsIgnoreDnsCase(
+ && DnsUtils.equalsIgnoreDnsCase(
info.getHostname(), otherInfo.getHostname())) {
return otherServiceId;
}
if (!info.getHostAddresses().isEmpty()
&& !otherInfo.getHostAddresses().isEmpty()
- && MdnsUtils.equalsIgnoreDnsCase(
+ && DnsUtils.equalsIgnoreDnsCase(
info.getHostname(), otherInfo.getHostname())) {
return otherServiceId;
}
@@ -849,7 +850,7 @@
sharedLog.wtf("Invalid priority in config_nsdOffloadServicesPriority: " + entry);
continue;
}
- priorities.put(MdnsUtils.toDnsLowerCase(priorityAndType[1]), priority);
+ priorities.put(DnsUtils.toDnsUpperCase(priorityAndType[1]), priority);
}
return priorities;
}
@@ -995,7 +996,7 @@
@NonNull Registration registration, byte[] rawOffloadPacket) {
final NsdServiceInfo nsdServiceInfo = registration.getServiceInfo();
final Integer mapPriority = mServiceTypeToOffloadPriority.get(
- MdnsUtils.toDnsLowerCase(nsdServiceInfo.getServiceType()));
+ DnsUtils.toDnsUpperCase(nsdServiceInfo.getServiceType()));
// Higher values of priority are less prioritized
final int priority = mapPriority == null ? Integer.MAX_VALUE : mapPriority;
final OffloadServiceInfo offloadServiceInfo = new OffloadServiceInfo(
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
index 0ab7a76..8123d27 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
@@ -30,6 +30,7 @@
import androidx.annotation.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.DnsUtils;
import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.util.MdnsUtils;
@@ -66,8 +67,8 @@
public void put(@NonNull String serviceType, @NonNull SocketKey socketKey,
@NonNull MdnsServiceTypeClient client) {
- final String dnsLowerServiceType = MdnsUtils.toDnsLowerCase(serviceType);
- final Pair<String, SocketKey> perSocketServiceType = new Pair<>(dnsLowerServiceType,
+ final String dnsUpperServiceType = DnsUtils.toDnsUpperCase(serviceType);
+ final Pair<String, SocketKey> perSocketServiceType = new Pair<>(dnsUpperServiceType,
socketKey);
clients.put(perSocketServiceType, client);
}
@@ -75,18 +76,18 @@
@Nullable
public MdnsServiceTypeClient get(
@NonNull String serviceType, @NonNull SocketKey socketKey) {
- final String dnsLowerServiceType = MdnsUtils.toDnsLowerCase(serviceType);
- final Pair<String, SocketKey> perSocketServiceType = new Pair<>(dnsLowerServiceType,
+ final String dnsUpperServiceType = DnsUtils.toDnsUpperCase(serviceType);
+ final Pair<String, SocketKey> perSocketServiceType = new Pair<>(dnsUpperServiceType,
socketKey);
return clients.getOrDefault(perSocketServiceType, null);
}
public List<MdnsServiceTypeClient> getByServiceType(@NonNull String serviceType) {
- final String dnsLowerServiceType = MdnsUtils.toDnsLowerCase(serviceType);
+ final String dnsUpperServiceType = DnsUtils.toDnsUpperCase(serviceType);
final List<MdnsServiceTypeClient> list = new ArrayList<>();
for (int i = 0; i < clients.size(); i++) {
final Pair<String, SocketKey> perSocketServiceType = clients.keyAt(i);
- if (dnsLowerServiceType.equals(perSocketServiceType.first)) {
+ if (dnsUpperServiceType.equals(perSocketServiceType.first)) {
list.add(clients.valueAt(i));
}
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
index c264f25..709dc79 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
@@ -67,6 +67,12 @@
*/
public static final String NSD_QUERY_WITH_KNOWN_ANSWER = "nsd_query_with_known_answer";
+ /**
+ * A feature flag to avoid advertising empty TXT records, as per RFC 6763 6.1.
+ */
+ public static final String NSD_AVOID_ADVERTISING_EMPTY_TXT_RECORDS =
+ "nsd_avoid_advertising_empty_txt_records";
+
// Flag for offload feature
public final boolean mIsMdnsOffloadFeatureEnabled;
@@ -91,6 +97,9 @@
// Flag for query with known-answer
public final boolean mIsQueryWithKnownAnswerEnabled;
+ // Flag for avoiding advertising empty TXT records
+ public final boolean mAvoidAdvertisingEmptyTxtRecords;
+
@Nullable
private final FlagOverrideProvider mOverrideProvider;
@@ -142,6 +151,15 @@
}
/**
+ * Indicates whether {@link #NSD_AVOID_ADVERTISING_EMPTY_TXT_RECORDS} is enabled, including for
+ * testing.
+ */
+ public boolean avoidAdvertisingEmptyTxtRecords() {
+ return mAvoidAdvertisingEmptyTxtRecords
+ || isForceEnabledForTest(NSD_AVOID_ADVERTISING_EMPTY_TXT_RECORDS);
+ }
+
+ /**
* The constructor for {@link MdnsFeatureFlags}.
*/
public MdnsFeatureFlags(boolean isOffloadFeatureEnabled,
@@ -152,6 +170,7 @@
boolean isUnicastReplyEnabled,
boolean isAggressiveQueryModeEnabled,
boolean isQueryWithKnownAnswerEnabled,
+ boolean avoidAdvertisingEmptyTxtRecords,
@Nullable FlagOverrideProvider overrideProvider) {
mIsMdnsOffloadFeatureEnabled = isOffloadFeatureEnabled;
mIncludeInetAddressRecordsInProbing = includeInetAddressRecordsInProbing;
@@ -161,6 +180,7 @@
mIsUnicastReplyEnabled = isUnicastReplyEnabled;
mIsAggressiveQueryModeEnabled = isAggressiveQueryModeEnabled;
mIsQueryWithKnownAnswerEnabled = isQueryWithKnownAnswerEnabled;
+ mAvoidAdvertisingEmptyTxtRecords = avoidAdvertisingEmptyTxtRecords;
mOverrideProvider = overrideProvider;
}
@@ -181,6 +201,7 @@
private boolean mIsUnicastReplyEnabled;
private boolean mIsAggressiveQueryModeEnabled;
private boolean mIsQueryWithKnownAnswerEnabled;
+ private boolean mAvoidAdvertisingEmptyTxtRecords;
private FlagOverrideProvider mOverrideProvider;
/**
@@ -195,6 +216,7 @@
mIsUnicastReplyEnabled = true; // Default enabled.
mIsAggressiveQueryModeEnabled = false;
mIsQueryWithKnownAnswerEnabled = false;
+ mAvoidAdvertisingEmptyTxtRecords = true; // Default enabled.
mOverrideProvider = null;
}
@@ -291,6 +313,16 @@
}
/**
+ * Set whether to avoid advertising empty TXT records.
+ *
+ * @see #NSD_AVOID_ADVERTISING_EMPTY_TXT_RECORDS
+ */
+ public Builder setAvoidAdvertisingEmptyTxtRecords(boolean avoidAdvertisingEmptyTxtRecords) {
+ mAvoidAdvertisingEmptyTxtRecords = avoidAdvertisingEmptyTxtRecords;
+ return this;
+ }
+
+ /**
* Builds a {@link MdnsFeatureFlags} with the arguments supplied to this builder.
*/
public MdnsFeatureFlags build() {
@@ -302,6 +334,7 @@
mIsUnicastReplyEnabled,
mIsAggressiveQueryModeEnabled,
mIsQueryWithKnownAnswerEnabled,
+ mAvoidAdvertisingEmptyTxtRecords,
mOverrideProvider);
}
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
index fcfb15f..c575d40 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
@@ -29,7 +29,9 @@
import android.util.ArrayMap;
import android.util.Log;
+import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.SharedLog;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.IOException;
import java.net.DatagramPacket;
@@ -225,6 +227,12 @@
Log.wtf(TAG, "No mDns packets to send");
return;
}
+ // Check all packets with the same address
+ if (!MdnsUtils.checkAllPacketsWithSameAddress(packets)) {
+ Log.wtf(TAG, "Some mDNS packets have a different target address. addresses="
+ + CollectionUtils.map(packets, DatagramPacket::getSocketAddress));
+ return;
+ }
final boolean isIpv6 = ((InetSocketAddress) packets.get(0).getSocketAddress())
.getAddress() instanceof Inet6Address;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsNsecRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsNsecRecord.java
index 1239180..a5b8803 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsNsecRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsNsecRecord.java
@@ -20,7 +20,7 @@
import android.text.TextUtils;
import com.android.net.module.util.CollectionUtils;
-import com.android.server.connectivity.mdns.util.MdnsUtils;
+import com.android.net.module.util.DnsUtils;
import java.io.IOException;
import java.util.ArrayList;
@@ -153,7 +153,7 @@
@Override
public int hashCode() {
return Objects.hash(super.hashCode(),
- Arrays.hashCode(MdnsUtils.toDnsLabelsLowerCase(mNextDomain)),
+ Arrays.hashCode(DnsUtils.toDnsLabelsUpperCase(mNextDomain)),
Arrays.hashCode(mTypes));
}
@@ -167,7 +167,7 @@
}
return super.equals(other)
- && MdnsUtils.equalsDnsLabelIgnoreDnsCase(mNextDomain,
+ && DnsUtils.equalsDnsLabelIgnoreDnsCase(mNextDomain,
((MdnsNsecRecord) other).mNextDomain)
&& Arrays.equals(mTypes, ((MdnsNsecRecord) other).mTypes);
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsPacketWriter.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketWriter.java
index 6879a64..cf788be 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsPacketWriter.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketWriter.java
@@ -16,8 +16,8 @@
package com.android.server.connectivity.mdns;
+import com.android.net.module.util.DnsUtils;
import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry;
-import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.IOException;
import java.net.DatagramPacket;
@@ -180,7 +180,7 @@
int existingOffset = entry.getKey();
String[] existingLabels = entry.getValue();
- if (MdnsUtils.equalsDnsLabelIgnoreDnsCase(existingLabels, labels)) {
+ if (DnsUtils.equalsDnsLabelIgnoreDnsCase(existingLabels, labels)) {
writePointer(existingOffset);
return;
} else if (MdnsRecord.labelsAreSuffix(existingLabels, labels)) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsPointerRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPointerRecord.java
index e5c90a4..39bf653 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsPointerRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPointerRecord.java
@@ -20,7 +20,7 @@
import androidx.annotation.VisibleForTesting;
-import com.android.server.connectivity.mdns.util.MdnsUtils;
+import com.android.net.module.util.DnsUtils;
import java.io.IOException;
import java.util.Arrays;
@@ -68,7 +68,7 @@
}
public boolean hasSubtype() {
- return (name != null) && (name.length > 2) && MdnsUtils.equalsIgnoreDnsCase(name[1],
+ return (name != null) && (name.length > 2) && DnsUtils.equalsIgnoreDnsCase(name[1],
MdnsConstants.SUBTYPE_LABEL);
}
@@ -83,7 +83,7 @@
@Override
public int hashCode() {
- return (super.hashCode() * 31) + Arrays.hashCode(MdnsUtils.toDnsLabelsLowerCase(pointer));
+ return (super.hashCode() * 31) + Arrays.hashCode(DnsUtils.toDnsLabelsUpperCase(pointer));
}
@Override
@@ -95,7 +95,7 @@
return false;
}
- return super.equals(other) && MdnsUtils.equalsDnsLabelIgnoreDnsCase(pointer,
+ return super.equals(other) && DnsUtils.equalsDnsLabelIgnoreDnsCase(pointer,
((MdnsPointerRecord) other).pointer);
}
}
\ No newline at end of file
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java b/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java
index e88947a..4a44fff 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java
@@ -23,8 +23,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.DnsUtils;
import com.android.net.module.util.SharedLog;
-import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.util.ArrayList;
import java.util.Collections;
@@ -114,7 +114,7 @@
private static boolean containsName(@NonNull List<MdnsRecord> records,
@NonNull String[] name) {
return CollectionUtils.any(records,
- r -> MdnsUtils.equalsDnsLabelIgnoreDnsCase(name, r.getName()));
+ r -> DnsUtils.equalsDnsLabelIgnoreDnsCase(name, r.getName()));
}
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsQueryScheduler.java b/service-t/src/com/android/server/connectivity/mdns/MdnsQueryScheduler.java
index 3fcf0d4..5c02767 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsQueryScheduler.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsQueryScheduler.java
@@ -63,17 +63,22 @@
* rescheduling is not necessary.
*/
@Nullable
- public ScheduledQueryTaskArgs maybeRescheduleCurrentRun(long now,
- long minRemainingTtl, long lastSentTime, long sessionId) {
+ public ScheduledQueryTaskArgs maybeRescheduleCurrentRun(
+ long now,
+ long minRemainingTtl,
+ long lastSentTime,
+ long sessionId,
+ int numOfQueriesBeforeBackoff) {
if (mLastScheduledQueryTaskArgs == null) {
return null;
}
- if (!mLastScheduledQueryTaskArgs.config.shouldUseQueryBackoff()) {
+ if (!mLastScheduledQueryTaskArgs.config.shouldUseQueryBackoff(numOfQueriesBeforeBackoff)) {
return null;
}
final long timeToRun = calculateTimeToRun(mLastScheduledQueryTaskArgs,
- mLastScheduledQueryTaskArgs.config, now, minRemainingTtl, lastSentTime);
+ mLastScheduledQueryTaskArgs.config, now, minRemainingTtl, lastSentTime,
+ numOfQueriesBeforeBackoff);
if (timeToRun <= mLastScheduledQueryTaskArgs.timeToRun) {
return null;
@@ -95,14 +100,16 @@
long minRemainingTtl,
long now,
long lastSentTime,
- long sessionId) {
- final QueryTaskConfig nextRunConfig = currentConfig.getConfigForNextRun();
+ long sessionId,
+ int queryMode,
+ int numOfQueriesBeforeBackoff) {
+ final QueryTaskConfig nextRunConfig = currentConfig.getConfigForNextRun(queryMode);
final long timeToRun;
if (mLastScheduledQueryTaskArgs == null) {
timeToRun = now + nextRunConfig.delayUntilNextTaskWithoutBackoffMs;
} else {
timeToRun = calculateTimeToRun(mLastScheduledQueryTaskArgs,
- nextRunConfig, now, minRemainingTtl, lastSentTime);
+ nextRunConfig, now, minRemainingTtl, lastSentTime, numOfQueriesBeforeBackoff);
}
mLastScheduledQueryTaskArgs = new ScheduledQueryTaskArgs(nextRunConfig, timeToRun,
minRemainingTtl + now,
@@ -122,9 +129,10 @@
}
private static long calculateTimeToRun(@NonNull ScheduledQueryTaskArgs taskArgs,
- QueryTaskConfig queryTaskConfig, long now, long minRemainingTtl, long lastSentTime) {
+ QueryTaskConfig queryTaskConfig, long now, long minRemainingTtl, long lastSentTime,
+ int numOfQueriesBeforeBackoff) {
final long baseDelayInMs = queryTaskConfig.delayUntilNextTaskWithoutBackoffMs;
- if (!queryTaskConfig.shouldUseQueryBackoff()) {
+ if (!queryTaskConfig.shouldUseQueryBackoff(numOfQueriesBeforeBackoff)) {
return lastSentTime + baseDelayInMs;
}
if (minRemainingTtl <= 0) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecord.java
index b865319..d464ca7 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecord.java
@@ -25,7 +25,7 @@
import androidx.annotation.VisibleForTesting;
-import com.android.server.connectivity.mdns.util.MdnsUtils;
+import com.android.net.module.util.DnsUtils;
import java.io.IOException;
import java.util.Arrays;
@@ -139,7 +139,7 @@
}
for (int i = 0; i < list1.length; ++i) {
- if (!MdnsUtils.equalsIgnoreDnsCase(list1[i], list2[i + offset])) {
+ if (!DnsUtils.equalsIgnoreDnsCase(list1[i], list2[i + offset])) {
return false;
}
}
@@ -284,13 +284,13 @@
MdnsRecord otherRecord = (MdnsRecord) other;
- return MdnsUtils.equalsDnsLabelIgnoreDnsCase(name, otherRecord.name) && (type
+ return DnsUtils.equalsDnsLabelIgnoreDnsCase(name, otherRecord.name) && (type
== otherRecord.type);
}
@Override
public int hashCode() {
- return Objects.hash(Arrays.hashCode(MdnsUtils.toDnsLabelsLowerCase(name)), type);
+ return Objects.hash(Arrays.hashCode(DnsUtils.toDnsLabelsUpperCase(name)), type);
}
/**
@@ -311,7 +311,7 @@
public Key(int recordType, String[] recordName) {
this.recordType = recordType;
- this.recordName = MdnsUtils.toDnsLabelsLowerCase(recordName);
+ this.recordName = DnsUtils.toDnsLabelsUpperCase(recordName);
}
@Override
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
index 36f3982..c3cb776 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -38,6 +38,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.DnsUtils;
import com.android.net.module.util.HexDump;
import com.android.server.connectivity.mdns.util.MdnsUtils;
@@ -226,11 +227,13 @@
/**
* Create a ServiceRegistration with only update the subType.
*/
- ServiceRegistration withSubtypes(@NonNull Set<String> newSubtypes) {
+ ServiceRegistration withSubtypes(@NonNull Set<String> newSubtypes,
+ @NonNull MdnsFeatureFlags featureFlags) {
NsdServiceInfo newServiceInfo = new NsdServiceInfo(serviceInfo);
newServiceInfo.setSubtypes(newSubtypes);
return new ServiceRegistration(srvRecord.record.getServiceHost(), newServiceInfo,
- repliedServiceCount, sentPacketCount, exiting, isProbing, ttl);
+ repliedServiceCount, sentPacketCount, exiting, isProbing, ttl,
+ featureFlags);
}
/**
@@ -238,7 +241,7 @@
*/
ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo,
int repliedServiceCount, int sentPacketCount, boolean exiting, boolean isProbing,
- @Nullable Duration ttl) {
+ @Nullable Duration ttl, @NonNull MdnsFeatureFlags featureFlags) {
this.serviceInfo = serviceInfo;
final long nonNameRecordsTtlMillis;
@@ -309,7 +312,8 @@
// Service name is verified unique after probing
true /* cacheFlush */,
nonNameRecordsTtlMillis,
- attrsToTextEntries(serviceInfo.getAttributes())),
+ attrsToTextEntries(
+ serviceInfo.getAttributes(), featureFlags)),
false /* sharedName */);
allRecords.addAll(ptrRecords);
@@ -392,9 +396,10 @@
* @param serviceInfo Service to advertise
*/
ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo,
- int repliedServiceCount, int sentPacketCount, @Nullable Duration ttl) {
+ int repliedServiceCount, int sentPacketCount, @Nullable Duration ttl,
+ @NonNull MdnsFeatureFlags featureFlags) {
this(deviceHostname, serviceInfo,repliedServiceCount, sentPacketCount,
- false /* exiting */, true /* isProbing */, ttl);
+ false /* exiting */, true /* isProbing */, ttl, featureFlags);
}
void setProbing(boolean probing) {
@@ -445,7 +450,7 @@
"Service ID must already exist for an update request: " + serviceId);
}
final ServiceRegistration updatedRegistration = existingRegistration.withSubtypes(
- subtypes);
+ subtypes, mMdnsFeatureFlags);
mServices.put(serviceId, updatedRegistration);
}
@@ -476,7 +481,8 @@
final ServiceRegistration registration = new ServiceRegistration(
mDeviceHostname, serviceInfo, NO_PACKET /* repliedServiceCount */,
- NO_PACKET /* sentPacketCount */, ttl);
+ NO_PACKET /* sentPacketCount */, ttl,
+ mMdnsFeatureFlags);
mServices.put(serviceId, registration);
// Remove existing exiting service
@@ -494,8 +500,8 @@
}
for (int i = 0; i < mServices.size(); i++) {
final NsdServiceInfo info = mServices.valueAt(i).serviceInfo;
- if (MdnsUtils.equalsIgnoreDnsCase(serviceName, info.getServiceName())
- && MdnsUtils.equalsIgnoreDnsCase(serviceType, info.getServiceType())) {
+ if (DnsUtils.equalsIgnoreDnsCase(serviceName, info.getServiceName())
+ && DnsUtils.equalsIgnoreDnsCase(serviceType, info.getServiceType())) {
return mServices.keyAt(i);
}
}
@@ -547,8 +553,17 @@
return new MdnsProber.ProbingInfo(serviceId, probingRecords);
}
- private static List<MdnsServiceInfo.TextEntry> attrsToTextEntries(Map<String, byte[]> attrs) {
- final List<MdnsServiceInfo.TextEntry> out = new ArrayList<>(attrs.size());
+ private static List<MdnsServiceInfo.TextEntry> attrsToTextEntries(
+ @NonNull Map<String, byte[]> attrs, @NonNull MdnsFeatureFlags featureFlags) {
+ final List<MdnsServiceInfo.TextEntry> out = new ArrayList<>(
+ attrs.size() == 0 ? 1 : attrs.size());
+ if (featureFlags.avoidAdvertisingEmptyTxtRecords() && attrs.size() == 0) {
+ // As per RFC6763 6.1, empty TXT records are not allowed, but records containing a
+ // single empty String must be treated as equivalent.
+ out.add(new MdnsServiceInfo.TextEntry("", MdnsServiceInfo.TextEntry.VALUE_NONE));
+ return out;
+ }
+
for (Map.Entry<String, byte[]> attr : attrs.entrySet()) {
out.add(new MdnsServiceInfo.TextEntry(attr.getKey(), attr.getValue()));
}
@@ -821,7 +836,7 @@
must match the question qtype unless the qtype is "ANY" (255) or the rrtype is
"CNAME" (5), and the record rrclass must match the question qclass unless the
qclass is "ANY" (255) */
- if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase(info.record.getName(), question.getName())) {
+ if (!DnsUtils.equalsDnsLabelIgnoreDnsCase(info.record.getName(), question.getName())) {
continue;
}
hasFullyOwnedNameMatch |= !info.isSharedName;
@@ -1232,7 +1247,7 @@
return RecordConflictType.NO_CONFLICT;
}
- if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase(record.getName(), fullServiceName)) {
+ if (!DnsUtils.equalsDnsLabelIgnoreDnsCase(record.getName(), fullServiceName)) {
return RecordConflictType.NO_CONFLICT;
}
@@ -1270,7 +1285,7 @@
}
// Different names. There won't be a conflict.
- if (!MdnsUtils.equalsIgnoreDnsCase(
+ if (!DnsUtils.equalsIgnoreDnsCase(
record.getName()[0], registration.serviceInfo.getHostname())) {
return RecordConflictType.NO_CONFLICT;
}
@@ -1351,7 +1366,7 @@
int id = mServices.keyAt(i);
ServiceRegistration service = mServices.valueAt(i);
if (service.exiting) continue;
- if (MdnsUtils.equalsIgnoreDnsCase(service.serviceInfo.getHostname(), hostname)) {
+ if (DnsUtils.equalsIgnoreDnsCase(service.serviceInfo.getHostname(), hostname)) {
consumer.accept(id, service);
}
}
@@ -1402,7 +1417,8 @@
if (existing == null) return null;
final ServiceRegistration newService = new ServiceRegistration(mDeviceHostname, newInfo,
- existing.repliedServiceCount, existing.sentPacketCount, existing.ttl);
+ existing.repliedServiceCount, existing.sentPacketCount, existing.ttl,
+ mMdnsFeatureFlags);
mServices.put(serviceId, newService);
return makeProbingInfo(serviceId, newService);
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java b/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
index 05ad1be..3636644 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
@@ -21,7 +21,7 @@
import android.net.Network;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.connectivity.mdns.util.MdnsUtils;
+import com.android.net.module.util.DnsUtils;
import java.io.IOException;
import java.util.ArrayList;
@@ -111,7 +111,7 @@
* pointer record is already present in the response with the same TTL.
*/
public synchronized boolean addPointerRecord(MdnsPointerRecord pointerRecord) {
- if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase(serviceName, pointerRecord.getPointer())) {
+ if (!DnsUtils.equalsDnsLabelIgnoreDnsCase(serviceName, pointerRecord.getPointer())) {
throw new IllegalArgumentException(
"Pointer records for different service names cannot be added");
}
@@ -305,13 +305,13 @@
boolean dropAddressRecords = false;
for (MdnsInetAddressRecord inetAddressRecord : getInet4AddressRecords()) {
- if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase(
+ if (!DnsUtils.equalsDnsLabelIgnoreDnsCase(
this.serviceRecord.getServiceHost(), inetAddressRecord.getName())) {
dropAddressRecords = true;
}
}
for (MdnsInetAddressRecord inetAddressRecord : getInet6AddressRecords()) {
- if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase(
+ if (!DnsUtils.equalsDnsLabelIgnoreDnsCase(
this.serviceRecord.getServiceHost(), inetAddressRecord.getName())) {
dropAddressRecords = true;
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
index b812bb4..52e76ad 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
@@ -22,6 +22,7 @@
import android.util.ArrayMap;
import android.util.Pair;
+import com.android.net.module.util.DnsUtils;
import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.EOFException;
@@ -49,7 +50,7 @@
List<MdnsResponse> responses, String[] pointer) {
if (responses != null) {
for (MdnsResponse response : responses) {
- if (MdnsUtils.equalsDnsLabelIgnoreDnsCase(response.getServiceName(), pointer)) {
+ if (DnsUtils.equalsDnsLabelIgnoreDnsCase(response.getServiceName(), pointer)) {
return response;
}
}
@@ -65,7 +66,7 @@
if (serviceRecord == null) {
continue;
}
- if (MdnsUtils.equalsDnsLabelIgnoreDnsCase(serviceRecord.getServiceHost(),
+ if (DnsUtils.equalsDnsLabelIgnoreDnsCase(serviceRecord.getServiceHost(),
hostName)) {
return response;
}
@@ -318,7 +319,7 @@
if (serviceRecord == null) {
continue;
}
- if (MdnsUtils.equalsDnsLabelIgnoreDnsCase(serviceRecord.getServiceHost(), hostName)) {
+ if (DnsUtils.equalsDnsLabelIgnoreDnsCase(serviceRecord.getServiceHost(), hostName)) {
if (result == null) {
result = new ArrayList<>(/* initialCapacity= */ responses.size());
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
index e9a41d1..a8a4ef1 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
@@ -16,10 +16,10 @@
package com.android.server.connectivity.mdns;
+import static com.android.net.module.util.DnsUtils.equalsIgnoreDnsCase;
+import static com.android.net.module.util.DnsUtils.toDnsUpperCase;
import static com.android.server.connectivity.mdns.MdnsResponse.EXPIRATION_NEVER;
import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
-import static com.android.server.connectivity.mdns.util.MdnsUtils.equalsIgnoreDnsCase;
-import static com.android.server.connectivity.mdns.util.MdnsUtils.toDnsLowerCase;
import static java.lang.Math.min;
@@ -49,16 +49,16 @@
*/
public class MdnsServiceCache {
static class CacheKey {
- @NonNull final String mLowercaseServiceType;
+ @NonNull final String mUpperCaseServiceType;
@NonNull final SocketKey mSocketKey;
CacheKey(@NonNull String serviceType, @NonNull SocketKey socketKey) {
- mLowercaseServiceType = toDnsLowerCase(serviceType);
+ mUpperCaseServiceType = toDnsUpperCase(serviceType);
mSocketKey = socketKey;
}
@Override public int hashCode() {
- return Objects.hash(mLowercaseServiceType, mSocketKey);
+ return Objects.hash(mUpperCaseServiceType, mSocketKey);
}
@Override public boolean equals(Object other) {
@@ -68,7 +68,7 @@
if (!(other instanceof CacheKey)) {
return false;
}
- return Objects.equals(mLowercaseServiceType, ((CacheKey) other).mLowercaseServiceType)
+ return Objects.equals(mUpperCaseServiceType, ((CacheKey) other).mUpperCaseServiceType)
&& Objects.equals(mSocketKey, ((CacheKey) other).mSocketKey);
}
}
@@ -233,6 +233,21 @@
}
/**
+ * Remove services which matches the given type and socket.
+ *
+ * @param cacheKey the target CacheKey.
+ */
+ public void removeServices(@NonNull CacheKey cacheKey) {
+ ensureRunningOnHandlerThread(mHandler);
+ // Remove all services
+ if (mCachedServices.remove(cacheKey) == null) {
+ return;
+ }
+ // Update the next expiration check time if services are removed.
+ mNextExpirationTime = getNextExpirationTime(mClock.elapsedRealtime());
+ }
+
+ /**
* Register a callback to listen to service expiration.
*
* <p> Registering the same callback instance twice is a no-op, since MdnsServiceTypeClient
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
index 1ec9e39..a16fcf7 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
@@ -16,8 +16,6 @@
package com.android.server.connectivity.mdns;
-import static com.android.server.connectivity.mdns.MdnsSocket.INTERFACE_INDEX_UNSPECIFIED;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.Network;
@@ -33,7 +31,6 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
@@ -355,6 +352,13 @@
/** Represents a DNS TXT key-value pair defined by RFC 6763. */
public static final class TextEntry implements Parcelable {
+ /**
+ * The value to use for attributes with no value.
+ *
+ * <p>As per RFC6763 P.16, attributes may have no value, which is different from having an
+ * empty value (which would be an empty byte array).
+ */
+ public static final byte[] VALUE_NONE = null;
public static final Parcelable.Creator<TextEntry> CREATOR =
new Parcelable.Creator<TextEntry>() {
@Override
@@ -389,7 +393,7 @@
// 2. If there is no '=' in a DNS-SD TXT record string, then it is a
// boolean attribute, simply identified as being present, with no value.
if (delimitPos < 0) {
- return new TextEntry(new String(textBytes, US_ASCII), (byte[]) null);
+ return new TextEntry(new String(textBytes, US_ASCII), VALUE_NONE);
} else if (delimitPos == 0) {
return null;
}
@@ -400,13 +404,13 @@
/** Creates a new {@link TextEntry} with given key and value of a UTF-8 string. */
public TextEntry(String key, String value) {
- this(key, value == null ? null : value.getBytes(UTF_8));
+ this(key, value == null ? VALUE_NONE : value.getBytes(UTF_8));
}
/** Creates a new {@link TextEntry} with given key and value of a byte array. */
public TextEntry(String key, byte[] value) {
this.key = key;
- this.value = value == null ? null : value.clone();
+ this.value = value == VALUE_NONE ? VALUE_NONE : value.clone();
}
private TextEntry(Parcel in) {
@@ -419,22 +423,26 @@
}
public byte[] getValue() {
- return value == null ? null : value.clone();
+ return value == VALUE_NONE ? VALUE_NONE : value.clone();
}
/** Converts this {@link TextEntry} instance to '=' separated byte array. */
public byte[] toBytes() {
final byte[] keyBytes = key.getBytes(US_ASCII);
- if (value == null) {
+ if (value == VALUE_NONE) {
return keyBytes;
}
return ByteUtils.concat(keyBytes, new byte[]{'='}, value);
}
+ public boolean isEmpty() {
+ return TextUtils.isEmpty(key) && (value == VALUE_NONE || value.length == 0);
+ }
+
/** Converts this {@link TextEntry} instance to '=' separated string. */
@Override
public String toString() {
- if (value == null) {
+ if (value == VALUE_NONE) {
return key;
}
return key + "=" + new String(value, UTF_8);
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceRecord.java
index 0d6a9ec..fd716d2 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceRecord.java
@@ -20,7 +20,7 @@
import androidx.annotation.VisibleForTesting;
-import com.android.server.connectivity.mdns.util.MdnsUtils;
+import com.android.net.module.util.DnsUtils;
import java.io.IOException;
import java.util.Arrays;
@@ -151,7 +151,7 @@
public int hashCode() {
return (super.hashCode() * 31)
+ Objects.hash(servicePriority, serviceWeight,
- Arrays.hashCode(MdnsUtils.toDnsLabelsLowerCase(serviceHost)),
+ Arrays.hashCode(DnsUtils.toDnsLabelsUpperCase(serviceHost)),
servicePort);
}
@@ -168,7 +168,7 @@
return super.equals(other)
&& (servicePriority == otherRecord.servicePriority)
&& (serviceWeight == otherRecord.serviceWeight)
- && MdnsUtils.equalsDnsLabelIgnoreDnsCase(serviceHost, otherRecord.serviceHost)
+ && DnsUtils.equalsDnsLabelIgnoreDnsCase(serviceHost, otherRecord.serviceHost)
&& (servicePort == otherRecord.servicePort);
}
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index b3bdbe0..8959c1b 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -33,6 +33,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.DnsUtils;
import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.util.MdnsUtils;
@@ -120,11 +121,11 @@
* @return true if the service name was not discovered before.
*/
boolean setServiceDiscovered(@NonNull String serviceName) {
- return discoveredServiceNames.add(MdnsUtils.toDnsLowerCase(serviceName));
+ return discoveredServiceNames.add(DnsUtils.toDnsUpperCase(serviceName));
}
void unsetServiceDiscovered(@NonNull String serviceName) {
- discoveredServiceNames.remove(MdnsUtils.toDnsLowerCase(serviceName));
+ discoveredServiceNames.remove(DnsUtils.toDnsUpperCase(serviceName));
}
}
@@ -147,7 +148,8 @@
final List<MdnsResponse> servicesToResolve = makeResponsesForResolve(socketKey);
final QueryTask queryTask = new QueryTask(taskArgs, servicesToResolve,
getAllDiscoverySubtypes(), needSendDiscoveryQueries(listeners),
- getExistingServices());
+ getExistingServices(), searchOptions.onlyUseIpv6OnIpv6OnlyNetworks(),
+ socketKey);
executor.submit(queryTask);
break;
}
@@ -178,7 +180,9 @@
minRemainingTtl,
now,
lastSentTime,
- sentResult.taskArgs.sessionId
+ sentResult.taskArgs.sessionId,
+ searchOptions.getQueryMode(),
+ searchOptions.numOfQueriesBeforeBackoff()
);
dependencies.sendMessageDelayed(
handler,
@@ -303,8 +307,8 @@
serviceCache.unregisterServiceExpiredCallback(cacheKey);
}
- private static MdnsServiceInfo buildMdnsServiceInfoFromResponse(
- @NonNull MdnsResponse response, @NonNull String[] serviceTypeLabels) {
+ private static MdnsServiceInfo buildMdnsServiceInfoFromResponse(@NonNull MdnsResponse response,
+ @NonNull String[] serviceTypeLabels, long elapsedRealtimeMillis) {
String[] hostName = null;
int port = 0;
if (response.hasServiceRecord()) {
@@ -351,7 +355,7 @@
textEntries,
response.getInterfaceIndex(),
response.getNetwork(),
- now.plusMillis(response.getMinRemainingTtl(now.toEpochMilli())));
+ now.plusMillis(response.getMinRemainingTtl(elapsedRealtimeMillis)));
}
private List<MdnsResponse> getExistingServices() {
@@ -380,8 +384,8 @@
if (existingInfo == null) {
for (MdnsResponse existingResponse : serviceCache.getCachedServices(cacheKey)) {
if (!responseMatchesOptions(existingResponse, searchOptions)) continue;
- final MdnsServiceInfo info =
- buildMdnsServiceInfoFromResponse(existingResponse, serviceTypeLabels);
+ final MdnsServiceInfo info = buildMdnsServiceInfoFromResponse(
+ existingResponse, serviceTypeLabels, clock.elapsedRealtime());
listener.onServiceNameDiscovered(info, true /* isServiceFromCache */);
listenerInfo.setServiceDiscovered(info.getServiceInstanceName());
if (existingResponse.isComplete()) {
@@ -396,10 +400,7 @@
// Keep tracking the ScheduledFuture for the task so we can cancel it if caller is not
// interested anymore.
final QueryTaskConfig taskConfig = new QueryTaskConfig(
- searchOptions.getQueryMode(),
- searchOptions.onlyUseIpv6OnIpv6OnlyNetworks(),
- searchOptions.numOfQueriesBeforeBackoff(),
- socketKey);
+ searchOptions.getQueryMode());
final long now = clock.elapsedRealtime();
if (lastSentTime == 0) {
lastSentTime = now;
@@ -412,7 +413,9 @@
minRemainingTtl,
now,
lastSentTime,
- currentSessionId
+ currentSessionId,
+ searchOptions.getQueryMode(),
+ searchOptions.numOfQueriesBeforeBackoff()
);
dependencies.sendMessageDelayed(
handler,
@@ -424,7 +427,8 @@
mdnsQueryScheduler.scheduleFirstRun(taskConfig, now,
minRemainingTtl, currentSessionId), servicesToResolve,
getAllDiscoverySubtypes(), needSendDiscoveryQueries(listeners),
- getExistingServices());
+ getExistingServices(), searchOptions.onlyUseIpv6OnIpv6OnlyNetworks(),
+ socketKey);
executor.submit(queryTask);
}
@@ -458,7 +462,7 @@
@NonNull MdnsSearchOptions options) {
final boolean matchesInstanceName = options.getResolveInstanceName() == null
// DNS is case-insensitive, so ignore case in the comparison
- || MdnsUtils.equalsIgnoreDnsCase(options.getResolveInstanceName(),
+ || DnsUtils.equalsIgnoreDnsCase(options.getResolveInstanceName(),
response.getServiceInstanceName());
// If discovery is requiring some subtypes, the response must have one that matches a
@@ -468,7 +472,7 @@
final boolean matchesSubtype = options.getSubtypes().size() == 0
|| CollectionUtils.any(options.getSubtypes(), requiredSub ->
CollectionUtils.any(responseSubtypes, actualSub ->
- MdnsUtils.equalsIgnoreDnsCase(
+ DnsUtils.equalsIgnoreDnsCase(
MdnsConstants.SUBTYPE_PREFIX + requiredSub, actualSub)));
return matchesInstanceName && matchesSubtype;
@@ -535,7 +539,8 @@
final long minRemainingTtl = getMinRemainingTtl(now);
MdnsQueryScheduler.ScheduledQueryTaskArgs args =
mdnsQueryScheduler.maybeRescheduleCurrentRun(now, minRemainingTtl,
- lastSentTime, currentSessionId + 1);
+ lastSentTime, currentSessionId + 1,
+ searchOptions.numOfQueriesBeforeBackoff());
if (args != null) {
removeScheduledTask();
dependencies.sendMessageDelayed(
@@ -561,7 +566,7 @@
if (response.getServiceInstanceName() != null) {
listeners.valueAt(i).unsetServiceDiscovered(response.getServiceInstanceName());
final MdnsServiceInfo serviceInfo = buildMdnsServiceInfoFromResponse(
- response, serviceTypeLabels);
+ response, serviceTypeLabels, clock.elapsedRealtime());
if (response.isComplete()) {
sharedLog.log(message + ". onServiceRemoved: " + serviceInfo);
listener.onServiceRemoved(serviceInfo);
@@ -605,8 +610,8 @@
+ " %b, responseIsComplete: %b",
serviceInstanceName, newInCache, serviceBecomesComplete,
response.isComplete()));
- MdnsServiceInfo serviceInfo =
- buildMdnsServiceInfoFromResponse(response, serviceTypeLabels);
+ final MdnsServiceInfo serviceInfo = buildMdnsServiceInfoFromResponse(
+ response, serviceTypeLabels, clock.elapsedRealtime());
for (int i = 0; i < listeners.size(); i++) {
// If a service stops matching the options (currently can only happen if it loses a
@@ -658,7 +663,7 @@
continue;
}
if (CollectionUtils.any(resolveResponses,
- r -> MdnsUtils.equalsIgnoreDnsCase(resolveName, r.getServiceInstanceName()))) {
+ r -> DnsUtils.equalsIgnoreDnsCase(resolveName, r.getServiceInstanceName()))) {
continue;
}
MdnsResponse knownResponse =
@@ -723,15 +728,21 @@
private final List<String> subtypes = new ArrayList<>();
private final boolean sendDiscoveryQueries;
private final List<MdnsResponse> existingServices = new ArrayList<>();
+ private final boolean onlyUseIpv6OnIpv6OnlyNetworks;
+ private final SocketKey socketKey;
QueryTask(@NonNull MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs,
@NonNull Collection<MdnsResponse> servicesToResolve,
@NonNull Collection<String> subtypes, boolean sendDiscoveryQueries,
- @NonNull Collection<MdnsResponse> existingServices) {
+ @NonNull Collection<MdnsResponse> existingServices,
+ boolean onlyUseIpv6OnIpv6OnlyNetworks,
+ @NonNull SocketKey socketKey) {
this.taskArgs = taskArgs;
this.servicesToResolve.addAll(servicesToResolve);
this.subtypes.addAll(subtypes);
this.sendDiscoveryQueries = sendDiscoveryQueries;
this.existingServices.addAll(existingServices);
+ this.onlyUseIpv6OnIpv6OnlyNetworks = onlyUseIpv6OnIpv6OnlyNetworks;
+ this.socketKey = socketKey;
}
@Override
@@ -745,8 +756,8 @@
subtypes,
taskArgs.config.expectUnicastResponse,
taskArgs.config.transactionId,
- taskArgs.config.socketKey,
- taskArgs.config.onlyUseIpv6OnIpv6OnlyNetworks,
+ socketKey,
+ onlyUseIpv6OnIpv6OnlyNetworks,
sendDiscoveryQueries,
servicesToResolve,
clock,
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
index 9cfcba1..17e5b31 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
@@ -28,7 +28,9 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.SharedLog;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.IOException;
import java.net.DatagramPacket;
@@ -249,6 +251,12 @@
Log.wtf(TAG, "No mDns packets to send");
return;
}
+ // Check all packets with the same address
+ if (!MdnsUtils.checkAllPacketsWithSameAddress(packets)) {
+ Log.wtf(TAG, "Some mDNS packets have a different target address. addresses="
+ + CollectionUtils.map(packets, DatagramPacket::getSocketAddress));
+ return;
+ }
final boolean isIpv4 = ((InetSocketAddress) packets.get(0).getSocketAddress())
.getAddress() instanceof Inet4Address;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsTextRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsTextRecord.java
index 92cf324..77d1d7a 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsTextRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsTextRecord.java
@@ -89,6 +89,13 @@
}
}
+ private boolean isEmpty() {
+ return entries == null || entries.size() == 0
+ // RFC6763 6.1 indicates that a TXT record with a single zero byte is equivalent to
+ // an empty record.
+ || (entries.size() == 1 && entries.get(0).isEmpty());
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@@ -105,7 +112,7 @@
@Override
public int hashCode() {
- return (super.hashCode() * 31) + Objects.hash(entries);
+ return (super.hashCode() * 31) + (isEmpty() ? 0 : Objects.hash(entries));
}
@Override
@@ -116,7 +123,19 @@
if (!(other instanceof MdnsTextRecord)) {
return false;
}
-
- return super.equals(other) && Objects.equals(entries, ((MdnsTextRecord) other).entries);
+ if (!super.equals(other)) {
+ return false;
+ }
+ // As per RFC6763 6.1: DNS-SD clients MUST treat the following as equivalent:
+ // - A TXT record containing a single zero byte.
+ // - An empty (zero-length) TXT record. (This is not strictly legal, but should one be
+ // received, it should be interpreted as the same as a single empty string.)
+ // - No TXT record
+ // Ensure that empty TXT records are considered equal, so that they are not considered
+ // conflicting for example.
+ if (isEmpty() && ((MdnsTextRecord) other).isEmpty()) {
+ return true;
+ }
+ return Objects.equals(entries, ((MdnsTextRecord) other).entries);
}
}
\ No newline at end of file
diff --git a/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java b/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java
index 0894166..d2cd463 100644
--- a/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java
+++ b/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java
@@ -19,9 +19,6 @@
import static com.android.server.connectivity.mdns.MdnsSearchOptions.AGGRESSIVE_QUERY_MODE;
import static com.android.server.connectivity.mdns.MdnsSearchOptions.PASSIVE_QUERY_MODE;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
import com.android.internal.annotations.VisibleForTesting;
/**
@@ -51,9 +48,6 @@
static final int MAX_TIME_BETWEEN_AGGRESSIVE_BURSTS_MS = 60000;
private final boolean alwaysAskForUnicastResponse =
MdnsConfigs.alwaysAskForUnicastResponseInEachBurst();
- private final int queryMode;
- final boolean onlyUseIpv6OnIpv6OnlyNetworks;
- private final int numOfQueriesBeforeBackoff;
@VisibleForTesting
final int transactionId;
@VisibleForTesting
@@ -64,16 +58,11 @@
final long delayUntilNextTaskWithoutBackoffMs;
private final boolean isFirstBurst;
private final long queryCount;
- @NonNull
- final SocketKey socketKey;
- QueryTaskConfig(@NonNull QueryTaskConfig other, long queryCount, int transactionId,
+ QueryTaskConfig(long queryCount, int transactionId,
boolean expectUnicastResponse, boolean isFirstBurst, int burstCounter,
int queriesPerBurst, int timeBetweenBurstsInMs,
long delayUntilNextTaskWithoutBackoffMs) {
- this.queryMode = other.queryMode;
- this.onlyUseIpv6OnIpv6OnlyNetworks = other.onlyUseIpv6OnIpv6OnlyNetworks;
- this.numOfQueriesBeforeBackoff = other.numOfQueriesBeforeBackoff;
this.transactionId = transactionId;
this.expectUnicastResponse = expectUnicastResponse;
this.queriesPerBurst = queriesPerBurst;
@@ -82,27 +71,20 @@
this.delayUntilNextTaskWithoutBackoffMs = delayUntilNextTaskWithoutBackoffMs;
this.isFirstBurst = isFirstBurst;
this.queryCount = queryCount;
- this.socketKey = other.socketKey;
}
- QueryTaskConfig(int queryMode,
- boolean onlyUseIpv6OnIpv6OnlyNetworks,
- int numOfQueriesBeforeBackoff,
- @Nullable SocketKey socketKey) {
- this.queryMode = queryMode;
- this.onlyUseIpv6OnIpv6OnlyNetworks = onlyUseIpv6OnIpv6OnlyNetworks;
- this.numOfQueriesBeforeBackoff = numOfQueriesBeforeBackoff;
+ QueryTaskConfig(int queryMode) {
this.queriesPerBurst = QUERIES_PER_BURST;
this.burstCounter = 0;
this.transactionId = 1;
this.expectUnicastResponse = true;
this.isFirstBurst = true;
// Config the scan frequency based on the scan mode.
- if (this.queryMode == AGGRESSIVE_QUERY_MODE) {
+ if (queryMode == AGGRESSIVE_QUERY_MODE) {
this.timeBetweenBurstsInMs = INITIAL_AGGRESSIVE_TIME_BETWEEN_BURSTS_MS;
this.delayUntilNextTaskWithoutBackoffMs =
TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS;
- } else if (this.queryMode == PASSIVE_QUERY_MODE) {
+ } else if (queryMode == PASSIVE_QUERY_MODE) {
// In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and then
// in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
// queries.
@@ -116,12 +98,11 @@
this.timeBetweenBurstsInMs = INITIAL_TIME_BETWEEN_BURSTS_MS;
this.delayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
}
- this.socketKey = socketKey;
this.queryCount = 0;
}
long getDelayUntilNextTaskWithoutBackoff(boolean isFirstQueryInBurst,
- boolean isLastQueryInBurst) {
+ boolean isLastQueryInBurst, int queryMode) {
if (isFirstQueryInBurst && queryMode == AGGRESSIVE_QUERY_MODE) {
return 0;
}
@@ -133,7 +114,7 @@
: TIME_BETWEEN_QUERIES_IN_BURST_MS;
}
- boolean getNextExpectUnicastResponse(boolean isLastQueryInBurst) {
+ boolean getNextExpectUnicastResponse(boolean isLastQueryInBurst, int queryMode) {
if (!isLastQueryInBurst) {
return false;
}
@@ -143,7 +124,7 @@
return alwaysAskForUnicastResponse;
}
- int getNextTimeBetweenBurstsMs(boolean isLastQueryInBurst) {
+ int getNextTimeBetweenBurstsMs(boolean isLastQueryInBurst, int queryMode) {
if (!isLastQueryInBurst) {
return timeBetweenBurstsInMs;
}
@@ -155,7 +136,7 @@
/**
* Get new QueryTaskConfig for next run.
*/
- public QueryTaskConfig getConfigForNextRun() {
+ public QueryTaskConfig getConfigForNextRun(int queryMode) {
long newQueryCount = queryCount + 1;
int newTransactionId = transactionId + 1;
if (newTransactionId > UNSIGNED_SHORT_MAX_VALUE) {
@@ -177,16 +158,18 @@
}
}
- return new QueryTaskConfig(this, newQueryCount, newTransactionId,
- getNextExpectUnicastResponse(isLastQueryInBurst), newIsFirstBurst, newBurstCounter,
- newQueriesPerBurst, getNextTimeBetweenBurstsMs(isLastQueryInBurst),
- getDelayUntilNextTaskWithoutBackoff(isFirstQueryInBurst, isLastQueryInBurst));
+ return new QueryTaskConfig(newQueryCount, newTransactionId,
+ getNextExpectUnicastResponse(isLastQueryInBurst, queryMode), newIsFirstBurst,
+ newBurstCounter, newQueriesPerBurst,
+ getNextTimeBetweenBurstsMs(isLastQueryInBurst, queryMode),
+ getDelayUntilNextTaskWithoutBackoff(
+ isFirstQueryInBurst, isLastQueryInBurst, queryMode));
}
/**
* Determine if the query backoff should be used.
*/
- public boolean shouldUseQueryBackoff() {
+ public boolean shouldUseQueryBackoff(int numOfQueriesBeforeBackoff) {
// Don't enable backoff mode during the burst or in the first burst
if (burstCounter != 0 || isFirstBurst) {
return false;
diff --git a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
index 3c11a24..8745941 100644
--- a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
+++ b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
@@ -16,6 +16,8 @@
package com.android.server.connectivity.mdns.util;
+import static com.android.net.module.util.DnsUtils.equalsDnsLabelIgnoreDnsCase;
+import static com.android.net.module.util.DnsUtils.equalsIgnoreDnsCase;
import static com.android.server.connectivity.mdns.MdnsConstants.FLAG_TRUNCATED;
import android.annotation.NonNull;
@@ -34,6 +36,7 @@
import java.io.IOException;
import java.net.DatagramPacket;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
@@ -55,18 +58,17 @@
private MdnsUtils() { }
/**
- * Convert the string to DNS case-insensitive lowercase
+ * Compare labels a equals b or a is suffix of b.
*
- * Per rfc6762#page-46, accented characters are not defined to be automatically equivalent to
- * their unaccented counterparts. So the "DNS lowercase" should be if character is A-Z then they
- * transform into a-z. Otherwise, they are kept as-is.
+ * @param a the type or subtype.
+ * @param b the base type
*/
- public static String toDnsLowerCase(@NonNull String string) {
- final char[] outChars = new char[string.length()];
- for (int i = 0; i < string.length(); i++) {
- outChars[i] = toDnsLowerCase(string.charAt(i));
- }
- return new String(outChars);
+ public static boolean typeEqualsOrIsSubtype(@NonNull String[] a,
+ @NonNull String[] b) {
+ return equalsDnsLabelIgnoreDnsCase(a, b)
+ || ((b.length == (a.length + 2))
+ && equalsIgnoreDnsCase(b[1], MdnsConstants.SUBTYPE_LABEL)
+ && MdnsRecord.labelsAreSuffix(a, b));
}
/**
@@ -80,70 +82,6 @@
}
}
- /**
- * Convert the array of labels to DNS case-insensitive lowercase.
- */
- public static String[] toDnsLabelsLowerCase(@NonNull String[] labels) {
- final String[] outStrings = new String[labels.length];
- for (int i = 0; i < labels.length; ++i) {
- outStrings[i] = toDnsLowerCase(labels[i]);
- }
- return outStrings;
- }
-
- /**
- * Compare two strings by DNS case-insensitive lowercase.
- */
- public static boolean equalsIgnoreDnsCase(@Nullable String a, @Nullable String b) {
- if (a == null || b == null) {
- return a == null && b == null;
- }
- if (a.length() != b.length()) return false;
- for (int i = 0; i < a.length(); i++) {
- if (toDnsLowerCase(a.charAt(i)) != toDnsLowerCase(b.charAt(i))) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Compare two set of DNS labels by DNS case-insensitive lowercase.
- */
- public static boolean equalsDnsLabelIgnoreDnsCase(@NonNull String[] a, @NonNull String[] b) {
- if (a == b) {
- return true;
- }
- int length = a.length;
- if (b.length != length) {
- return false;
- }
- for (int i = 0; i < length; i++) {
- if (!equalsIgnoreDnsCase(a[i], b[i])) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Compare labels a equals b or a is suffix of b.
- *
- * @param a the type or subtype.
- * @param b the base type
- */
- public static boolean typeEqualsOrIsSubtype(@NonNull String[] a,
- @NonNull String[] b) {
- return MdnsUtils.equalsDnsLabelIgnoreDnsCase(a, b)
- || ((b.length == (a.length + 2))
- && MdnsUtils.equalsIgnoreDnsCase(b[1], MdnsConstants.SUBTYPE_LABEL)
- && MdnsRecord.labelsAreSuffix(a, b));
- }
-
- private static char toDnsLowerCase(char a) {
- return a >= 'A' && a <= 'Z' ? (char) (a + ('a' - 'A')) : a;
- }
-
/*** Ensure that current running thread is same as given handler thread */
public static void ensureRunningOnHandlerThread(@NonNull Handler handler) {
if (!isRunningOnHandlerThread(handler)) {
@@ -361,4 +299,23 @@
return SystemClock.elapsedRealtime();
}
}
+
+ /**
+ * Check all DatagramPackets with the same destination address.
+ */
+ public static boolean checkAllPacketsWithSameAddress(List<DatagramPacket> packets) {
+ // No packet for address check
+ if (packets.isEmpty()) {
+ return true;
+ }
+
+ final InetAddress address =
+ ((InetSocketAddress) packets.get(0).getSocketAddress()).getAddress();
+ for (DatagramPacket packet : packets) {
+ if (!address.equals(((InetSocketAddress) packet.getSocketAddress()).getAddress())) {
+ return false;
+ }
+ }
+ return true;
+ }
}
\ No newline at end of file
diff --git a/service-t/src/com/android/server/ethernet/EthernetInterfaceStateMachine.java b/service-t/src/com/android/server/ethernet/EthernetInterfaceStateMachine.java
new file mode 100644
index 0000000..b8f6859
--- /dev/null
+++ b/service-t/src/com/android/server/ethernet/EthernetInterfaceStateMachine.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2024 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.ethernet;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.NetworkCapabilities;
+import android.net.NetworkProvider;
+import android.net.NetworkProvider.NetworkOfferCallback;
+import android.net.NetworkRequest;
+import android.net.NetworkScore;
+import android.net.ip.IIpClient;
+import android.net.ip.IpClientCallbacks;
+import android.net.ip.IpClientManager;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Message;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.util.State;
+import com.android.net.module.util.SyncStateMachine;
+import com.android.net.module.util.SyncStateMachine.StateInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * EthernetInterfaceStateMachine manages the lifecycle of an ethernet-like network interface which
+ * includes managing a NetworkOffer, IpClient, and NetworkAgent as well as making the interface
+ * available as a tethering downstream.
+ *
+ * All methods exposed by this class *must* be called on the Handler thread provided in the
+ * constructor.
+ */
+class EthernetInterfaceStateMachine extends SyncStateMachine {
+ private static final String TAG = EthernetInterfaceStateMachine.class.getSimpleName();
+
+ private static final int CMD_ON_LINK_UP = 1;
+ private static final int CMD_ON_LINK_DOWN = 2;
+ private static final int CMD_ON_NETWORK_NEEDED = 3;
+ private static final int CMD_ON_NETWORK_UNNEEDED = 4;
+ private static final int CMD_ON_IPCLIENT_CREATED = 5;
+
+ private class EthernetNetworkOfferCallback implements NetworkOfferCallback {
+ private final Set<Integer> mRequestIds = new ArraySet<>();
+
+ @Override
+ public void onNetworkNeeded(@NonNull NetworkRequest request) {
+ if (this != mNetworkOfferCallback) {
+ return;
+ }
+
+ mRequestIds.add(request.requestId);
+ if (mRequestIds.size() == 1) {
+ processMessage(CMD_ON_NETWORK_NEEDED);
+ }
+ }
+
+ @Override
+ public void onNetworkUnneeded(@NonNull NetworkRequest request) {
+ if (this != mNetworkOfferCallback) {
+ return;
+ }
+
+ if (!mRequestIds.remove(request.requestId)) {
+ // This can only happen if onNetworkNeeded was not called for a request or if
+ // the requestId changed. Both should *never* happen.
+ Log.wtf(TAG, "onNetworkUnneeded called for unknown request");
+ }
+ if (mRequestIds.isEmpty()) {
+ processMessage(CMD_ON_NETWORK_UNNEEDED);
+ }
+ }
+ }
+
+ private class EthernetIpClientCallback extends IpClientCallbacks {
+ private final ConditionVariable mOnQuitCv = new ConditionVariable(false);
+
+ private void safelyPostOnHandler(Runnable r) {
+ mHandler.post(() -> {
+ if (this != mIpClientCallback) {
+ return;
+ }
+ r.run();
+ });
+ }
+
+ @Override
+ public void onIpClientCreated(IIpClient ipClient) {
+ safelyPostOnHandler(() -> {
+ // TODO: add a SyncStateMachine#processMessage(cmd, obj) overload.
+ processMessage(CMD_ON_IPCLIENT_CREATED, 0, 0,
+ mDependencies.makeIpClientManager(ipClient));
+ });
+ }
+
+ public void waitOnQuit() {
+ if (!mOnQuitCv.block(5_000 /* timeoutMs */)) {
+ Log.wtf(TAG, "Timed out waiting on IpClient to shutdown.");
+ }
+ }
+
+ @Override
+ public void onQuit() {
+ mOnQuitCv.open();
+ }
+ }
+
+ private @Nullable EthernetNetworkOfferCallback mNetworkOfferCallback;
+ private @Nullable EthernetIpClientCallback mIpClientCallback;
+ private @Nullable IpClientManager mIpClient;
+ private final String mIface;
+ private final Handler mHandler;
+ private final Context mContext;
+ private final NetworkCapabilities mCapabilities;
+ private final NetworkProvider mNetworkProvider;
+ private final EthernetNetworkFactory.Dependencies mDependencies;
+ private boolean mLinkUp = false;
+
+ /** Interface is in tethering mode. */
+ private class TetheringState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_ON_LINK_UP:
+ case CMD_ON_LINK_DOWN:
+ // TODO: think about what to do here.
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ /** Link is down */
+ private class LinkDownState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_ON_LINK_UP:
+ transitionTo(mStoppedState);
+ return HANDLED;
+ case CMD_ON_LINK_DOWN:
+ // do nothing, already in the correct state.
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ /** Parent states of all states that do not cause a NetworkOffer to be extended. */
+ private class NetworkOfferExtendedState extends State {
+ @Override
+ public void enter() {
+ if (mNetworkOfferCallback != null) {
+ // This should never happen. If it happens anyway, log and move on.
+ Log.wtf(TAG, "Previous NetworkOffer was never retracted");
+ }
+
+ mNetworkOfferCallback = new EthernetNetworkOfferCallback();
+ final NetworkScore defaultScore = new NetworkScore.Builder().build();
+ mNetworkProvider.registerNetworkOffer(defaultScore,
+ new NetworkCapabilities(mCapabilities), cmd -> mHandler.post(cmd),
+ mNetworkOfferCallback);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_ON_LINK_UP:
+ // do nothing, already in the correct state.
+ return HANDLED;
+ case CMD_ON_LINK_DOWN:
+ transitionTo(mLinkDownState);
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ mNetworkProvider.unregisterNetworkOffer(mNetworkOfferCallback);
+ mNetworkOfferCallback = null;
+ }
+ }
+
+ /**
+ * Offer is extended but has not been requested.
+ *
+ * StoppedState's sole purpose is to react to a CMD_ON_NETWORK_NEEDED and transition to
+ * StartedState when that happens. Note that StoppedState could be rolled into
+ * NetworkOfferExtendedState. However, keeping the states separate provides some additional
+ * protection by logging a Log.wtf if a CMD_ON_NETWORK_NEEDED is received in an unexpected state
+ * (i.e. StartedState or RunningState). StoppedState is a child of NetworkOfferExtendedState.
+ */
+ private class StoppedState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_ON_NETWORK_NEEDED:
+ transitionTo(mStartedState);
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ /** Network is needed, starts IpClient and manages its lifecycle */
+ private class StartedState extends State {
+ @Override
+ public void enter() {
+ mIpClientCallback = new EthernetIpClientCallback();
+ mDependencies.makeIpClient(mContext, mIface, mIpClientCallback);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_ON_NETWORK_UNNEEDED:
+ transitionTo(mStoppedState);
+ return HANDLED;
+ case CMD_ON_IPCLIENT_CREATED:
+ mIpClient = (IpClientManager) msg.obj;
+ transitionTo(mRunningState);
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ if (mIpClient != null) {
+ mIpClient.shutdown();
+ // TODO: consider adding a StoppingState and making the shutdown operation
+ // asynchronous.
+ mIpClientCallback.waitOnQuit();
+ }
+ mIpClientCallback = null;
+ }
+ }
+
+ /** IpClient is running, starts provisioning and registers NetworkAgent */
+ private class RunningState extends State {
+
+ }
+
+ private final TetheringState mTetheringState = new TetheringState();
+ private final LinkDownState mLinkDownState = new LinkDownState();
+ private final NetworkOfferExtendedState mOfferExtendedState = new NetworkOfferExtendedState();
+ private final StoppedState mStoppedState = new StoppedState();
+ private final StartedState mStartedState = new StartedState();
+ private final RunningState mRunningState = new RunningState();
+
+ public EthernetInterfaceStateMachine(String iface, Handler handler, Context context,
+ NetworkCapabilities capabilities, NetworkProvider provider,
+ EthernetNetworkFactory.Dependencies deps) {
+ super(TAG + "." + iface, handler.getLooper().getThread());
+
+ mIface = iface;
+ mHandler = handler;
+ mContext = context;
+ mCapabilities = capabilities;
+ mNetworkProvider = provider;
+ mDependencies = deps;
+
+ // Interface lifecycle:
+ // [ LinkDownState ]
+ // |
+ // v
+ // *link comes up*
+ // |
+ // v
+ // [ StoppedState ]
+ // |
+ // v
+ // *network is needed*
+ // |
+ // v
+ // [ StartedState ]
+ // |
+ // v
+ // *IpClient is created*
+ // |
+ // v
+ // [ RunningState ]
+ // |
+ // v
+ // *interface is requested for tethering*
+ // |
+ // v
+ // [TetheringState]
+ //
+ // Tethering mode is special as the interface is configured by Tethering, rather than the
+ // ethernet module.
+ final List<StateInfo> states = new ArrayList<>();
+ states.add(new StateInfo(mTetheringState, null));
+
+ // CHECKSTYLE:OFF IndentationCheck
+ // Initial state
+ states.add(new StateInfo(mLinkDownState, null));
+ states.add(new StateInfo(mOfferExtendedState, null));
+ states.add(new StateInfo(mStoppedState, mOfferExtendedState));
+ states.add(new StateInfo(mStartedState, mOfferExtendedState));
+ states.add(new StateInfo(mRunningState, mStartedState));
+ // CHECKSTYLE:ON IndentationCheck
+ addAllStates(states);
+
+ // TODO: set initial state to TetheringState if a tethering interface has been requested and
+ // this is the first interface to be added.
+ start(mLinkDownState);
+ }
+
+ public boolean updateLinkState(boolean up) {
+ if (mLinkUp == up) {
+ return false;
+ }
+
+ // TODO: consider setting mLinkUp as part of processMessage().
+ mLinkUp = up;
+ if (!up) { // was up, goes down
+ processMessage(CMD_ON_LINK_DOWN);
+ } else { // was down, comes up
+ processMessage(CMD_ON_LINK_UP);
+ }
+
+ return true;
+ }
+}
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 114cf2e..11343d2 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -74,12 +74,12 @@
import static com.android.net.module.util.DeviceConfigUtils.getDeviceConfigPropertyInt;
import static com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport;
import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
-import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_PERIODIC;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_DUMPSYS;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_FORCE_UPDATE;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_GLOBAL_ALERT;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_NETWORK_STATUS_CHANGED;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_OPEN_SESSION;
+import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_PERIODIC;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_RAT_CHANGED;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_REG_CALLBACK;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_REMOVE_UIDS;
@@ -242,13 +242,11 @@
// A message for broadcasting ACTION_NETWORK_STATS_UPDATED in handler thread to prevent
// deadlock.
private static final int MSG_BROADCAST_NETWORK_STATS_UPDATED = 4;
-
/** Flags to control detail level of poll event. */
private static final int FLAG_PERSIST_NETWORK = 0x1;
private static final int FLAG_PERSIST_UID = 0x2;
private static final int FLAG_PERSIST_ALL = FLAG_PERSIST_NETWORK | FLAG_PERSIST_UID;
private static final int FLAG_PERSIST_FORCE = 0x100;
-
/**
* When global alert quota is high, wait for this delay before processing each polling,
* and do not schedule further polls once there is already one queued.
@@ -313,6 +311,12 @@
static final String TRAFFIC_STATS_CACHE_MAX_ENTRIES_NAME = "trafficstats_cache_max_entries";
static final int DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS = 1000;
static final int DEFAULT_TRAFFIC_STATS_CACHE_MAX_ENTRIES = 400;
+ /**
+ * The delay time between to network stats update intents.
+ * Added to fix intent spams (b/3115462)
+ */
+ @VisibleForTesting(visibility = PRIVATE)
+ static final int BROADCAST_NETWORK_STATS_UPDATED_DELAY_MS = 1000;
private final Context mContext;
private final NetworkStatsFactory mStatsFactory;
@@ -385,6 +389,7 @@
long getXtPersistBytes(long def);
long getUidPersistBytes(long def);
long getUidTagPersistBytes(long def);
+ long getBroadcastNetworkStatsUpdateDelayMs();
}
private final Object mStatsLock = new Object();
@@ -469,15 +474,36 @@
private long mLastStatsSessionPoll;
+ /**
+ * The timestamp of the most recent network stats broadcast.
+ *
+ * Note that this time could be in the past for completed broadcasts,
+ * or in the future for scheduled broadcasts.
+ *
+ * It is initialized to {@code Long.MIN_VALUE} to ensure that the first broadcast request
+ * is fulfilled immediately, regardless of the delay time.
+ *
+ * This value is used to enforce rate limiting on intents, preventing intent spam.
+ */
+ @GuardedBy("mStatsLock")
+ private long mLatestNetworkStatsUpdatedBroadcastScheduledTime = Long.MIN_VALUE;
+
+
private final TrafficStatsRateLimitCache mTrafficStatsTotalCache;
private final TrafficStatsRateLimitCache mTrafficStatsIfaceCache;
private final TrafficStatsRateLimitCache mTrafficStatsUidCache;
static final String TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG =
"trafficstats_rate_limit_cache_enabled_flag";
+ static final String BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG =
+ "broadcast_network_stats_updated_rate_limit_enabled_flag";
private final boolean mAlwaysUseTrafficStatsRateLimitCache;
private final int mTrafficStatsRateLimitCacheExpiryDuration;
private final int mTrafficStatsRateLimitCacheMaxEntries;
+ private final boolean mBroadcastNetworkStatsUpdatedRateLimitEnabled;
+
+
+
private final Object mOpenSessionCallsLock = new Object();
/**
@@ -669,6 +695,8 @@
mAlwaysUseTrafficStatsRateLimitCache =
mDeps.alwaysUseTrafficStatsRateLimitCache(mContext);
+ mBroadcastNetworkStatsUpdatedRateLimitEnabled =
+ mDeps.enabledBroadcastNetworkStatsUpdatedRateLimiting(mContext);
mTrafficStatsRateLimitCacheExpiryDuration =
mDeps.getTrafficStatsRateLimitCacheExpiryDuration();
mTrafficStatsRateLimitCacheMaxEntries =
@@ -696,6 +724,15 @@
@VisibleForTesting
public static class Dependencies {
/**
+ * Get broadcast network stats updated delay time in ms
+ * @return
+ */
+ @NonNull
+ public long getBroadcastNetworkStatsUpdateDelayMs() {
+ return BROADCAST_NETWORK_STATS_UPDATED_DELAY_MS;
+ }
+
+ /**
* Get legacy platform stats directory.
*/
@NonNull
@@ -927,6 +964,17 @@
}
/**
+ * Get whether broadcast network stats update rate limiting is enabled.
+ *
+ * This method should only be called once in the constructor,
+ * to ensure that the code does not need to deal with flag values changing at runtime.
+ */
+ public boolean enabledBroadcastNetworkStatsUpdatedRateLimiting(Context ctx) {
+ return DeviceConfigUtils.isTetheringFeatureNotChickenedOut(
+ ctx, BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG);
+ }
+
+ /**
* Get whether TrafficStats rate-limit cache is always applied.
*
* This method should only be called once in the constructor,
@@ -2645,8 +2693,22 @@
performSampleLocked();
}
- // finally, dispatch updated event to any listeners
- mHandler.sendMessage(mHandler.obtainMessage(MSG_BROADCAST_NETWORK_STATS_UPDATED));
+ // Dispatch updated event to listeners, preventing intent spamming
+ // (b/343844995) possibly from abnormal modem RAT changes or misbehaving
+ // app calls (see NetworkStatsEventLogger#POLL_REASON_* for possible reasons).
+ // If no broadcasts are scheduled, use the time of the last broadcast
+ // to schedule the next one ASAP.
+ if (!mBroadcastNetworkStatsUpdatedRateLimitEnabled) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_BROADCAST_NETWORK_STATS_UPDATED));
+ } else if (mLatestNetworkStatsUpdatedBroadcastScheduledTime < SystemClock.uptimeMillis()) {
+ mLatestNetworkStatsUpdatedBroadcastScheduledTime = Math.max(
+ mLatestNetworkStatsUpdatedBroadcastScheduledTime
+ + mSettings.getBroadcastNetworkStatsUpdateDelayMs(),
+ SystemClock.uptimeMillis()
+ );
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_BROADCAST_NETWORK_STATS_UPDATED),
+ mLatestNetworkStatsUpdatedBroadcastScheduledTime);
+ }
Trace.traceEnd(TRACE_TAG_NETWORK);
}
@@ -3605,6 +3667,11 @@
public long getUidTagPersistBytes(long def) {
return def;
}
+
+ @Override
+ public long getBroadcastNetworkStatsUpdateDelayMs() {
+ return BROADCAST_NETWORK_STATS_UPDATED_DELAY_MS;
+ }
}
// TODO: Read stats by using BpfNetMapsReader.
diff --git a/service/Android.bp b/service/Android.bp
index 1dd09a9..c68f0b8 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -190,18 +190,12 @@
"connectivity_native_aidl_interface-lateststable-java",
"dnsresolver_aidl_interface-V15-java",
"modules-utils-shell-command-handler",
- "net-utils-device-common",
- "net-utils-device-common-ip",
- "net-utils-device-common-netlink",
- "net-utils-services-common",
+ "net-utils-service-connectivity",
"netd-client",
"networkstack-client",
"PlatformProperties",
"service-connectivity-protos",
"service-connectivity-stats-protos",
- // The required dependency net-utils-device-common-struct-base is in the classpath via
- // framework-connectivity
- "net-utils-device-common-struct",
],
apex_available: [
"com.android.tethering",
@@ -212,6 +206,7 @@
},
visibility: [
"//packages/modules/Connectivity/service-t",
+ "//packages/modules/Connectivity/networksecurity:__subpackages__",
"//packages/modules/Connectivity/tests:__subpackages__",
"//packages/modules/Connectivity/thread/service:__subpackages__",
"//packages/modules/Connectivity/thread/tests:__subpackages__",
@@ -253,6 +248,7 @@
"service-connectivity-pre-jarjar",
"service-connectivity-tiramisu-pre-jarjar",
"service-nearby-pre-jarjar",
+ "service-networksecurity-pre-jarjar",
service_remoteauth_pre_jarjar_lib,
"service-thread-pre-jarjar",
],
@@ -321,6 +317,7 @@
":framework-connectivity-jarjar-rules",
":service-connectivity-jarjar-gen",
":service-nearby-jarjar-gen",
+ ":service-networksecurity-jarjar-gen",
":service-remoteauth-jarjar-gen",
":service-thread-jarjar-gen",
],
@@ -410,6 +407,24 @@
visibility: ["//visibility:private"],
}
+java_genrule {
+ name: "service-networksecurity-jarjar-gen",
+ tool_files: [
+ ":service-networksecurity-pre-jarjar{.jar}",
+ "jarjar-excludes.txt",
+ ],
+ tools: [
+ "jarjar-rules-generator",
+ ],
+ out: ["service_ct_jarjar_rules.txt"],
+ cmd: "$(location jarjar-rules-generator) " +
+ "$(location :service-networksecurity-pre-jarjar{.jar}) " +
+ "--prefix com.android.server.net.ct " +
+ "--excludes $(location jarjar-excludes.txt) " +
+ "--output $(out)",
+ visibility: ["//visibility:private"],
+}
+
genrule {
name: "statslog-connectivity-java-gen",
tools: ["stats-log-api-gen"],
diff --git a/service/ServiceConnectivityResources/res/values-ar/strings.xml b/service/ServiceConnectivityResources/res/values-ar/strings.xml
index 92dd9a1..8cefec4 100644
--- a/service/ServiceConnectivityResources/res/values-ar/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ar/strings.xml
@@ -40,7 +40,7 @@
<item msgid="5624324321165953608">"Wi-Fi"</item>
<item msgid="5667906231066981731">"بلوتوث"</item>
<item msgid="346574747471703768">"إيثرنت"</item>
- <item msgid="5734728378097476003">"شبكة افتراضية خاصة (VPN)"</item>
+ <item msgid="5734728378097476003">"شبكة VPN"</item>
</string-array>
<string name="network_switch_type_name_unknown" msgid="5116448402191972082">"نوع شبكة غير معروف"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values/config_thread.xml b/service/ServiceConnectivityResources/res/values/config_thread.xml
index 02a9ce6..4027038 100644
--- a/service/ServiceConnectivityResources/res/values/config_thread.xml
+++ b/service/ServiceConnectivityResources/res/values/config_thread.xml
@@ -54,9 +54,21 @@
-->
<string translatable="false" name="config_thread_model_name">Thread Border Router</string>
- <!-- Whether the Thread network will be managed by the Google Home ecosystem. When this value
- is set, a TXT entry "vgh=0" or "vgh=1" will be added to the "_mehscop._udp" mDNS service
- respectively (The TXT value is a string).
+ <!-- Specifies vendor-specific mDNS TXT entries which will be included in the "_meshcop._udp"
+ service. The TXT entries list MUST conform to the format requirement in RFC 6763 section 6. For
+ example, the key and value of each TXT entry MUST be separated with "=". If the value length is
+ 0, the trailing "=" may be omitted. Additionally, the TXT keys MUST start with "v" and be at
+ least 2 characters.
+
+ Note, do not include credentials in any of the TXT entries - they will be advertised on Wi-Fi
+ or Ethernet link.
+
+ An example config can be:
+ <string-array name="config_thread_mdns_vendor_specific_txts">
+ <item>vab=123</item>
+ <item>vcd</item>
+ </string-array>
-->
- <bool name="config_thread_managed_by_google_home">false</bool>
+ <string-array name="config_thread_mdns_vendor_specific_txts">
+ </string-array>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index 158b0c8..fbaae05 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -51,6 +51,7 @@
<item type="string" name="config_thread_vendor_name" />
<item type="string" name="config_thread_vendor_oui" />
<item type="string" name="config_thread_model_name" />
+ <item type="array" name="config_thread_mdns_vendor_specific_txts" />
</policy>
</overlayable>
</resources>
diff --git a/service/lint-baseline.xml b/service/lint-baseline.xml
index 3e11d52..b09589c 100644
--- a/service/lint-baseline.xml
+++ b/service/lint-baseline.xml
@@ -3,17 +3,6 @@
<issue
id="NewApi"
- message="Call requires API level 33 (current min is 30): `getUidRule`"
- errorLine1=" return BpfNetMapsReader.getUidRule(sUidOwnerMap, childChain, uid);"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/BpfNetMaps.java"
- line="643"
- column="33"/>
- </issue>
-
- <issue
- id="NewApi"
message="Call requires API level 31 (current min is 30): `BpfBitmap`"
errorLine1=" return new BpfBitmap(BLOCKED_PORTS_MAP_PATH);"
errorLine2=" ~~~~~~~~~~~~~">
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index b3e7d8c..44868b2d 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -580,6 +580,7 @@
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public int getUidRule(final int childChain, final int uid) {
return BpfNetMapsUtils.getUidRule(sUidOwnerMap, childChain, uid);
}
diff --git a/service/src/com/android/server/CallbackQueue.java b/service/src/com/android/server/CallbackQueue.java
new file mode 100644
index 0000000..4e068ea
--- /dev/null
+++ b/service/src/com/android/server/CallbackQueue.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 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;
+
+import android.annotation.NonNull;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+
+import com.android.net.module.util.GrowingIntArray;
+
+/**
+ * A utility class to add/remove {@link NetworkCallback}s from a queue.
+ *
+ * <p>This is intended to be used as a temporary builder to create/modify callbacks stored in an int
+ * array for memory efficiency.
+ *
+ * <p>Intended usage:
+ * <pre>
+ * final CallbackQueue queue = new CallbackQueue(storedCallbacks);
+ * queue.forEach(netId, callbackId -> { [...] });
+ * queue.addCallback(netId, callbackId);
+ * [...]
+ * storedCallbacks = queue.getMinimizedBackingArray();
+ * </pre>
+ *
+ * <p>This class is not thread-safe.
+ */
+public class CallbackQueue extends GrowingIntArray {
+ public CallbackQueue(int[] initialCallbacks) {
+ super(initialCallbacks);
+ }
+
+ /**
+ * Get a callback int from netId and callbackId.
+ *
+ * <p>The first 16 bits of each int is the netId; the last 16 bits are the callback index.
+ */
+ private static int getCallbackInt(int netId, int callbackId) {
+ return (netId << 16) | (callbackId & 0xffff);
+ }
+
+ private static int getNetId(int callbackInt) {
+ return callbackInt >>> 16;
+ }
+
+ private static int getCallbackId(int callbackInt) {
+ return callbackInt & 0xffff;
+ }
+
+ /**
+ * A consumer interface for {@link #forEach(CallbackConsumer)}.
+ *
+ * <p>This is similar to a BiConsumer<Integer, Integer>, but avoids the boxing cost.
+ */
+ public interface CallbackConsumer {
+ /**
+ * Method called on each callback in the queue.
+ */
+ void accept(int netId, int callbackId);
+ }
+
+ /**
+ * Iterate over all callbacks in the queue.
+ */
+ public void forEach(@NonNull CallbackConsumer consumer) {
+ forEach(value -> {
+ final int netId = getNetId(value);
+ final int callbackId = getCallbackId(value);
+ consumer.accept(netId, callbackId);
+ });
+ }
+
+ /**
+ * Indicates whether the queue contains a callback for the given (netId, callbackId).
+ */
+ public boolean hasCallback(int netId, int callbackId) {
+ return contains(getCallbackInt(netId, callbackId));
+ }
+
+ /**
+ * Remove all callbacks for the given netId.
+ *
+ * @return true if at least one callback was removed.
+ */
+ public boolean removeCallbacksForNetId(int netId) {
+ return removeValues(cb -> getNetId(cb) == netId);
+ }
+
+ /**
+ * Remove all callbacks for the given netId and callbackId.
+ * @return true if at least one callback was removed.
+ */
+ public boolean removeCallbacks(int netId, int callbackId) {
+ final int cbInt = getCallbackInt(netId, callbackId);
+ return removeValues(cb -> cb == cbInt);
+ }
+
+ /**
+ * Add a callback at the end of the queue.
+ */
+ public void addCallback(int netId, int callbackId) {
+ add(getCallbackInt(netId, callbackId));
+ }
+
+ @Override
+ protected String valueToString(int item) {
+ final int callbackId = getCallbackId(item);
+ final int netId = getNetId(item);
+ return ConnectivityManager.getCallbackName(callbackId) + "(" + netId + ")";
+ }
+}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 5186203..0fe24a2 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -38,14 +38,27 @@
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
import static android.net.ConnectivityManager.BLOCKED_REASON_LOCKDOWN_VPN;
-import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.BLOCKED_REASON_NETWORK_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
+import static android.net.ConnectivityManager.CALLBACK_AVAILABLE;
+import static android.net.ConnectivityManager.CALLBACK_BLK_CHANGED;
+import static android.net.ConnectivityManager.CALLBACK_CAP_CHANGED;
import static android.net.ConnectivityManager.CALLBACK_IP_CHANGED;
+import static android.net.ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED;
+import static android.net.ConnectivityManager.CALLBACK_LOSING;
+import static android.net.ConnectivityManager.CALLBACK_LOST;
+import static android.net.ConnectivityManager.CALLBACK_PRECHECK;
+import static android.net.ConnectivityManager.CALLBACK_RESUMED;
+import static android.net.ConnectivityManager.CALLBACK_SUSPENDED;
+import static android.net.ConnectivityManager.CALLBACK_UNAVAIL;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
import static android.net.ConnectivityManager.FIREWALL_RULE_DEFAULT;
import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
+import static android.net.ConnectivityManager.NETID_UNSET;
+import static android.net.ConnectivityManager.NetworkCallback.DECLARED_METHODS_ALL;
+import static android.net.ConnectivityManager.NetworkCallback.DECLARED_METHODS_NONE;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE;
@@ -109,7 +122,6 @@
import static android.os.Process.INVALID_UID;
import static android.os.Process.VPN_UID;
import static android.system.OsConstants.ETH_P_ALL;
-import static android.system.OsConstants.F_OK;
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
@@ -134,10 +146,9 @@
import static com.android.net.module.util.PermissionUtils.hasAnyPermissionOf;
import static com.android.server.ConnectivityStatsLog.CONNECTIVITY_STATE_SAMPLE;
import static com.android.server.connectivity.ConnectivityFlags.DELAY_DESTROY_SOCKETS;
-import static com.android.server.connectivity.ConnectivityFlags.REQUEST_RESTRICTED_WIFI;
import static com.android.server.connectivity.ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING;
-
-import static java.util.Map.Entry;
+import static com.android.server.connectivity.ConnectivityFlags.QUEUE_CALLBACKS_FOR_FROZEN_APPS;
+import static com.android.server.connectivity.ConnectivityFlags.REQUEST_RESTRICTED_WIFI;
import android.Manifest;
import android.annotation.CheckResult;
@@ -279,7 +290,6 @@
import android.stats.connectivity.ValidatedState;
import android.sysprop.NetworkProperties;
import android.system.ErrnoException;
-import android.system.Os;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -323,6 +333,7 @@
import com.android.net.module.util.LocationPermissionChecker;
import com.android.net.module.util.PerUidCounter;
import com.android.net.module.util.PermissionUtils;
+import com.android.net.module.util.RoutingCoordinatorService;
import com.android.net.module.util.TcUtils;
import com.android.net.module.util.netlink.InetDiagMessage;
import com.android.networkstack.apishim.BroadcastOptionsShimImpl;
@@ -360,7 +371,6 @@
import com.android.server.connectivity.ProfileNetworkPreferenceInfo;
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.QosCallbackTracker;
-import com.android.server.connectivity.RoutingCoordinatorService;
import com.android.server.connectivity.SatelliteAccessController;
import com.android.server.connectivity.UidRangeUtils;
import com.android.server.connectivity.VpnNetworkPreferenceInfo;
@@ -393,7 +403,6 @@
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Objects;
-import java.util.Random;
import java.util.Set;
import java.util.SortedSet;
import java.util.StringJoiner;
@@ -503,6 +512,9 @@
private final boolean mUseDeclaredMethodsForCallbacksEnabled;
+ // Flag to delay callbacks for frozen apps, suppressing duplicate and stale callbacks.
+ private final boolean mQueueCallbacksForFrozenApps;
+
/**
* Uids ConnectivityService tracks blocked status of to send blocked status callbacks.
* Key is uid based on mAsUid of registered networkRequestInfo
@@ -1791,7 +1803,7 @@
mDefaultRequest = new NetworkRequestInfo(
Process.myUid(), defaultInternetRequest, null,
null /* binder */, NetworkCallback.FLAG_INCLUDE_LOCATION_INFO,
- null /* attributionTags */);
+ null /* attributionTags */, DECLARED_METHODS_NONE);
mNetworkRequests.put(defaultInternetRequest, mDefaultRequest);
mDefaultNetworkRequests.add(mDefaultRequest);
mNetworkRequestInfoLogs.log("REGISTER " + mDefaultRequest);
@@ -1845,35 +1857,6 @@
new PermissionMonitor(mContext, mNetd, mBpfNetMaps, mHandlerThread);
mHandlerThread.start();
mHandler = new InternalHandler(mHandlerThread.getLooper());
- // Temporary hack to report netbpfload result.
- // TODO: remove in 2024-09 when netbpfload starts loading mainline bpf programs.
- if (!mDeps.isAtLeastV()) {
- mHandler.postDelayed(() -> {
- // Test Log.wtf reporting pipeline. Ignore this Log.wtf if it shows up in the logs.
- final Random r = new Random();
- if (Build.TYPE.equals("user") && r.nextInt(1000) == 0) {
- Log.wtf(TAG, "NOT A FAILURE, PLEASE IGNORE! Ensure netbpfload result reported");
- }
- // Did netbpfload create the map?
- try {
- Os.access("/sys/fs/bpf/net_shared/map_gentle_test", F_OK);
- } catch (ErrnoException e) {
- Log.wtf(TAG, "netbpfload did not create map", e);
- }
- // Did netbpfload create the program?
- try {
- Os.access("/sys/fs/bpf/net_shared/prog_gentle_skfilter_accept", F_OK);
- } catch (ErrnoException e) {
- Log.wtf(TAG, "netbpfload did not create program", e);
- }
- // Did netbpfload run to completion?
- try {
- Os.access("/sys/fs/bpf/netd_shared/mainline_done", F_OK);
- } catch (ErrnoException e) {
- Log.wtf(TAG, "netbpfload did not run to completion", e);
- }
- }, 30_000 /* delayMillis */);
- }
mTrackerHandler = new NetworkStateTrackerHandler(mHandlerThread.getLooper());
mConnectivityDiagnosticsHandler =
new ConnectivityDiagnosticsHandler(mHandlerThread.getLooper());
@@ -1895,6 +1878,9 @@
context, ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN);
mUseDeclaredMethodsForCallbacksEnabled = mDeps.isFeatureEnabled(context,
ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS);
+ // registerUidFrozenStateChangedCallback is only available on U+
+ mQueueCallbacksForFrozenApps = mDeps.isAtLeastU()
+ && mDeps.isFeatureEnabled(context, QUEUE_CALLBACKS_FOR_FROZEN_APPS);
mCarrierPrivilegeAuthenticator = mDeps.makeCarrierPrivilegeAuthenticator(
mContext, mTelephonyManager, mRequestRestrictedWifiEnabled,
this::handleUidCarrierPrivilegesLost, mHandler);
@@ -2050,7 +2036,7 @@
mDelayDestroySockets = mDeps.isFeatureNotChickenedOut(context, DELAY_DESTROY_SOCKETS);
mAllowSysUiConnectivityReports = mDeps.isFeatureNotChickenedOut(
mContext, ALLOW_SYSUI_CONNECTIVITY_REPORTS);
- if (mDestroyFrozenSockets) {
+ if (mDestroyFrozenSockets || mQueueCallbacksForFrozenApps) {
final UidFrozenStateChangedCallback frozenStateChangedCallback =
new UidFrozenStateChangedCallback() {
@Override
@@ -2162,7 +2148,7 @@
}
@VisibleForTesting
- void updateMobileDataPreferredUids() {
+ public void updateMobileDataPreferredUids() {
mHandler.sendEmptyMessage(EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED);
}
@@ -2207,7 +2193,7 @@
handleRegisterNetworkRequest(new NetworkRequestInfo(
Process.myUid(), networkRequest, null /* messenger */, null /* binder */,
NetworkCallback.FLAG_INCLUDE_LOCATION_INFO,
- null /* attributionTags */));
+ null /* attributionTags */, DECLARED_METHODS_NONE));
} else {
handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID,
/* callOnUnavailable */ false);
@@ -3418,7 +3404,7 @@
}
@VisibleForTesting
- void handleBlockedReasonsChanged(List<Pair<Integer, Integer>> reasonsList) {
+ public void handleBlockedReasonsChanged(List<Pair<Integer, Integer>> reasonsList) {
for (Pair<Integer, Integer> reasons: reasonsList) {
final int uid = reasons.first;
final int blockedReasons = reasons.second;
@@ -3486,6 +3472,14 @@
private void handleFrozenUids(int[] uids, int[] frozenStates) {
ensureRunningOnConnectivityServiceThread();
+ handleDestroyFrozenSockets(uids, frozenStates);
+ handleFreezeNetworkCallbacks(uids, frozenStates);
+ }
+
+ private void handleDestroyFrozenSockets(int[] uids, int[] frozenStates) {
+ if (!mDestroyFrozenSockets) {
+ return;
+ }
for (int i = 0; i < uids.length; i++) {
final int uid = uids[i];
final boolean addReason = frozenStates[i] == UID_FROZEN_STATE_FROZEN;
@@ -3497,6 +3491,73 @@
}
}
+ private void handleFreezeNetworkCallbacks(int[] uids, int[] frozenStates) {
+ if (!mQueueCallbacksForFrozenApps) {
+ return;
+ }
+ for (int i = 0; i < uids.length; i++) {
+ final int uid = uids[i];
+ // These counters may be modified on different threads, but using them here is fine
+ // because this is only an optimization where wrong behavior would only happen if they
+ // are zero even though there is a request registered. This is not possible as they are
+ // always incremented before posting messages to register, and decremented on the
+ // handler thread when unregistering.
+ if (mSystemNetworkRequestCounter.get(uid) == 0
+ && mNetworkRequestCounter.get(uid) == 0) {
+ // Avoid iterating requests if there isn't any. The counters only track app requests
+ // and not internal requests (for example always-on requests which do not have a
+ // mMessenger), so it does not completely match the content of mRequests. This is OK
+ // as only app requests need to be frozen.
+ continue;
+ }
+
+ if (frozenStates[i] == UID_FROZEN_STATE_FROZEN) {
+ freezeNetworkCallbacksForUid(uid);
+ } else {
+ unfreezeNetworkCallbacksForUid(uid);
+ }
+ }
+ }
+
+ /**
+ * Suspend callbacks for a UID that was just frozen.
+ *
+ * <p>Note that it is not possible for a process to be frozen during a blocking binder call
+ * (see CachedAppOptimizer.freezeBinder), and IConnectivityManager callback registrations are
+ * blocking binder calls, so no callback can be registered while the UID is frozen. This means
+ * it is not necessary to check frozen state on new callback registrations, and calling this
+ * method when a UID is newly frozen is sufficient.
+ *
+ * <p>If it ever becomes possible for a process to be frozen during a blocking binder call,
+ * ConnectivityService will need to handle freezing callbacks that reach ConnectivityService
+ * after the app was frozen when being registered.
+ */
+ private void freezeNetworkCallbacksForUid(int uid) {
+ if (DDBG) Log.d(TAG, "Freezing callbacks for UID " + uid);
+ for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+ if (nri.mUid != uid) continue;
+ // mNetworkRequests can have duplicate values for multilayer requests, but calling
+ // onFrozen multiple times is fine.
+ // If freezeNetworkCallbacksForUid was called multiple times in a raw for a frozen UID
+ // (which would be incorrect), this would also handle it gracefully.
+ nri.onFrozen();
+ }
+ }
+
+ private void unfreezeNetworkCallbacksForUid(int uid) {
+ // This sends all callbacks for one NetworkRequest at a time, which may not be the
+ // same order they were queued in, but different network requests use different
+ // binder objects, so the relative order of their callbacks is not guaranteed.
+ // If callbacks are not queued, callbacks from different binder objects may be
+ // posted on different threads when the process is unfrozen, so even if they were
+ // called a long time apart while the process was frozen, they may still appear in
+ // different order when unfreezing it.
+ for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+ if (nri.mUid != uid) continue;
+ nri.sendQueuedCallbacks();
+ }
+ }
+
private void handleUpdateFirewallDestroySocketReasons(
List<Pair<Integer, Integer>> reasonsList) {
if (!shouldTrackFirewallDestroySocketReasons()) {
@@ -5396,7 +5457,7 @@
// by other networks that are already connected. Perhaps that can be done by
// sending all CALLBACK_LOST messages (for requests, not listens) at the end
// of rematchAllNetworksAndRequests
- notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST);
+ notifyNetworkCallbacks(nai, CALLBACK_LOST);
mKeepaliveTracker.handleStopAllKeepalives(nai, SocketKeepalive.ERROR_INVALID_NETWORK);
mQosCallbackTracker.handleNetworkReleased(nai.network);
@@ -5518,8 +5579,7 @@
// correctly contains null as an upstream.
if (sendCallbacks) {
nri.setSatisfier(null, null);
- notifyNetworkCallbacks(local,
- ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
+ notifyNetworkCallbacks(local, CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
}
}
@@ -5894,8 +5954,7 @@
log("releasing " + nri.mRequests.get(0) + " (timeout)");
}
handleRemoveNetworkRequest(nri);
- callCallbackForRequest(
- nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
+ callCallbackForRequest(nri, null, CALLBACK_UNAVAIL, 0);
}
private void handleReleaseNetworkRequest(@NonNull final NetworkRequest request,
@@ -5911,7 +5970,7 @@
}
handleRemoveNetworkRequest(nri);
if (callOnUnavailable) {
- callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
+ callCallbackForRequest(nri, null, CALLBACK_UNAVAIL, 0);
}
}
@@ -7067,7 +7126,7 @@
// should have its link properties fixed up for PAC proxies.
mProxyTracker.updateDefaultNetworkProxyPortForPAC(nai.linkProperties, nai.network);
if (nai.everConnected()) {
- notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_IP_CHANGED);
+ notifyNetworkCallbacks(nai, CALLBACK_IP_CHANGED);
}
}
@@ -7553,6 +7612,29 @@
// single NetworkRequest in mRequests.
final List<NetworkRequest> mRequests;
+ /**
+ * List of callbacks that are queued for sending later when the requesting app is unfrozen.
+ *
+ * <p>There may typically be hundreds of NetworkRequestInfo, so a memory-efficient structure
+ * (just an int[]) is used to keep queued callbacks. This reduces the number of object
+ * references.
+ *
+ * <p>This is intended to be used with {@link CallbackQueue} which defines the internal
+ * format.
+ */
+ @NonNull
+ private int[] mQueuedCallbacks = new int[0];
+
+ private static final int MATCHED_NETID_NOT_FROZEN = -1;
+
+ /**
+ * If this request was already satisfied by a network when the requesting UID was frozen,
+ * the netId that was matched at that time. Otherwise, NETID_UNSET if no network was
+ * satisfying this request when frozen (including if this is a listen and not a request),
+ * and MATCHED_NETID_NOT_FROZEN if not frozen.
+ */
+ private int mMatchedNetIdWhenFrozen = MATCHED_NETID_NOT_FROZEN;
+
// mSatisfier and mActiveRequest rely on one another therefore set them together.
void setSatisfier(
@Nullable final NetworkAgentInfo satisfier,
@@ -7609,6 +7691,8 @@
// Preference order of this request.
final int mPreferenceOrder;
+ final int mDeclaredMethodsFlags;
+
// In order to preserve the mapping of NetworkRequest-to-callback when apps register
// callbacks using a returned NetworkRequest, the original NetworkRequest needs to be
// maintained for keying off of. This is only a concern when the original nri
@@ -7662,21 +7746,22 @@
mCallbackFlags = NetworkCallback.FLAG_NONE;
mCallingAttributionTag = callingAttributionTag;
mPreferenceOrder = preferenceOrder;
+ mDeclaredMethodsFlags = DECLARED_METHODS_NONE;
}
NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r, @Nullable final Messenger m,
@Nullable final IBinder binder,
@NetworkCallback.Flag int callbackFlags,
- @Nullable String callingAttributionTag) {
+ @Nullable String callingAttributionTag, int declaredMethodsFlags) {
this(asUid, Collections.singletonList(r), r, m, binder, callbackFlags,
- callingAttributionTag);
+ callingAttributionTag, declaredMethodsFlags);
}
NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r,
@NonNull final NetworkRequest requestForCallback, @Nullable final Messenger m,
@Nullable final IBinder binder,
@NetworkCallback.Flag int callbackFlags,
- @Nullable String callingAttributionTag) {
+ @Nullable String callingAttributionTag, int declaredMethodsFlags) {
super();
ensureAllNetworkRequestsHaveType(r);
mRequests = initializeRequests(r);
@@ -7691,6 +7776,7 @@
mCallbackFlags = callbackFlags;
mCallingAttributionTag = callingAttributionTag;
mPreferenceOrder = PREFERENCE_ORDER_INVALID;
+ mDeclaredMethodsFlags = declaredMethodsFlags;
linkDeathRecipient();
}
@@ -7720,6 +7806,8 @@
}
setSatisfier(satisfier, activeRequest);
}
+ mMatchedNetIdWhenFrozen = nri.mMatchedNetIdWhenFrozen;
+ mQueuedCallbacks = nri.mQueuedCallbacks;
mMessenger = nri.mMessenger;
mBinder = nri.mBinder;
mPid = nri.mPid;
@@ -7731,6 +7819,7 @@
mCallingAttributionTag = nri.mCallingAttributionTag;
mUidTrackedForBlockedStatus = nri.mUidTrackedForBlockedStatus;
mPreferenceOrder = PREFERENCE_ORDER_INVALID;
+ mDeclaredMethodsFlags = nri.mDeclaredMethodsFlags;
linkDeathRecipient();
}
@@ -7783,6 +7872,190 @@
}
}
+ /**
+ * Called when this NRI is being frozen.
+ *
+ * <p>Calling this method multiple times when the NRI is frozen is fine. This may happen
+ * if iterating through the NetworkRequest -> NRI map since there are duplicates in the
+ * NRI values for multilayer requests. It may also happen if an app is frozen, killed,
+ * restarted and refrozen since there is no callback sent when processes are killed, but in
+ * that case the callbacks to the killed app do not matter.
+ */
+ void onFrozen() {
+ if (mMatchedNetIdWhenFrozen != MATCHED_NETID_NOT_FROZEN) {
+ // Already frozen
+ return;
+ }
+ if (mSatisfier != null) {
+ mMatchedNetIdWhenFrozen = mSatisfier.network.netId;
+ } else {
+ mMatchedNetIdWhenFrozen = NETID_UNSET;
+ }
+ }
+
+ boolean maybeQueueCallback(@NonNull NetworkAgentInfo nai, int callbackId) {
+ if (mMatchedNetIdWhenFrozen == MATCHED_NETID_NOT_FROZEN) {
+ return false;
+ }
+
+ boolean ignoreThisCallback = false;
+ final int netId = nai.network.netId;
+ final CallbackQueue queue = new CallbackQueue(mQueuedCallbacks);
+ // Based on the new callback, clear previous callbacks that are no longer necessary.
+ // For example, if the network is lost, there is no need to send intermediate callbacks.
+ switch (callbackId) {
+ // PRECHECK is not an API and not very meaningful, do not deliver it for frozen apps
+ // Networks are likely to already be lost when the app is unfrozen, also skip LOSING
+ case CALLBACK_PRECHECK:
+ case CALLBACK_LOSING:
+ ignoreThisCallback = true;
+ break;
+ case CALLBACK_LOST:
+ // All callbacks for this netId before onLost are unnecessary. And onLost itself
+ // is also unnecessary if onAvailable was previously queued for this netId: the
+ // Network just appeared and disappeared while the app was frozen.
+ ignoreThisCallback = queue.hasCallback(netId, CALLBACK_AVAILABLE);
+ queue.removeCallbacksForNetId(netId);
+ break;
+ case CALLBACK_AVAILABLE:
+ if (mSatisfier != null) {
+ // For requests that are satisfied by individual networks (not LISTEN), when
+ // AVAILABLE is received, the request is matching a new Network, so previous
+ // callbacks (for other Networks) are unnecessary.
+ queue.clear();
+ }
+ break;
+ case CALLBACK_SUSPENDED:
+ case CALLBACK_RESUMED:
+ if (queue.hasCallback(netId, CALLBACK_AVAILABLE)) {
+ // AVAILABLE will already send the latest suspended status
+ ignoreThisCallback = true;
+ break;
+ }
+ // If SUSPENDED was queued, just remove it from the queue instead of sending
+ // RESUMED; and vice-versa.
+ final int otherCb = callbackId == CALLBACK_SUSPENDED
+ ? CALLBACK_RESUMED
+ : CALLBACK_SUSPENDED;
+ ignoreThisCallback = queue.removeCallbacks(netId, otherCb);
+ break;
+ case CALLBACK_CAP_CHANGED:
+ case CALLBACK_IP_CHANGED:
+ case CALLBACK_LOCAL_NETWORK_INFO_CHANGED:
+ case CALLBACK_BLK_CHANGED:
+ ignoreThisCallback = queue.hasCallback(netId, CALLBACK_AVAILABLE);
+ break;
+ default:
+ Log.wtf(TAG, "Unexpected callback type: "
+ + ConnectivityManager.getCallbackName(callbackId));
+ return false;
+ }
+
+ if (!ignoreThisCallback) {
+ // For non-listen (matching) callbacks, AVAILABLE can appear in the queue twice in a
+ // row for the same network if the new AVAILABLE suppressed intermediate AVAILABLEs
+ // for other networks. Example:
+ // A is matched, app is frozen, B is matched, A is matched again (removes callbacks
+ // for B), app is unfrozen.
+ // In that case call AVAILABLE sub-callbacks to update state, but not AVAILABLE
+ // itself.
+ if (callbackId == CALLBACK_AVAILABLE && netId == mMatchedNetIdWhenFrozen) {
+ // The queue should have been cleared here, since this is AVAILABLE on a
+ // non-listen callback (mMatchedNetIdWhenFrozen is set).
+ addAvailableSubCallbacks(nai, queue);
+ } else {
+ // When unfreezing, no need to send a callback multiple times for the same netId
+ queue.removeCallbacks(netId, callbackId);
+ // TODO: this code always adds the callback for simplicity. It would save
+ // some CPU/memory if the code instead only added to the queue callbacks where
+ // isCallbackOverridden=true, or which need to be in the queue because they
+ // affect other callbacks that are overridden.
+ queue.addCallback(netId, callbackId);
+ }
+ }
+ // Instead of shrinking the queue, possibly reallocating, the NRI could keep the array
+ // and length in memory for future adds, but this saves memory by avoiding the cost
+ // of an extra member and of unused array length (there are often hundreds of NRIs).
+ mQueuedCallbacks = queue.getMinimizedBackingArray();
+ return true;
+ }
+
+ /**
+ * Called when this NRI is being unfrozen to stop queueing, and send queued callbacks.
+ *
+ * <p>Calling this method multiple times when the NRI is unfrozen (for example iterating
+ * through the NetworkRequest -> NRI map where there are duplicate values for multilayer
+ * requests) is fine.
+ */
+ void sendQueuedCallbacks() {
+ mMatchedNetIdWhenFrozen = MATCHED_NETID_NOT_FROZEN;
+ if (mQueuedCallbacks.length == 0) {
+ return;
+ }
+ new CallbackQueue(mQueuedCallbacks).forEach((netId, callbackId) -> {
+ // For CALLBACK_LOST only, there will not be a NAI for the netId. Build and send the
+ // callback directly.
+ if (callbackId == CALLBACK_LOST) {
+ if (isCallbackOverridden(CALLBACK_LOST)) {
+ final Bundle cbBundle = makeCommonBundleForCallback(this,
+ new Network(netId));
+ callCallbackForRequest(this, CALLBACK_LOST, cbBundle, 0 /* arg1 */);
+ }
+ return; // Next item in forEach
+ }
+
+ // Other callbacks should always have a NAI, because if a Network disconnects
+ // LOST will be called, unless the request is no longer satisfied by that Network in
+ // which case AVAILABLE will have been called for another Network. In both cases
+ // previous callbacks are cleared.
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
+ if (nai == null) {
+ Log.wtf(TAG, "Missing NetworkAgentInfo for net " + netId
+ + " for callback " + callbackId);
+ return; // Next item in forEach
+ }
+
+ final int arg1 =
+ callbackId == CALLBACK_AVAILABLE || callbackId == CALLBACK_BLK_CHANGED
+ ? getBlockedState(nai, mAsUid)
+ : 0;
+ callCallbackForRequest(this, nai, callbackId, arg1);
+ });
+ mQueuedCallbacks = new int[0];
+ }
+
+ boolean isCallbackOverridden(int callbackId) {
+ return !mUseDeclaredMethodsForCallbacksEnabled
+ || (mDeclaredMethodsFlags & (1 << callbackId)) != 0;
+ }
+
+ /**
+ * Queue all callbacks that are called by AVAILABLE, except onAvailable.
+ *
+ * <p>AVAILABLE may call SUSPENDED, CAP_CHANGED, IP_CHANGED, LOCAL_NETWORK_INFO_CHANGED,
+ * and BLK_CHANGED, in this order.
+ */
+ private void addAvailableSubCallbacks(
+ @NonNull NetworkAgentInfo nai, @NonNull CallbackQueue queue) {
+ final boolean callSuspended =
+ !nai.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_SUSPENDED);
+ final boolean callLocalInfoChanged = nai.isLocalNetwork();
+
+ final int cbCount = 3 + (callSuspended ? 1 : 0) + (callLocalInfoChanged ? 1 : 0);
+ // Avoid unnecessary re-allocations by reserving enough space for all callbacks to add.
+ queue.ensureHasCapacity(cbCount);
+ final int netId = nai.network.netId;
+ if (callSuspended) {
+ queue.addCallback(netId, CALLBACK_SUSPENDED);
+ }
+ queue.addCallback(netId, CALLBACK_CAP_CHANGED);
+ queue.addCallback(netId, CALLBACK_IP_CHANGED);
+ if (callLocalInfoChanged) {
+ queue.addCallback(netId, CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
+ }
+ queue.addCallback(netId, CALLBACK_BLK_CHANGED);
+ }
+
boolean hasHigherOrderThan(@NonNull final NetworkRequestInfo target) {
// Compare two preference orders.
return mPreferenceOrder < target.mPreferenceOrder;
@@ -7818,10 +8091,53 @@
+ (mPendingIntent == null ? "" : " to trigger " + mPendingIntent)
+ " callback flags: " + mCallbackFlags
+ " order: " + mPreferenceOrder
- + " isUidTracked: " + mUidTrackedForBlockedStatus;
+ + " isUidTracked: " + mUidTrackedForBlockedStatus
+ + " declaredMethods: " + declaredMethodsFlagsToString(mDeclaredMethodsFlags);
}
}
+ /**
+ * Get a readable String for a bitmask of declared methods.
+ */
+ @VisibleForTesting
+ public static String declaredMethodsFlagsToString(int flags) {
+ if (flags == DECLARED_METHODS_NONE) {
+ return "NONE";
+ }
+ if (flags == DECLARED_METHODS_ALL) {
+ return "ALL";
+ }
+ final StringBuilder sb = new StringBuilder();
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_PRECHECK, "PRECHK", sb);
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_AVAILABLE, "AVAIL", sb);
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_LOSING, "LOSING", sb);
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_LOST, "LOST", sb);
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_UNAVAIL, "UNAVAIL", sb);
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_CAP_CHANGED, "NC", sb);
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_IP_CHANGED, "LP", sb);
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_SUSPENDED, "SUSP", sb);
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_RESUMED, "RESUME", sb);
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_BLK_CHANGED, "BLK", sb);
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_LOCAL_NETWORK_INFO_CHANGED,
+ "LOCALINF", sb);
+ if (flags != 0) {
+ sb.append("|0x").append(Integer.toHexString(flags));
+ }
+ return sb.toString();
+ }
+
+ private static int maybeAppendDeclaredMethod(int declaredMethodsFlags,
+ int callbackId, String callbackName, @NonNull StringBuilder builder) {
+ final int callbackFlag = 1 << callbackId;
+ if ((declaredMethodsFlags & callbackFlag) != 0) {
+ if (builder.length() > 0) {
+ builder.append('|');
+ }
+ builder.append(callbackName);
+ }
+ return declaredMethodsFlags & ~callbackFlag;
+ }
+
// Keep backward compatibility since the ServiceSpecificException is used by
// the API surface, see {@link ConnectivityManager#convertServiceException}.
public static class RequestInfoPerUidCounter extends PerUidCounter {
@@ -7956,7 +8272,21 @@
public NetworkRequest requestNetwork(int asUid, NetworkCapabilities networkCapabilities,
int reqTypeInt, Messenger messenger, int timeoutMs, final IBinder binder,
int legacyType, int callbackFlags, @NonNull String callingPackageName,
- @Nullable String callingAttributionTag) {
+ @Nullable String callingAttributionTag, int declaredMethodsFlag) {
+ if (declaredMethodsFlag == 0) {
+ // This could happen if raw binder calls are used to call the previous overload of
+ // requestNetwork, as missing int arguments in a binder call end up as 0
+ // (Parcel.readInt returns 0 at the end of a parcel). Such raw calls this would be
+ // really unexpected bad behavior from the caller though.
+ // TODO: remove after verifying this does not happen. This could allow enabling the
+ // optimization for callbacks that do not override any method (right now they use
+ // DECLARED_METHODS_ALL), if it is OK to break NetworkCallbacks created using
+ // dexmaker-mockito-inline and either spy() or MockSettings.useConstructor (see
+ // comment in ConnectivityManager which sets the flag to DECLARED_METHODS_ALL).
+ Log.wtf(TAG, "requestNetwork called without declaredMethodsFlag from "
+ + callingPackageName);
+ declaredMethodsFlag = DECLARED_METHODS_ALL;
+ }
if (legacyType != TYPE_NONE && !hasNetworkStackPermission()) {
if (isTargetSdkAtleast(Build.VERSION_CODES.M, mDeps.getCallingUid(),
callingPackageName)) {
@@ -8048,7 +8378,7 @@
nextNetworkRequestId(), reqType);
final NetworkRequestInfo nri = getNriToRegister(
asUid, networkRequest, messenger, binder, callbackFlags,
- callingAttributionTag);
+ callingAttributionTag, declaredMethodsFlag);
if (DBG) log("requestNetwork for " + nri);
trackUidAndRegisterNetworkRequest(EVENT_REGISTER_NETWORK_REQUEST, nri);
if (timeoutMs > 0) {
@@ -8074,7 +8404,7 @@
private NetworkRequestInfo getNriToRegister(final int asUid, @NonNull final NetworkRequest nr,
@Nullable final Messenger msgr, @Nullable final IBinder binder,
@NetworkCallback.Flag int callbackFlags,
- @Nullable String callingAttributionTag) {
+ @Nullable String callingAttributionTag, int declaredMethodsFlags) {
final List<NetworkRequest> requests;
if (NetworkRequest.Type.TRACK_DEFAULT == nr.type) {
requests = copyDefaultNetworkRequestsForUid(
@@ -8083,7 +8413,8 @@
requests = Collections.singletonList(nr);
}
return new NetworkRequestInfo(
- asUid, requests, nr, msgr, binder, callbackFlags, callingAttributionTag);
+ asUid, requests, nr, msgr, binder, callbackFlags, callingAttributionTag,
+ declaredMethodsFlags);
}
private boolean shouldCheckCapabilitiesDeclaration(
@@ -8220,21 +8551,13 @@
// Policy already enforced.
return;
}
- if (mDeps.isAtLeastV()) {
- if (mBpfNetMaps.isUidRestrictedOnMeteredNetworks(uid)) {
- // If UID is restricted, don't allow them to bring up metered APNs.
- networkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
- }
- return;
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- if (mPolicyManager.isUidRestrictedOnMeteredNetworks(uid)) {
- // If UID is restricted, don't allow them to bring up metered APNs.
- networkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
+ final boolean isRestrictedOnMeteredNetworks = mDeps.isAtLeastV()
+ ? mBpfNetMaps.isUidRestrictedOnMeteredNetworks(uid)
+ : BinderUtils.withCleanCallingIdentity(() ->
+ mPolicyManager.isUidRestrictedOnMeteredNetworks(uid));
+ if (isRestrictedOnMeteredNetworks) {
+ // If UID is restricted, don't allow them to bring up metered APNs.
+ networkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
}
}
@@ -8373,7 +8696,13 @@
public NetworkRequest listenForNetwork(NetworkCapabilities networkCapabilities,
Messenger messenger, IBinder binder,
@NetworkCallback.Flag int callbackFlags,
- @NonNull String callingPackageName, @NonNull String callingAttributionTag) {
+ @NonNull String callingPackageName, @NonNull String callingAttributionTag,
+ int declaredMethodsFlag) {
+ if (declaredMethodsFlag == 0) {
+ Log.wtf(TAG, "listenForNetwork called without declaredMethodsFlag from "
+ + callingPackageName);
+ declaredMethodsFlag = DECLARED_METHODS_ALL;
+ }
final int callingUid = mDeps.getCallingUid();
if (!hasWifiNetworkListenPermission(networkCapabilities)) {
enforceAccessPermission();
@@ -8395,7 +8724,7 @@
NetworkRequest.Type.LISTEN);
NetworkRequestInfo nri =
new NetworkRequestInfo(callingUid, networkRequest, messenger, binder, callbackFlags,
- callingAttributionTag);
+ callingAttributionTag, declaredMethodsFlag);
if (VDBG) log("listenForNetwork for " + nri);
trackUidAndRegisterNetworkRequest(EVENT_REGISTER_NETWORK_LISTENER, nri);
@@ -9095,7 +9424,7 @@
}
networkAgent.networkMonitor().notifyLinkPropertiesChanged(
new LinkProperties(newLp, true /* parcelSensitiveFields */));
- notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
+ notifyNetworkCallbacks(networkAgent, CALLBACK_IP_CHANGED);
}
mKeepaliveTracker.handleCheckKeepalivesStillValid(networkAgent);
@@ -9343,8 +9672,10 @@
* interfaces.
* Ingress discard rule is added to the address iff
* 1. The address is not a link local address
- * 2. The address is used by a single VPN interface and not used by any other
- * interfaces even non-VPN ones
+ * 2. The address is used by a single interface of VPN whose VPN type is not TYPE_VPN_LEGACY
+ * or TYPE_VPN_OEM and the address is not used by any other interfaces even non-VPN ones
+ * Ingress discard rule is not be added to TYPE_VPN_LEGACY or TYPE_VPN_OEM VPN since these VPNs
+ * might need to receive packet to VPN address via non-VPN interface.
* This method can be called during network disconnects, when nai has already been removed from
* mNetworkAgentInfos.
*
@@ -9379,7 +9710,10 @@
// for different network.
final Set<Pair<InetAddress, String>> ingressDiscardRules = new ArraySet<>();
for (final NetworkAgentInfo agent : nais) {
- if (!agent.isVPN() || agent.isDestroyed()) {
+ final int vpnType = getVpnType(agent);
+ if (!agent.isVPN() || agent.isDestroyed()
+ || vpnType == VpnManager.TYPE_VPN_LEGACY
+ || vpnType == VpnManager.TYPE_VPN_OEM) {
continue;
}
final LinkProperties agentLp = (nai == agent) ? lp : agent.linkProperties;
@@ -9591,8 +9925,7 @@
if (prevSuspended != suspended) {
// TODO (b/73132094) : remove this call once the few users of onSuspended and
// onResumed have been removed.
- notifyNetworkCallbacks(nai, suspended ? ConnectivityManager.CALLBACK_SUSPENDED
- : ConnectivityManager.CALLBACK_RESUMED);
+ notifyNetworkCallbacks(nai, suspended ? CALLBACK_SUSPENDED : CALLBACK_RESUMED);
}
if (prevSuspended != suspended || prevRoaming != roaming) {
// updateNetworkInfo will mix in the suspended info from the capabilities and
@@ -9679,7 +10012,7 @@
// If the requestable capabilities have changed or the score changed, we can't have been
// called by rematchNetworkAndRequests, so it's safe to start a rematch.
rematchAllNetworksAndRequests();
- notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
+ notifyNetworkCallbacks(nai, CALLBACK_CAP_CHANGED);
}
updateNetworkInfoForRoamingAndSuspended(nai, prevNc, newNc);
@@ -9821,7 +10154,7 @@
// But here there is no new request, so the rematch won't see anything. Send
// callbacks to apps now to tell them about the loss of upstream.
notifyNetworkCallbacks(nai,
- ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
+ CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
return;
}
}
@@ -9837,7 +10170,8 @@
configBuilder.setUpstreamSelector(nr);
final NetworkRequestInfo nri = new NetworkRequestInfo(
nai.creatorUid, nr, null /* messenger */, null /* binder */,
- 0 /* callbackFlags */, null /* attributionTag */);
+ 0 /* callbackFlags */, null /* attributionTag */,
+ DECLARED_METHODS_NONE);
if (null != oldSatisfier) {
// Set the old satisfier in the new NRI so that the rematch will see any changes
nri.setSatisfier(oldSatisfier, nr);
@@ -10126,7 +10460,7 @@
private void sendPendingIntentForRequest(NetworkRequestInfo nri, NetworkAgentInfo networkAgent,
int notificationType) {
- if (notificationType == ConnectivityManager.CALLBACK_AVAILABLE && !nri.mPendingIntentSent) {
+ if (notificationType == CALLBACK_AVAILABLE && !nri.mPendingIntentSent) {
Intent intent = new Intent();
intent.putExtra(ConnectivityManager.EXTRA_NETWORK, networkAgent.network);
// If apps could file multi-layer requests with PendingIntents, they'd need to know
@@ -10196,6 +10530,18 @@
return new LocalNetworkInfo.Builder().setUpstreamNetwork(upstream).build();
}
+ private Bundle makeCommonBundleForCallback(@NonNull final NetworkRequestInfo nri,
+ @Nullable Network network) {
+ final Bundle bundle = new Bundle();
+ // TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects.
+ // TODO: check if defensive copies of data is needed.
+ putParcelable(bundle, nri.getNetworkRequestForCallback());
+ if (network != null) {
+ putParcelable(bundle, network);
+ }
+ return bundle;
+ }
+
// networkAgent is only allowed to be null if notificationType is
// CALLBACK_UNAVAIL. This is because UNAVAIL is about no network being
// available, while all other cases are about some particular network.
@@ -10208,19 +10554,24 @@
// are Type.LISTEN, but should not have NetworkCallbacks invoked.
return;
}
- final Bundle bundle = new Bundle();
- // TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects.
- // TODO: check if defensive copies of data is needed.
- final NetworkRequest nrForCallback = nri.getNetworkRequestForCallback();
- putParcelable(bundle, nrForCallback);
- Message msg = Message.obtain();
- if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL) {
- putParcelable(bundle, networkAgent.network);
+ // Even if a callback ends up not being sent, it may affect other callbacks in the queue, so
+ // queue callbacks before checking the declared methods flags.
+ if (networkAgent != null && nri.maybeQueueCallback(networkAgent, notificationType)) {
+ return;
}
+ if (!nri.isCallbackOverridden(notificationType)) {
+ // No need to send the notification as the recipient method is not overridden
+ return;
+ }
+ final Network bundleNetwork = notificationType == CALLBACK_UNAVAIL
+ ? null
+ : networkAgent.network;
+ final Bundle bundle = makeCommonBundleForCallback(nri, bundleNetwork);
final boolean includeLocationSensitiveInfo =
(nri.mCallbackFlags & NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) != 0;
+ final NetworkRequest nrForCallback = nri.getNetworkRequestForCallback();
switch (notificationType) {
- case ConnectivityManager.CALLBACK_AVAILABLE: {
+ case CALLBACK_AVAILABLE: {
final NetworkCapabilities nc =
createWithLocationInfoSanitizedIfNecessaryWhenParceled(
networkCapabilitiesRestrictedForCallerPermissions(
@@ -10235,15 +10586,9 @@
// method here.
bundle.putParcelable(LocalNetworkInfo.class.getSimpleName(),
localNetworkInfoForNai(networkAgent));
- // For this notification, arg1 contains the blocked status.
- msg.arg1 = arg1;
break;
}
- case ConnectivityManager.CALLBACK_LOSING: {
- msg.arg1 = arg1;
- break;
- }
- case ConnectivityManager.CALLBACK_CAP_CHANGED: {
+ case CALLBACK_CAP_CHANGED: {
// networkAgent can't be null as it has been accessed a few lines above.
final NetworkCapabilities netCap =
networkCapabilitiesRestrictedForCallerPermissions(
@@ -10256,17 +10601,16 @@
nri.mCallingAttributionTag));
break;
}
- case ConnectivityManager.CALLBACK_IP_CHANGED: {
+ case CALLBACK_IP_CHANGED: {
putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions(
networkAgent.linkProperties, nri.mPid, nri.mUid));
break;
}
- case ConnectivityManager.CALLBACK_BLK_CHANGED: {
+ case CALLBACK_BLK_CHANGED: {
maybeLogBlockedStatusChanged(nri, networkAgent.network, arg1);
- msg.arg1 = arg1;
break;
}
- case ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED: {
+ case CALLBACK_LOCAL_NETWORK_INFO_CHANGED: {
if (!networkAgent.isLocalNetwork()) {
Log.wtf(TAG, "Callback for local info for a non-local network");
return;
@@ -10275,17 +10619,26 @@
break;
}
}
+ callCallbackForRequest(nri, notificationType, bundle, arg1);
+ }
+
+ private void callCallbackForRequest(@NonNull final NetworkRequestInfo nri, int notificationType,
+ Bundle bundle, int arg1) {
+ Message msg = Message.obtain();
+ msg.arg1 = arg1;
msg.what = notificationType;
msg.setData(bundle);
try {
if (VDBG) {
String notification = ConnectivityManager.getCallbackName(notificationType);
- log("sending notification " + notification + " for " + nrForCallback);
+ log("sending notification " + notification + " for "
+ + nri.getNetworkRequestForCallback());
}
nri.mMessenger.send(msg);
} catch (RemoteException e) {
// may occur naturally in the race of binder death.
- loge("RemoteException caught trying to send a callback msg for " + nrForCallback);
+ loge("RemoteException caught trying to send a callback msg for "
+ + nri.getNetworkRequestForCallback());
}
}
@@ -10535,7 +10888,7 @@
private void processListenRequests(@NonNull final NetworkAgentInfo nai) {
// For consistency with previous behaviour, send onLost callbacks before onAvailable.
processNewlyLostListenRequests(nai);
- notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
+ notifyNetworkCallbacks(nai, CALLBACK_CAP_CHANGED);
processNewlySatisfiedListenRequests(nai);
}
@@ -10548,7 +10901,7 @@
if (!nr.isListen()) continue;
if (nai.isSatisfyingRequest(nr.requestId) && !nai.satisfies(nr)) {
nai.removeRequest(nr.requestId);
- callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_LOST, 0);
+ callCallbackForRequest(nri, nai, CALLBACK_LOST, 0);
}
}
}
@@ -10880,7 +11233,7 @@
notifyNetworkAvailable(event.mNewNetwork, event.mNetworkRequestInfo);
} else {
callCallbackForRequest(event.mNetworkRequestInfo, event.mOldNetwork,
- ConnectivityManager.CALLBACK_LOST, 0);
+ CALLBACK_LOST, 0);
}
}
@@ -10924,7 +11277,7 @@
if (null != localInfoChangedAgents) {
for (final NetworkAgentInfo nai : localInfoChangedAgents) {
notifyNetworkCallbacks(nai,
- ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
+ CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
}
}
@@ -10967,7 +11320,7 @@
if (Objects.equals(nai.networkCapabilities, newNc)) return;
updateNetworkPermissions(nai, newNc);
nai.getAndSetNetworkCapabilities(newNc);
- notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
+ notifyNetworkCallbacks(nai, CALLBACK_CAP_CHANGED);
}
private void updateLegacyTypeTrackerAndVpnLockdownForRematch(
@@ -11336,7 +11689,7 @@
rematchAllNetworksAndRequests();
// This has to happen after matching the requests, because callbacks are just requests.
- notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
+ notifyNetworkCallbacks(networkAgent, CALLBACK_PRECHECK);
} else if (state == NetworkInfo.State.DISCONNECTED) {
networkAgent.disconnect();
if (networkAgent.isVPN()) {
@@ -11369,22 +11722,18 @@
protected void notifyNetworkAvailable(NetworkAgentInfo nai, NetworkRequestInfo nri) {
mHandler.removeMessages(EVENT_TIMEOUT_NETWORK_REQUEST, nri);
if (nri.mPendingIntent != null) {
- sendPendingIntentForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE);
+ sendPendingIntentForRequest(nri, nai, CALLBACK_AVAILABLE);
// Attempt no subsequent state pushes where intents are involved.
return;
}
- final int blockedReasons = mUidBlockedReasons.get(nri.mAsUid, BLOCKED_REASON_NONE);
- final boolean metered = nai.networkCapabilities.isMetered();
- final boolean vpnBlocked = isUidBlockedByVpn(nri.mAsUid, mVpnBlockedUidRanges);
- callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE,
- getBlockedState(nri.mAsUid, blockedReasons, metered, vpnBlocked));
+ callCallbackForRequest(nri, nai, CALLBACK_AVAILABLE, getBlockedState(nai, nri.mAsUid));
}
// Notify the requests on this NAI that the network is now lingered.
private void notifyNetworkLosing(@NonNull final NetworkAgentInfo nai, final long now) {
final int lingerTime = (int) (nai.getInactivityExpiry() - now);
- notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime);
+ notifyNetworkCallbacks(nai, CALLBACK_LOSING, lingerTime);
}
private int getPermissionBlockedState(final int uid, final int reasons) {
@@ -11408,6 +11757,13 @@
: reasons & ~BLOCKED_REASON_LOCKDOWN_VPN;
}
+ private int getBlockedState(@NonNull NetworkAgentInfo nai, int uid) {
+ final boolean metered = nai.networkCapabilities.isMetered();
+ final boolean vpnBlocked = isUidBlockedByVpn(uid, mVpnBlockedUidRanges);
+ final int blockedReasons = mUidBlockedReasons.get(uid, BLOCKED_REASON_NONE);
+ return getBlockedState(uid, blockedReasons, metered, vpnBlocked);
+ }
+
private void setUidBlockedReasons(int uid, @BlockedReason int blockedReasons) {
if (blockedReasons == BLOCKED_REASON_NONE) {
mUidBlockedReasons.delete(uid);
@@ -11447,7 +11803,7 @@
final int newBlockedState = getBlockedState(
nri.mAsUid, blockedReasons, newMetered, newVpnBlocked);
if (oldBlockedState != newBlockedState) {
- callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED,
+ callCallbackForRequest(nri, nai, CALLBACK_BLK_CHANGED,
newBlockedState);
}
}
@@ -11474,7 +11830,7 @@
NetworkRequest nr = nai.requestAt(i);
NetworkRequestInfo nri = mNetworkRequests.get(nr);
if (nri != null && nri.mAsUid == uid) {
- callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED,
+ callCallbackForRequest(nri, nai, CALLBACK_BLK_CHANGED,
newBlockedState);
}
}
@@ -11832,6 +12188,10 @@
return 0;
}
case "get-package-networking-enabled": {
+ if (!mDeps.isAtLeastT()) {
+ throw new UnsupportedOperationException(
+ "This command is not supported on T-");
+ }
final String packageName = getNextArg();
final int rule = getPackageFirewallRule(
ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3, packageName);
@@ -11861,6 +12221,10 @@
return 0;
}
case "get-background-networking-enabled-for-uid": {
+ if (!mDeps.isAtLeastT()) {
+ throw new UnsupportedOperationException(
+ "This command is not supported on T-");
+ }
final Integer uid = parseIntegerArgument(getNextArg());
if (null == uid) {
onHelp();
@@ -13909,6 +14273,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private int getPackageFirewallRule(final int chain, final String packageName)
throws PackageManager.NameNotFoundException {
final PackageManager pm = mContext.getPackageManager();
@@ -13916,6 +14281,7 @@
return getUidFirewallRule(chain, appId);
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Override
public int getUidFirewallRule(final int chain, final int uid) {
enforceNetworkStackOrSettingsPermission();
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index b1c770b..e808746 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -45,7 +45,6 @@
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.InterfaceParams;
-import com.android.net.module.util.SingleWriterBpfMap;
import com.android.net.module.util.TcUtils;
import com.android.net.module.util.bpf.ClatEgress4Key;
import com.android.net.module.util.bpf.ClatEgress4Value;
@@ -257,7 +256,8 @@
@Nullable
public IBpfMap<ClatIngress6Key, ClatIngress6Value> getBpfIngress6Map() {
try {
- return SingleWriterBpfMap.getSingleton(CLAT_INGRESS6_MAP_PATH,
+ // written from clatd.c
+ return new BpfMap<>(CLAT_INGRESS6_MAP_PATH,
ClatIngress6Key.class, ClatIngress6Value.class);
} catch (ErrnoException e) {
Log.e(TAG, "Cannot create ingress6 map: " + e);
@@ -269,7 +269,8 @@
@Nullable
public IBpfMap<ClatEgress4Key, ClatEgress4Value> getBpfEgress4Map() {
try {
- return SingleWriterBpfMap.getSingleton(CLAT_EGRESS4_MAP_PATH,
+ // written from clatd.c
+ return new BpfMap<>(CLAT_EGRESS4_MAP_PATH,
ClatEgress4Key.class, ClatEgress4Value.class);
} catch (ErrnoException e) {
Log.e(TAG, "Cannot create egress4 map: " + e);
diff --git a/service/src/com/android/server/connectivity/ConnectivityFlags.java b/service/src/com/android/server/connectivity/ConnectivityFlags.java
index 1ee1ed7..df87316 100644
--- a/service/src/com/android/server/connectivity/ConnectivityFlags.java
+++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java
@@ -49,6 +49,9 @@
public static final String USE_DECLARED_METHODS_FOR_CALLBACKS =
"use_declared_methods_for_callbacks";
+ public static final String QUEUE_CALLBACKS_FOR_FROZEN_APPS =
+ "queue_callbacks_for_frozen_apps";
+
private boolean mNoRematchAllRequestsOnRegister;
/**
diff --git a/service/src/com/android/server/connectivity/ConnectivityNativeService.java b/service/src/com/android/server/connectivity/ConnectivityNativeService.java
index cf6127f..917ad4d 100644
--- a/service/src/com/android/server/connectivity/ConnectivityNativeService.java
+++ b/service/src/com/android/server/connectivity/ConnectivityNativeService.java
@@ -23,6 +23,7 @@
import android.os.Binder;
import android.os.Process;
import android.os.ServiceSpecificException;
+import android.os.UserHandle;
import android.system.ErrnoException;
import android.util.Log;
@@ -67,8 +68,8 @@
}
private void enforceBlockPortPermission() {
- final int uid = Binder.getCallingUid();
- if (uid == Process.ROOT_UID || uid == Process.PHONE_UID) return;
+ final int appId = UserHandle.getAppId(Binder.getCallingUid());
+ if (appId == Process.ROOT_UID || appId == Process.PHONE_UID) return;
PermissionUtils.enforceNetworkStackPermission(mContext);
}
diff --git a/service/src/com/android/server/connectivity/NetworkDiagnostics.java b/service/src/com/android/server/connectivity/NetworkDiagnostics.java
index 3db37e5..8a2e72c 100644
--- a/service/src/com/android/server/connectivity/NetworkDiagnostics.java
+++ b/service/src/com/android/server/connectivity/NetworkDiagnostics.java
@@ -567,7 +567,9 @@
@Override
public void close() {
- IoUtils.closeQuietly(mFileDescriptor);
+ if (mFileDescriptor != null) {
+ IoUtils.closeQuietly(mFileDescriptor);
+ }
}
}
@@ -611,6 +613,7 @@
setupSocket(SOCK_DGRAM, mProtocol, TIMEOUT_SEND, TIMEOUT_RECV, 0);
} catch (ErrnoException | IOException e) {
mMeasurement.recordFailure(e.toString());
+ close();
return;
}
mMeasurement.description += " src{" + socketAddressToString(mSocketAddress) + "}";
@@ -695,6 +698,7 @@
NetworkConstants.DNS_SERVER_PORT);
} catch (ErrnoException | IOException e) {
mMeasurement.recordFailure(e.toString());
+ close();
return;
}
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index e2834b0..ed0670d 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -39,12 +39,13 @@
"device/com/android/net/module/util/DeviceConfigUtils.java",
"device/com/android/net/module/util/DomainUtils.java",
"device/com/android/net/module/util/FdEventsReader.java",
+ "device/com/android/net/module/util/FeatureVersions.java",
+ "device/com/android/net/module/util/HandlerUtils.java",
"device/com/android/net/module/util/NetworkMonitorUtils.java",
"device/com/android/net/module/util/PacketReader.java",
"device/com/android/net/module/util/SharedLog.java",
"device/com/android/net/module/util/SocketUtils.java",
- "device/com/android/net/module/util/FeatureVersions.java",
- "device/com/android/net/module/util/HandlerUtils.java",
+ "device/com/android/net/module/util/SyncStateMachine.java",
// This library is used by system modules, for which the system health impact of Kotlin
// has not yet been evaluated. Annotations may need jarjar'ing.
// "src_devicecommon/**/*.kt",
@@ -57,17 +58,13 @@
"//apex_available:platform",
],
visibility: [
- "//frameworks/base/packages/Tethering",
"//packages/modules/Connectivity:__subpackages__",
"//packages/modules/Connectivity/framework:__subpackages__",
- "//frameworks/opt/net/ike",
- "//frameworks/opt/net/wifi/service",
- "//packages/modules/Wifi/service",
- "//frameworks/opt/net/telephony",
"//packages/modules/NetworkStack:__subpackages__",
"//packages/modules/CaptivePortalLogin",
],
static_libs: [
+ "modules-utils-statemachine",
"net-utils-framework-common",
],
libs: [
@@ -280,7 +277,7 @@
"//apex_available:platform",
],
lint: {
- baseline_filename: "lint-baseline.xml",
+ strict_updatability_linting: true,
error_checks: ["NewApi"],
},
}
@@ -343,12 +340,10 @@
],
sdk_version: "module_current",
min_sdk_version: "30",
- static_libs: [
- "modules-utils-build_system",
- ],
libs: [
"framework-annotations-lib",
"framework-connectivity",
+ "modules-utils-build_system",
],
// TODO: remove "apex_available:platform".
apex_available: [
@@ -443,6 +438,7 @@
"device/com/android/net/module/util/SharedLog.java",
"framework/com/android/net/module/util/ByteUtils.java",
"framework/com/android/net/module/util/CollectionUtils.java",
+ "framework/com/android/net/module/util/DnsUtils.java",
"framework/com/android/net/module/util/HexDump.java",
"framework/com/android/net/module/util/LinkPropertiesUtils.java",
],
@@ -454,6 +450,135 @@
visibility: ["//packages/modules/Connectivity/service-t"],
}
+// net-utils-framework-connectivity is only for framework-connectivity.
+java_library {
+ name: "net-utils-framework-connectivity",
+ srcs: [
+ ":net-utils-framework-connectivity-srcs",
+ ],
+ sdk_version: "module_current",
+ min_sdk_version: "30",
+ apex_available: [
+ "com.android.tethering",
+ "//apex_available:platform",
+ ],
+ visibility: [
+ "//packages/modules/Connectivity/framework",
+ ],
+ libs: [
+ "androidx.annotation_annotation",
+ "framework-annotations-lib",
+ "framework-configinfrastructure",
+ "framework-connectivity.stubs.module_lib",
+ ],
+ lint: {
+ strict_updatability_linting: true,
+ error_checks: ["NewApi"],
+ },
+}
+
+java_defaults {
+ name: "net-utils-non-bootclasspath-defaults",
+ sdk_version: "module_current",
+ min_sdk_version: "30",
+ libs: [
+ "androidx.annotation_annotation",
+ "framework-annotations-lib",
+ "framework-configinfrastructure",
+ "framework-connectivity",
+ "framework-connectivity.stubs.module_lib",
+ "framework-connectivity-t.stubs.module_lib",
+ "framework-location.stubs.module_lib",
+ "framework-tethering",
+ "unsupportedappusage",
+ ],
+ static_libs: [
+ "modules-utils-build_system",
+ "modules-utils-statemachine",
+ "net-utils-non-bootclasspath-aidl-java",
+ "netd-client",
+ ],
+ apex_available: [
+ "com.android.tethering",
+ "//apex_available:platform",
+ ],
+ defaults_visibility: [
+ "//visibility:private",
+ ],
+ lint: {
+ strict_updatability_linting: true,
+ error_checks: ["NewApi"],
+ },
+}
+
+// net-utils-service-connectivity is only for service-connectivity.
+java_library {
+ name: "net-utils-service-connectivity",
+ srcs: [
+ ":net-utils-all-srcs",
+ ],
+ exclude_srcs: [
+ ":net-utils-framework-connectivity-srcs",
+ ],
+ libs: [
+ "net-utils-framework-connectivity",
+ ],
+ defaults: ["net-utils-non-bootclasspath-defaults"],
+ jarjar_rules: "jarjar-rules-shared.txt",
+ visibility: [
+ "//packages/modules/Connectivity/service",
+ "//packages/modules/Connectivity/staticlibs/tests/unit",
+ ],
+}
+
+java_library {
+ name: "net-utils-connectivity-apks",
+ srcs: [
+ ":net-utils-all-srcs",
+ ":framework-connectivity-shared-srcs",
+ ],
+ defaults: ["net-utils-non-bootclasspath-defaults"],
+ jarjar_rules: "jarjar-rules-shared.txt",
+ visibility: [
+ "//packages/modules/CaptivePortalLogin:__subpackages__",
+ "//packages/modules/Connectivity/Tethering",
+ ],
+}
+
+aidl_interface {
+ name: "net-utils-non-bootclasspath-aidl",
+ srcs: [
+ ":net-utils-aidl-srcs",
+ ],
+ unstable: true,
+ backend: {
+ java: {
+ enabled: true,
+ min_sdk_version: "30",
+ apex_available: [
+ "com.android.tethering",
+ "com.android.wifi",
+ "//apex_available:platform",
+ ],
+ },
+ cpp: {
+ enabled: false,
+ },
+ ndk: {
+ enabled: false,
+ },
+ rust: {
+ enabled: false,
+ },
+ },
+ include_dirs: [
+ "packages/modules/Connectivity/framework/aidl-export",
+ ],
+ visibility: [
+ "//system/tools/aidl/build",
+ ],
+}
+
// Use a filegroup and not a library for telephony sources, as framework-annotations cannot be
// included either (some annotations would be duplicated on the bootclasspath).
filegroup {
@@ -471,8 +596,7 @@
],
}
-// Use a filegroup and not a library for wifi sources, as this needs corresponding jar-jar
-// rules on the wifi side.
+// Filegroup to build lib used by Wifi framework
// Any class here *must* have a corresponding jarjar rule in the wifi build rules.
filegroup {
name: "net-utils-framework-wifi-common-srcs",
@@ -484,24 +608,78 @@
"framework/com/android/net/module/util/NetUtils.java",
],
path: "framework",
+ visibility: ["//visibility:private"],
+}
+
+// Use a file group containing classes necessary for framework-connectivity. The file group should
+// be as small as possible because because the classes end up in the bootclasspath and R8 is not
+// used to remove unused classes.
+filegroup {
+ name: "net-utils-framework-connectivity-srcs",
+ srcs: [
+ "device/com/android/net/module/util/BpfBitmap.java",
+ "device/com/android/net/module/util/BpfDump.java",
+ "device/com/android/net/module/util/BpfMap.java",
+ "device/com/android/net/module/util/BpfUtils.java",
+ "device/com/android/net/module/util/IBpfMap.java",
+ "device/com/android/net/module/util/JniUtil.java",
+ "device/com/android/net/module/util/SingleWriterBpfMap.java",
+ "device/com/android/net/module/util/Struct.java",
+ "device/com/android/net/module/util/TcUtils.java",
+ "framework/com/android/net/module/util/HexDump.java",
+ ],
+ visibility: ["//visibility:private"],
+}
+
+filegroup {
+ name: "net-utils-all-srcs",
+ srcs: [
+ "device/**/*.java",
+ ":net-utils-framework-common-srcs",
+ ],
+ visibility: ["//visibility:private"],
+}
+
+filegroup {
+ name: "net-utils-aidl-srcs",
+ srcs: [
+ "device/**/*.aidl",
+ ],
+ path: "device",
+ visibility: ["//visibility:private"],
+}
+
+java_library {
+ name: "net-utils-service-wifi",
+ srcs: [
+ ":net-utils-all-srcs",
+ ],
+ exclude_srcs: [":net-utils-framework-wifi-common-srcs"],
+ libs: [
+ "net-utils-framework-wifi",
+ ],
+ defaults: ["net-utils-non-bootclasspath-defaults"],
+
visibility: [
- "//frameworks/base",
+ "//packages/modules/Wifi/service",
+ ],
+ apex_available: [
+ "com.android.wifi",
],
}
-// Use a filegroup and not a library for wifi sources, as this needs corresponding jar-jar
-// rules on the wifi side.
-// Any class here *must* have a corresponding jarjar rule in the wifi build rules.
-filegroup {
- name: "net-utils-wifi-service-common-srcs",
- srcs: [
- "device/android/net/NetworkFactory.java",
- "device/android/net/NetworkFactoryImpl.java",
- "device/android/net/NetworkFactoryLegacyImpl.java",
- "device/android/net/NetworkFactoryShim.java",
+java_library {
+ name: "net-utils-framework-wifi",
+ sdk_version: "module_current",
+ min_sdk_version: "30",
+ srcs: [":net-utils-framework-wifi-common-srcs"],
+ libs: [
+ "framework-annotations-lib",
+ "framework-connectivity.stubs.module_lib",
+ "unsupportedappusage",
],
visibility: [
- "//frameworks/opt/net/wifi/service",
- "//packages/modules/Wifi/service",
+ "//packages/modules/Wifi/framework",
],
+ apex_available: ["com.android.wifi"],
}
diff --git a/staticlibs/device/com/android/net/module/util/GrowingIntArray.java b/staticlibs/device/com/android/net/module/util/GrowingIntArray.java
new file mode 100644
index 0000000..d47738b
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/GrowingIntArray.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2024 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.net.module.util;
+
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+import java.util.StringJoiner;
+import java.util.function.IntConsumer;
+import java.util.function.IntPredicate;
+
+/**
+ * A growing array of primitive ints.
+ *
+ * <p>This is similar to ArrayList<Integer>, but avoids the cost of boxing (each Integer costs
+ * 16 bytes) and creation / garbage collection of individual Integer objects.
+ *
+ * <p>This class does not use any heuristic for growing capacity, so every call to
+ * {@link #add(int)} may reallocate the backing array. Callers should use
+ * {@link #ensureHasCapacity(int)} to minimize this behavior when they plan to add several values.
+ */
+public class GrowingIntArray {
+ private int[] mValues;
+ private int mLength;
+
+ /**
+ * Create an empty GrowingIntArray with the given capacity.
+ */
+ public GrowingIntArray(int initialCapacity) {
+ mValues = new int[initialCapacity];
+ mLength = 0;
+ }
+
+ /**
+ * Create a GrowingIntArray with an initial array of values.
+ *
+ * <p>The array will be used as-is and may be modified, so callers must stop using it after
+ * calling this constructor.
+ */
+ protected GrowingIntArray(int[] initialValues) {
+ mValues = initialValues;
+ mLength = initialValues.length;
+ }
+
+ /**
+ * Add a value to the array.
+ */
+ public void add(int value) {
+ ensureHasCapacity(1);
+ mValues[mLength] = value;
+ mLength++;
+ }
+
+ /**
+ * Get the current number of values in the array.
+ */
+ public int length() {
+ return mLength;
+ }
+
+ /**
+ * Get the value at a given index.
+ *
+ * @throws ArrayIndexOutOfBoundsException if the index is out of bounds.
+ */
+ public int get(int index) {
+ if (index < 0 || index >= mLength) {
+ throw new ArrayIndexOutOfBoundsException(index);
+ }
+ return mValues[index];
+ }
+
+ /**
+ * Iterate over all values in the array.
+ */
+ public void forEach(@NonNull IntConsumer consumer) {
+ for (int i = 0; i < mLength; i++) {
+ consumer.accept(mValues[i]);
+ }
+ }
+
+ /**
+ * Remove all values matching a predicate.
+ *
+ * @return true if at least one value was removed.
+ */
+ public boolean removeValues(@NonNull IntPredicate predicate) {
+ int newQueueLength = 0;
+ for (int i = 0; i < mLength; i++) {
+ final int cb = mValues[i];
+ if (!predicate.test(cb)) {
+ mValues[newQueueLength] = cb;
+ newQueueLength++;
+ }
+ }
+ if (mLength != newQueueLength) {
+ mLength = newQueueLength;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Indicates whether the array contains the given value.
+ */
+ public boolean contains(int value) {
+ for (int i = 0; i < mLength; i++) {
+ if (mValues[i] == value) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Remove all values from the array.
+ */
+ public void clear() {
+ mLength = 0;
+ }
+
+ /**
+ * Ensure at least the given number of values can be added to the array without reallocating.
+ *
+ * @param capacity The minimum number of additional values the array must be able to hold.
+ */
+ public void ensureHasCapacity(int capacity) {
+ if (mValues.length >= mLength + capacity) {
+ return;
+ }
+ mValues = Arrays.copyOf(mValues, mLength + capacity);
+ }
+
+ @VisibleForTesting
+ int getBackingArrayLength() {
+ return mValues.length;
+ }
+
+ /**
+ * Shrink the array backing this class to the minimum required length.
+ */
+ public void shrinkToLength() {
+ if (mValues.length != mLength) {
+ mValues = Arrays.copyOf(mValues, mLength);
+ }
+ }
+
+ /**
+ * Get values as array by shrinking the internal array to length and returning it.
+ *
+ * <p>This avoids reallocations if the array is already the correct length, but callers should
+ * stop using this instance of {@link GrowingIntArray} if they use the array returned by this
+ * method.
+ */
+ public int[] getMinimizedBackingArray() {
+ shrinkToLength();
+ return mValues;
+ }
+
+ /**
+ * Get the String representation of an item in the array, for use by {@link #toString()}.
+ */
+ protected String valueToString(int item) {
+ return String.valueOf(item);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ final StringJoiner joiner = new StringJoiner(",", "[", "]");
+ forEach(item -> joiner.add(valueToString(item)));
+ return joiner.toString();
+ }
+}
diff --git a/framework/src/android/net/IRoutingCoordinator.aidl b/staticlibs/device/com/android/net/module/util/IRoutingCoordinator.aidl
similarity index 89%
rename from framework/src/android/net/IRoutingCoordinator.aidl
rename to staticlibs/device/com/android/net/module/util/IRoutingCoordinator.aidl
index cf02ec4..72a4a94 100644
--- a/framework/src/android/net/IRoutingCoordinator.aidl
+++ b/staticlibs/device/com/android/net/module/util/IRoutingCoordinator.aidl
@@ -14,11 +14,15 @@
* limitations under the License.
*/
-package android.net;
+package com.android.net.module.util;
import android.net.RouteInfo;
/** @hide */
+// TODO: b/350630377 - This @Descriptor annotation workaround is to prevent the DESCRIPTOR from
+// being jarjared which changes the DESCRIPTOR and casues "java.lang.SecurityException: Binder
+// invocation to an incorrect interface" when calling the IPC.
+@Descriptor("value=no.jarjar.com.android.net.module.util.IRoutingCoordinator")
interface IRoutingCoordinator {
/**
* Add a route for specific network
diff --git a/staticlibs/device/com/android/net/module/util/PacketBuilder.java b/staticlibs/device/com/android/net/module/util/PacketBuilder.java
index 33e5bfa..a2dbd81 100644
--- a/staticlibs/device/com/android/net/module/util/PacketBuilder.java
+++ b/staticlibs/device/com/android/net/module/util/PacketBuilder.java
@@ -24,10 +24,13 @@
import static com.android.net.module.util.IpUtils.ipChecksum;
import static com.android.net.module.util.IpUtils.tcpChecksum;
import static com.android.net.module.util.IpUtils.udpChecksum;
+import static com.android.net.module.util.NetworkStackConstants.IPPROTO_FRAGMENT;
import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_FRAGMENT_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.IPV6_LEN_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.TCP_CHECKSUM_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.UDP_CHECKSUM_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.UDP_LENGTH_OFFSET;
@@ -37,6 +40,7 @@
import androidx.annotation.NonNull;
import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.FragmentHeader;
import com.android.net.module.util.structs.Ipv4Header;
import com.android.net.module.util.structs.Ipv6Header;
import com.android.net.module.util.structs.TcpHeader;
@@ -47,6 +51,10 @@
import java.net.Inet6Address;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
/**
* The class is used to build a packet.
@@ -210,6 +218,20 @@
}
}
+ private void writeFragmentHeader(ByteBuffer buffer, short nextHeader, int offset,
+ boolean mFlag, int id) throws IOException {
+ if ((offset & 7) != 0) {
+ throw new IOException("Invalid offset value, must be multiple of 8");
+ }
+ final FragmentHeader fragmentHeader = new FragmentHeader(nextHeader,
+ offset | (mFlag ? 1 : 0), id);
+ try {
+ fragmentHeader.writeToByteBuffer(buffer);
+ } catch (IllegalArgumentException | BufferOverflowException e) {
+ throw new IOException("Error writing to buffer: ", e);
+ }
+ }
+
/**
* Finalize the packet.
*
@@ -219,9 +241,31 @@
*/
@NonNull
public ByteBuffer finalizePacket() throws IOException {
+ // If the packet is finalized with L2 mtu greater than or equal to its current size, it will
+ // either return a List of size 1 or throw an IOException if something goes wrong.
+ return finalizePacket(mBuffer.position()).get(0);
+ }
+
+ /**
+ * Finalizes the packet with specified link MTU.
+ *
+ * Call after writing L4 header (no payload) or L4 payload to the buffer used by the builder.
+ * L3 header length, L3 header checksum and L4 header checksum are calculated and written back
+ * after finalization.
+ *
+ * @param l2mtu the maximum size, in bytes, of each individual packet. If the packet size
+ * exceeds the l2mtu, it will be fragmented into smaller packets.
+ * @return a list of packet(s), each containing a portion of the original L3 payload.
+ */
+ @NonNull
+ public List<ByteBuffer> finalizePacket(int l2mtu) throws IOException {
// [1] Finalize IPv4 or IPv6 header.
int ipHeaderOffset = INVALID_OFFSET;
if (mIpv4HeaderOffset != INVALID_OFFSET) {
+ if (mBuffer.position() > l2mtu) {
+ throw new IOException("IPv4 fragmentation is not supported");
+ }
+
ipHeaderOffset = mIpv4HeaderOffset;
// Populate the IPv4 totalLength field.
@@ -243,12 +287,15 @@
}
// [2] Finalize TCP or UDP header.
+ final int ipPayloadOffset;
if (mTcpHeaderOffset != INVALID_OFFSET) {
+ ipPayloadOffset = mTcpHeaderOffset;
// Populate the TCP header checksum field.
mBuffer.putShort(mTcpHeaderOffset + TCP_CHECKSUM_OFFSET, tcpChecksum(mBuffer,
ipHeaderOffset /* ipOffset */, mTcpHeaderOffset /* transportOffset */,
mBuffer.position() - mTcpHeaderOffset /* transportLen */));
} else if (mUdpHeaderOffset != INVALID_OFFSET) {
+ ipPayloadOffset = mUdpHeaderOffset;
// Populate the UDP header length field.
mBuffer.putShort(mUdpHeaderOffset + UDP_LENGTH_OFFSET,
(short) (mBuffer.position() - mUdpHeaderOffset));
@@ -257,11 +304,81 @@
mBuffer.putShort(mUdpHeaderOffset + UDP_CHECKSUM_OFFSET, udpChecksum(mBuffer,
ipHeaderOffset /* ipOffset */, mUdpHeaderOffset /* transportOffset */));
} else {
- throw new IOException("Packet is missing neither TCP nor UDP header");
+ throw new IOException("Packet has neither TCP nor UDP header");
}
- mBuffer.flip();
- return mBuffer;
+ if (mBuffer.position() <= l2mtu) {
+ mBuffer.flip();
+ return Arrays.asList(mBuffer);
+ }
+
+ // IPv6 Packet is fragmented into multiple smaller packets that would fit within the link
+ // MTU.
+ // Refer to https://tools.ietf.org/html/rfc2460
+ //
+ // original packet:
+ // +------------------+--------------+--------------+--//--+----------+
+ // | Unfragmentable | first | second | | last |
+ // | Part | fragment | fragment | .... | fragment |
+ // +------------------+--------------+--------------+--//--+----------+
+ //
+ // fragment packets:
+ // +------------------+--------+--------------+
+ // | Unfragmentable |Fragment| first |
+ // | Part | Header | fragment |
+ // +------------------+--------+--------------+
+ //
+ // +------------------+--------+--------------+
+ // | Unfragmentable |Fragment| second |
+ // | Part | Header | fragment |
+ // +------------------+--------+--------------+
+ // o
+ // o
+ // o
+ // +------------------+--------+----------+
+ // | Unfragmentable |Fragment| last |
+ // | Part | Header | fragment |
+ // +------------------+--------+----------+
+ final List<ByteBuffer> fragments = new ArrayList<>();
+ final int totalPayloadLen = mBuffer.position() - ipPayloadOffset;
+ final int perPacketPayloadLen = l2mtu - ipPayloadOffset - IPV6_FRAGMENT_HEADER_LEN;
+ final short protocol = (short) Byte.toUnsignedInt(
+ mBuffer.get(mIpv6HeaderOffset + IPV6_PROTOCOL_OFFSET));
+ Random random = new Random();
+ final int id = random.nextInt(Integer.MAX_VALUE);
+ int startOffset = 0;
+ // Copy the packet content to a byte array.
+ byte[] packet = new byte[mBuffer.position()];
+ // The ByteBuffer#get(int index, byte[] dst) method is only available in API level 35 and
+ // above. Here, we use a more primitive approach: reposition the ByteBuffer to the beginning
+ // before copying, then return its position to the end afterward.
+ mBuffer.position(0);
+ mBuffer.get(packet);
+ mBuffer.position(packet.length);
+ while (startOffset < totalPayloadLen) {
+ int copyPayloadLen = Math.min(perPacketPayloadLen, totalPayloadLen - startOffset);
+ // The data portion must be broken into segments aligned with 8-octet boundaries.
+ // Therefore, the payload length should be a multiple of 8 bytes for all fragments
+ // except the last one.
+ // See https://datatracker.ietf.org/doc/html/rfc791 section 3.2
+ if (copyPayloadLen != totalPayloadLen - startOffset) {
+ copyPayloadLen &= ~7;
+ }
+ ByteBuffer fragment = ByteBuffer.allocate(ipPayloadOffset + IPV6_FRAGMENT_HEADER_LEN
+ + copyPayloadLen);
+ fragment.put(packet, 0, ipPayloadOffset);
+ writeFragmentHeader(fragment, protocol, startOffset,
+ startOffset + copyPayloadLen < totalPayloadLen, id);
+ fragment.put(packet, ipPayloadOffset + startOffset, copyPayloadLen);
+ fragment.putShort(mIpv6HeaderOffset + IPV6_LEN_OFFSET,
+ (short) (IPV6_FRAGMENT_HEADER_LEN + copyPayloadLen));
+ fragment.put(mIpv6HeaderOffset + IPV6_PROTOCOL_OFFSET, (byte) IPPROTO_FRAGMENT);
+ fragment.flip();
+ fragments.add(fragment);
+ startOffset += copyPayloadLen;
+ }
+
+ return fragments;
}
/**
diff --git a/framework/src/android/net/RoutingCoordinatorManager.java b/staticlibs/device/com/android/net/module/util/RoutingCoordinatorManager.java
similarity index 95%
rename from framework/src/android/net/RoutingCoordinatorManager.java
rename to staticlibs/device/com/android/net/module/util/RoutingCoordinatorManager.java
index a9e7eef..02e3643 100644
--- a/framework/src/android/net/RoutingCoordinatorManager.java
+++ b/staticlibs/device/com/android/net/module/util/RoutingCoordinatorManager.java
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package android.net;
+package com.android.net.module.util;
import android.content.Context;
-import android.os.Build;
+import android.net.RouteInfo;
+import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
/**
* A manager class for talking to the routing coordinator service.
@@ -30,15 +30,14 @@
* by the build rules. Do not change build rules to gain access to this class from elsewhere.
* @hide
*/
-@RequiresApi(Build.VERSION_CODES.S)
public class RoutingCoordinatorManager {
@NonNull final Context mContext;
@NonNull final IRoutingCoordinator mService;
public RoutingCoordinatorManager(@NonNull final Context context,
- @NonNull final IRoutingCoordinator service) {
+ @NonNull final IBinder binder) {
mContext = context;
- mService = service;
+ mService = IRoutingCoordinator.Stub.asInterface(binder);
}
/**
diff --git a/service/src/com/android/server/connectivity/RoutingCoordinatorService.java b/staticlibs/device/com/android/net/module/util/RoutingCoordinatorService.java
similarity index 98%
rename from service/src/com/android/server/connectivity/RoutingCoordinatorService.java
rename to staticlibs/device/com/android/net/module/util/RoutingCoordinatorService.java
index 742a2cc..c75b860 100644
--- a/service/src/com/android/server/connectivity/RoutingCoordinatorService.java
+++ b/staticlibs/device/com/android/net/module/util/RoutingCoordinatorService.java
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.server.connectivity;
+package com.android.net.module.util;
import static com.android.net.module.util.NetdUtils.toRouteInfoParcel;
import android.annotation.NonNull;
import android.net.INetd;
-import android.net.IRoutingCoordinator;
+
import android.net.RouteInfo;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
diff --git a/staticlibs/device/com/android/net/module/util/SocketUtils.java b/staticlibs/device/com/android/net/module/util/SocketUtils.java
index 5e6a6c6..51671a6 100644
--- a/staticlibs/device/com/android/net/module/util/SocketUtils.java
+++ b/staticlibs/device/com/android/net/module/util/SocketUtils.java
@@ -19,8 +19,7 @@
import static android.net.util.SocketUtils.closeSocket;
import android.annotation.NonNull;
-import android.annotation.RequiresApi;
-import android.os.Build;
+import android.annotation.SuppressLint;
import android.system.NetlinkSocketAddress;
import java.io.FileDescriptor;
@@ -41,7 +40,11 @@
/**
* Make a socket address to communicate with netlink.
*/
- @NonNull @RequiresApi(Build.VERSION_CODES.S)
+ // NetlinkSocketAddress was CorePlatformApi on R and linter warns this is available on S+.
+ // android.net.util.SocketUtils.makeNetlinkSocketAddress can be used instead, but this method
+ // has been used on R, so suppress the linter and keep as it is.
+ @SuppressLint("NewApi")
+ @NonNull
public static SocketAddress makeNetlinkSocketAddress(int portId, int groupsMask) {
return new NetlinkSocketAddress(portId, groupsMask);
}
diff --git a/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java b/staticlibs/device/com/android/net/module/util/SyncStateMachine.java
similarity index 96%
rename from Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java
rename to staticlibs/device/com/android/net/module/util/SyncStateMachine.java
index a17eb26..da184d3 100644
--- a/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java
+++ b/staticlibs/device/com/android/net/module/util/SyncStateMachine.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.networkstack.tethering.util;
+package com.android.net.module.util;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -165,6 +165,11 @@
* The message is processed sequentially, so calling this method recursively is not permitted.
* In other words, using this method inside State#enter, State#exit, or State#processMessage
* is incorrect and will result in an IllegalStateException.
+ *
+ * @param what is assigned to Message.what
+ * @param arg1 is assigned to Message.arg1
+ * @param arg2 is assigned to Message.arg2
+ * @param obj is assigned to Message.obj
*/
public final void processMessage(int what, int arg1, int arg2, @Nullable Object obj) {
ensureCorrectThread();
@@ -189,6 +194,15 @@
mCurrentlyProcessing = Integer.MIN_VALUE;
}
+ /**
+ * Synchronously process a message and perform state transition.
+ *
+ * @param what is assigned to Message.what.
+ */
+ public final void processMessage(int what) {
+ processMessage(what, 0, 0, null);
+ }
+
private void maybeProcessSelfMessageQueue() {
while (!mSelfMsgQueue.isEmpty()) {
currentStateProcessMessageThenPerformTransitions(mSelfMsgQueue.poll());
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
index df7010e..0c49edc 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
@@ -62,6 +62,17 @@
mInterfaceName = null;
}
+ @VisibleForTesting
+ public RtNetlinkLinkMessage(@NonNull StructNlMsgHdr nlmsghdr,
+ int mtu, @NonNull StructIfinfoMsg ifinfomsg, @NonNull MacAddress hardwareAddress,
+ @NonNull String interfaceName) {
+ super(nlmsghdr);
+ mMtu = mtu;
+ mIfinfomsg = ifinfomsg;
+ mHardwareAddress = hardwareAddress;
+ mInterfaceName = interfaceName;
+ }
+
public int getMtu() {
return mMtu;
}
diff --git a/staticlibs/framework/com/android/net/module/util/DnsUtils.java b/staticlibs/framework/com/android/net/module/util/DnsUtils.java
new file mode 100644
index 0000000..19ffd72
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/DnsUtils.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 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.net.module.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * Dns common utility functions.
+ *
+ * @hide
+ */
+public class DnsUtils {
+
+ private DnsUtils() { }
+
+ /**
+ * Convert the string to DNS case-insensitive uppercase.
+ *
+ * Per rfc6762#page-46, accented characters are not defined to be automatically equivalent to
+ * their unaccented counterparts. So the "DNS uppercase" should be if character is a-z then they
+ * transform into A-Z. Otherwise, they are kept as-is.
+ */
+ public static String toDnsUpperCase(@NonNull String string) {
+ final char[] outChars = new char[string.length()];
+ for (int i = 0; i < string.length(); i++) {
+ outChars[i] = toDnsUpperCase(string.charAt(i));
+ }
+ return new String(outChars);
+ }
+
+ /**
+ * Convert the array of labels to DNS case-insensitive uppercase.
+ */
+ public static String[] toDnsLabelsUpperCase(@NonNull String[] labels) {
+ final String[] outStrings = new String[labels.length];
+ for (int i = 0; i < labels.length; ++i) {
+ outStrings[i] = toDnsUpperCase(labels[i]);
+ }
+ return outStrings;
+ }
+
+ /**
+ * Compare two strings by DNS case-insensitive uppercase.
+ */
+ public static boolean equalsIgnoreDnsCase(@Nullable String a, @Nullable String b) {
+ if (a == null || b == null) {
+ return a == null && b == null;
+ }
+ if (a.length() != b.length()) return false;
+ for (int i = 0; i < a.length(); i++) {
+ if (toDnsUpperCase(a.charAt(i)) != toDnsUpperCase(b.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Compare two set of DNS labels by DNS case-insensitive uppercase.
+ */
+ public static boolean equalsDnsLabelIgnoreDnsCase(@NonNull String[] a, @NonNull String[] b) {
+ if (a == b) {
+ return true;
+ }
+ int length = a.length;
+ if (b.length != length) {
+ return false;
+ }
+ for (int i = 0; i < length; i++) {
+ if (!equalsIgnoreDnsCase(a[i], b[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static char toDnsUpperCase(char a) {
+ return a >= 'a' && a <= 'z' ? (char) (a - ('a' - 'A')) : a;
+ }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index 319d51a..f1ff2e4 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -129,7 +129,9 @@
public static final int IPV6_PROTOCOL_OFFSET = 6;
public static final int IPV6_SRC_ADDR_OFFSET = 8;
public static final int IPV6_DST_ADDR_OFFSET = 24;
+ public static final int IPV6_FRAGMENT_ID_OFFSET = 4;
public static final int IPV6_MIN_MTU = 1280;
+ public static final int IPV6_FRAGMENT_ID_LEN = 4;
public static final int IPV6_FRAGMENT_HEADER_LEN = 8;
public static final int RFC7421_PREFIX_LENGTH = 64;
// getSockOpt() for v6 MTU
@@ -141,6 +143,8 @@
public static final Inet6Address IPV6_ADDR_ALL_HOSTS_MULTICAST =
(Inet6Address) InetAddresses.parseNumericAddress("ff02::3");
+ public static final int IPPROTO_FRAGMENT = 44;
+
/**
* ICMP constants.
*
@@ -256,6 +260,18 @@
public static final short DNS_OVER_TLS_PORT = 853;
/**
+ * Dns query type constants.
+ *
+ * See also:
+ * - https://datatracker.ietf.org/doc/html/rfc1035#section-3.2.2
+ */
+ public static final int TYPE_A = 1;
+ public static final int TYPE_PTR = 12;
+ public static final int TYPE_TXT = 16;
+ public static final int TYPE_AAAA = 28;
+ public static final int TYPE_SRV = 33;
+
+ /**
* IEEE802.11 standard constants.
*
* See also:
diff --git a/staticlibs/framework/com/android/net/module/util/PerUidCounter.java b/staticlibs/framework/com/android/net/module/util/PerUidCounter.java
index 463b0c4..98d91a5 100644
--- a/staticlibs/framework/com/android/net/module/util/PerUidCounter.java
+++ b/staticlibs/framework/com/android/net/module/util/PerUidCounter.java
@@ -87,7 +87,9 @@
}
}
- @VisibleForTesting
+ /**
+ * Get the current counter value for the given uid.
+ */
public synchronized int get(int uid) {
return mUidToCount.get(uid, 0);
}
diff --git a/staticlibs/lint-baseline.xml b/staticlibs/lint-baseline.xml
deleted file mode 100644
index 2ee3a43..0000000
--- a/staticlibs/lint-baseline.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.4.0-alpha04" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha04">
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `makeNetlinkSocketAddress`"
- errorLine1=" Os.bind(fd, makeNetlinkSocketAddress(0, mBindGroups));"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/staticlibs/device/com/android/net/module/util/ip/NetlinkMonitor.java"
- line="111"
- column="25"/>
- </issue>
-
-</issues>
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
index cd51004..4bcd259 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
@@ -20,6 +20,7 @@
#include <android-base/unique_fd.h>
#include <linux/bpf.h>
#include <poll.h>
+#include <sys/epoll.h>
#include <sys/mman.h>
#include <utils/Log.h>
@@ -33,7 +34,7 @@
// BpfRingbufBase contains the non-templated functionality of BPF ring buffers.
class BpfRingbufBase {
public:
- ~BpfRingbufBase() {
+ virtual ~BpfRingbufBase() {
if (mConsumerPos) munmap(mConsumerPos, mConsumerSize);
if (mProducerPos) munmap(mProducerPos, mProducerSize);
mConsumerPos = nullptr;
@@ -139,12 +140,24 @@
static base::Result<std::unique_ptr<BpfRingbuf<Value>>> Create(
const char* path);
+ int epoll_ctl_add(int epfd, struct epoll_event *event) {
+ return epoll_ctl(epfd, EPOLL_CTL_ADD, mRingFd.get(), event);
+ }
+
+ int epoll_ctl_mod(int epfd, struct epoll_event *event) {
+ return epoll_ctl(epfd, EPOLL_CTL_MOD, mRingFd.get(), event);
+ }
+
+ int epoll_ctl_del(int epfd) {
+ return epoll_ctl(epfd, EPOLL_CTL_DEL, mRingFd.get(), NULL);
+ }
+
// Consumes all messages from the ring buffer, passing them to the callback.
// Returns the number of messages consumed or a non-ok result on error. If the
// ring buffer has no pending messages an OK result with count 0 is returned.
base::Result<int> ConsumeAll(const MessageCallback& callback);
- private:
+ protected:
// Empty ctor for use by Create.
BpfRingbuf() : BpfRingbufBase(sizeof(Value)) {}
};
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
index e11a9e2..4ddec8b 100644
--- a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
@@ -117,9 +117,6 @@
unsigned _btf_min_bpfloader_ver SECTION("btf_min_bpfloader_ver") = 39u; \
unsigned _btf_user_min_bpfloader_ver SECTION("btf_user_min_bpfloader_ver") = 0xFFFFFFFFu
-#define DISABLE_ON_MAINLINE_BEFORE_U_QPR3() \
- unsigned _netbpfload_min_ver SECTION("netbpfload_min_ver") = BPFLOADER_MAINLINE_U_QPR3_VERSION;
-
/* flag the resulting bpf .o file as critical to system functionality,
* loading all kernel version appropriate programs in it must succeed
* for bpfloader success
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Log.h b/staticlibs/netd/libnetdutils/include/netdutils/Log.h
index d266cbc..2de5ed7 100644
--- a/staticlibs/netd/libnetdutils/include/netdutils/Log.h
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Log.h
@@ -203,7 +203,7 @@
void record(Level lvl, const std::string& entry);
mutable std::shared_mutex mLock;
- std::deque<const std::string> mEntries; // GUARDED_BY(mLock), when supported
+ std::deque<std::string> mEntries; // GUARDED_BY(mLock), when supported
};
} // namespace netdutils
diff --git a/staticlibs/tests/unit/Android.bp b/staticlibs/tests/unit/Android.bp
index fa466f8..91f94b5 100644
--- a/staticlibs/tests/unit/Android.bp
+++ b/staticlibs/tests/unit/Android.bp
@@ -27,6 +27,7 @@
"net-utils-device-common-ip",
"net-utils-device-common-struct-base",
"net-utils-device-common-wear",
+ "net-utils-service-connectivity",
],
libs: [
"android.test.runner",
@@ -39,6 +40,7 @@
"//packages/modules/NetworkStack/tests/integration",
],
lint: {
+ strict_updatability_linting: true,
test: true,
},
}
@@ -56,4 +58,33 @@
],
jarjar_rules: "jarjar-rules.txt",
test_suites: ["device-tests"],
+ lint: {
+ strict_updatability_linting: true,
+ },
+}
+
+python_test_host {
+ name: "NetworkStaticLibHostPythonTests",
+ srcs: [
+ "host/python/*.py",
+ ],
+ main: "host/python/run_tests.py",
+ libs: [
+ "mobly",
+ "net-tests-utils-host-python-common",
+ ],
+ test_config: "host/python/test_config.xml",
+ test_suites: [
+ "general-tests",
+ ],
+ // MoblyBinaryHostTest doesn't support unit_test.
+ test_options: {
+ unit_test: false,
+ },
+ // Needed for applying VirtualEnv.
+ version: {
+ py3: {
+ embedded_launcher: false,
+ },
+ },
}
diff --git a/staticlibs/tests/unit/host/python/adb_utils_test.py b/staticlibs/tests/unit/host/python/adb_utils_test.py
new file mode 100644
index 0000000..8fcca37
--- /dev/null
+++ b/staticlibs/tests/unit/host/python/adb_utils_test.py
@@ -0,0 +1,122 @@
+# Copyright (C) 2024 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.
+
+from unittest.mock import MagicMock, patch
+from absl.testing import parameterized
+from mobly import asserts
+from mobly import base_test
+from mobly import config_parser
+from net_tests_utils.host.python import adb_utils
+from net_tests_utils.host.python.assert_utils import UnexpectedBehaviorError
+
+
+class TestAdbUtils(base_test.BaseTestClass, parameterized.TestCase):
+
+ def __init__(self, configs: config_parser.TestRunConfig):
+ super().__init__(configs)
+
+ def setup_test(self):
+ self.mock_ad = MagicMock() # Mock Android device object
+ self.mock_ad.log = MagicMock()
+ self.mock_ad.adb.shell.return_value = b"" # Default empty return for shell
+
+ @patch(
+ "net_tests_utils.host.python.adb_utils.expect_dumpsys_state_with_retry"
+ )
+ @patch("net_tests_utils.host.python.adb_utils._set_screen_state")
+ def test_set_doze_mode_enable(
+ self, mock_set_screen_state, mock_expect_dumpsys_state
+ ):
+ adb_utils.set_doze_mode(self.mock_ad, True)
+ mock_set_screen_state.assert_called_once_with(self.mock_ad, False)
+
+ @patch(
+ "net_tests_utils.host.python.adb_utils.expect_dumpsys_state_with_retry"
+ )
+ def test_set_doze_mode_disable(self, mock_expect_dumpsys_state):
+ adb_utils.set_doze_mode(self.mock_ad, False)
+
+ @patch("net_tests_utils.host.python.adb_utils._get_screen_state")
+ def test_set_screen_state_success(self, mock_get_screen_state):
+ mock_get_screen_state.side_effect = [False, True] # Simulate toggle
+ adb_utils._set_screen_state(self.mock_ad, True)
+
+ @patch("net_tests_utils.host.python.adb_utils._get_screen_state")
+ def test_set_screen_state_failure(self, mock_get_screen_state):
+ mock_get_screen_state.return_value = False # State doesn't change
+ with asserts.assert_raises(UnexpectedBehaviorError):
+ adb_utils._set_screen_state(self.mock_ad, True)
+
+ @parameterized.parameters(
+ ("Awake", True),
+ ("Asleep", False),
+ ("Dozing", False),
+ ("SomeOtherState", False),
+ ) # Declare inputs for state_str and expected_result.
+ @patch("net_tests_utils.host.python.adb_utils.get_value_of_key_from_dumpsys")
+ def test_get_screen_state(self, state_str, expected_result, mock_get_value):
+ mock_get_value.return_value = state_str
+ asserts.assert_equal(
+ adb_utils._get_screen_state(self.mock_ad), expected_result
+ )
+
+ def test_get_value_of_key_from_dumpsys(self):
+ self.mock_ad.adb.shell.return_value = (
+ b"mWakefulness=Awake\nmOtherKey=SomeValue"
+ )
+ result = adb_utils.get_value_of_key_from_dumpsys(
+ self.mock_ad, "power", "mWakefulness"
+ )
+ asserts.assert_equal(result, "Awake")
+
+ @parameterized.parameters(
+ (True, ["true"]),
+ (False, ["false"]),
+ (
+ True,
+ ["false", "true"],
+ ), # Expect True, get False which is unexpected, then get True
+ (
+ False,
+ ["true", "false"],
+ ), # Expect False, get True which is unexpected, then get False
+ ) # Declare inputs for expected_state and returned_value
+ @patch("net_tests_utils.host.python.adb_utils.get_value_of_key_from_dumpsys")
+ def test_expect_dumpsys_state_with_retry_success(
+ self, expected_state, returned_value, mock_get_value
+ ):
+ mock_get_value.side_effect = returned_value
+ # Verify the method returns and does not throw.
+ adb_utils.expect_dumpsys_state_with_retry(
+ self.mock_ad, "service", "key", expected_state, 0
+ )
+
+ @patch("net_tests_utils.host.python.adb_utils.get_value_of_key_from_dumpsys")
+ def test_expect_dumpsys_state_with_retry_failure(self, mock_get_value):
+ mock_get_value.return_value = "false"
+ with asserts.assert_raises(UnexpectedBehaviorError):
+ adb_utils.expect_dumpsys_state_with_retry(
+ self.mock_ad, "service", "key", True, 0
+ )
+
+ @patch("net_tests_utils.host.python.adb_utils.get_value_of_key_from_dumpsys")
+ def test_expect_dumpsys_state_with_retry_not_found(self, mock_get_value):
+ # Simulate the get_value_of_key_from_dumpsys cannot find the give key.
+ mock_get_value.return_value = None
+
+ # Expect the function to raise UnexpectedBehaviorError due to the exception
+ with asserts.assert_raises(UnexpectedBehaviorError):
+ adb_utils.expect_dumpsys_state_with_retry(
+ self.mock_ad, "service", "key", True
+ )
diff --git a/staticlibs/tests/unit/host/python/apf_utils_test.py b/staticlibs/tests/unit/host/python/apf_utils_test.py
new file mode 100644
index 0000000..b5a941b
--- /dev/null
+++ b/staticlibs/tests/unit/host/python/apf_utils_test.py
@@ -0,0 +1,178 @@
+# Copyright (C) 2024 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.
+
+from unittest.mock import MagicMock, patch
+from absl.testing import parameterized
+from mobly import asserts
+from mobly import base_test
+from mobly import config_parser
+from mobly.controllers.android_device_lib.adb import AdbError
+from net_tests_utils.host.python.apf_utils import (
+ ApfCapabilities,
+ PatternNotFoundException,
+ UnsupportedOperationException,
+ get_apf_capabilities,
+ get_apf_counter,
+ get_apf_counters_from_dumpsys,
+ get_hardware_address,
+ is_send_raw_packet_downstream_supported,
+ send_raw_packet_downstream,
+)
+from net_tests_utils.host.python.assert_utils import UnexpectedBehaviorError
+
+TEST_IFACE_NAME = "eth0"
+TEST_PACKET_IN_HEX = "AABBCCDDEEFF"
+
+
+class TestApfUtils(base_test.BaseTestClass, parameterized.TestCase):
+
+ def __init__(self, configs: config_parser.TestRunConfig):
+ super().__init__(configs)
+
+ def setup_test(self):
+ self.mock_ad = MagicMock() # Mock Android device object
+
+ @patch("net_tests_utils.host.python.adb_utils.get_dumpsys_for_service")
+ def test_get_apf_counters_from_dumpsys_success(
+ self, mock_get_dumpsys: MagicMock
+ ) -> None:
+ mock_get_dumpsys.return_value = """
+IpClient.wlan0
+ APF packet counters:
+ COUNTER_NAME1: 123
+ COUNTER_NAME2: 456
+"""
+ counters = get_apf_counters_from_dumpsys(self.mock_ad, "wlan0")
+ asserts.assert_equal(counters, {"COUNTER_NAME1": 123, "COUNTER_NAME2": 456})
+
+ @patch("net_tests_utils.host.python.adb_utils.get_dumpsys_for_service")
+ def test_get_apf_counters_from_dumpsys_exceptions(
+ self, mock_get_dumpsys: MagicMock
+ ) -> None:
+ test_cases = [
+ "",
+ "IpClient.wlan0\n",
+ "IpClient.wlan0\n APF packet counters:\n",
+ """
+IpClient.wlan1
+ APF packet counters:
+ COUNTER_NAME1: 123
+ COUNTER_NAME2: 456
+""",
+ ]
+
+ for dumpsys_output in test_cases:
+ mock_get_dumpsys.return_value = dumpsys_output
+ with asserts.assert_raises(PatternNotFoundException):
+ get_apf_counters_from_dumpsys(self.mock_ad, "wlan0")
+
+ @patch("net_tests_utils.host.python.apf_utils.get_apf_counters_from_dumpsys")
+ def test_get_apf_counter(self, mock_get_counters: MagicMock) -> None:
+ iface = "wlan0"
+ mock_get_counters.return_value = {
+ "COUNTER_NAME1": 123,
+ "COUNTER_NAME2": 456,
+ }
+ asserts.assert_equal(
+ get_apf_counter(self.mock_ad, iface, "COUNTER_NAME1"), 123
+ )
+ # Not found
+ asserts.assert_equal(
+ get_apf_counter(self.mock_ad, iface, "COUNTER_NAME3"), 0
+ )
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_get_hardware_address_success(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = """
+46: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq ...
+ link/ether 72:05:77:82:21:e0 brd ff:ff:ff:ff:ff:ff
+"""
+ mac_address = get_hardware_address(self.mock_ad, "wlan0")
+ asserts.assert_equal(mac_address, "72:05:77:82:21:E0")
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_get_hardware_address_not_found(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = "Some output without MAC address"
+ with asserts.assert_raises(PatternNotFoundException):
+ get_hardware_address(self.mock_ad, "wlan0")
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_send_raw_packet_downstream_success(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = "" # Successful command output
+ send_raw_packet_downstream(
+ self.mock_ad, TEST_IFACE_NAME, TEST_PACKET_IN_HEX
+ )
+ mock_adb_shell.assert_called_once_with(
+ self.mock_ad,
+ "cmd network_stack send-raw-packet-downstream"
+ f" {TEST_IFACE_NAME} {TEST_PACKET_IN_HEX}",
+ )
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_send_raw_packet_downstream_failure(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = ( # Unexpected command output
+ "Any Unexpected Output"
+ )
+ with asserts.assert_raises(UnexpectedBehaviorError):
+ send_raw_packet_downstream(
+ self.mock_ad, TEST_IFACE_NAME, TEST_PACKET_IN_HEX
+ )
+ asserts.assert_true(
+ is_send_raw_packet_downstream_supported(self.mock_ad),
+ "Send raw packet should be supported.",
+ )
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_send_raw_packet_downstream_unsupported(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.side_effect = AdbError(
+ cmd="", stdout="Unknown command", stderr="", ret_code=3
+ )
+ with asserts.assert_raises(UnsupportedOperationException):
+ send_raw_packet_downstream(
+ self.mock_ad, TEST_IFACE_NAME, TEST_PACKET_IN_HEX
+ )
+ asserts.assert_false(
+ is_send_raw_packet_downstream_supported(self.mock_ad),
+ "Send raw packet should not be supported.",
+ )
+
+ @parameterized.parameters(
+ ("2,2048,1", ApfCapabilities(2, 2048, 1)), # Valid input
+ ("3,1024,0", ApfCapabilities(3, 1024, 0)), # Valid input
+ ("invalid,output", ApfCapabilities(0, 0, 0)), # Invalid input
+ ("", ApfCapabilities(0, 0, 0)), # Empty input
+ )
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_get_apf_capabilities(
+ self, mock_output, expected_result, mock_adb_shell
+ ):
+ """Tests the get_apf_capabilities function with various inputs and expected results."""
+ # Configure the mock adb_shell to return the specified output
+ mock_adb_shell.return_value = mock_output
+
+ # Call the function under test
+ result = get_apf_capabilities(self.mock_ad, "wlan0")
+
+ # Assert that the result matches the expected result
+ asserts.assert_equal(result, expected_result)
diff --git a/staticlibs/tests/unit/host/python/assert_utils_test.py b/staticlibs/tests/unit/host/python/assert_utils_test.py
new file mode 100644
index 0000000..7a33373
--- /dev/null
+++ b/staticlibs/tests/unit/host/python/assert_utils_test.py
@@ -0,0 +1,94 @@
+# Copyright (C) 2024 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.
+
+from mobly import asserts
+from mobly import base_test
+from net_tests_utils.host.python.assert_utils import UnexpectedBehaviorError, expect_with_retry
+
+
+class TestAssertUtils(base_test.BaseTestClass):
+
+ def test_predicate_succeed(self):
+ """Test when the predicate becomes True within retries."""
+ call_count = 0
+
+ def predicate():
+ nonlocal call_count
+ call_count += 1
+ return call_count > 2 # True on the third call
+
+ expect_with_retry(predicate, max_retries=5, retry_interval_sec=0)
+ asserts.assert_equal(call_count, 3) # Ensure it was called exactly 3 times
+
+ def test_predicate_failed(self):
+ """Test when the predicate never becomes True."""
+
+ with asserts.assert_raises(UnexpectedBehaviorError):
+ expect_with_retry(
+ predicate=lambda: False, max_retries=3, retry_interval_sec=0
+ )
+
+ def test_retry_action_not_called_succeed(self):
+ """Test that the retry_action is not called if the predicate returns true in the first try."""
+ retry_action_called = False
+
+ def retry_action():
+ nonlocal retry_action_called
+ retry_action_called = True
+
+ expect_with_retry(
+ predicate=lambda: True,
+ retry_action=retry_action,
+ max_retries=5,
+ retry_interval_sec=0,
+ )
+ asserts.assert_false(
+ retry_action_called, "retry_action called."
+ ) # Assert retry_action was NOT called
+
+ def test_retry_action_not_called_failed(self):
+ """Test that the retry_action is not called if the max_retries is reached."""
+ retry_action_called = False
+
+ def retry_action():
+ nonlocal retry_action_called
+ retry_action_called = True
+
+ with asserts.assert_raises(UnexpectedBehaviorError):
+ expect_with_retry(
+ predicate=lambda: False,
+ retry_action=retry_action,
+ max_retries=1,
+ retry_interval_sec=0,
+ )
+ asserts.assert_false(
+ retry_action_called, "retry_action called."
+ ) # Assert retry_action was NOT called
+
+ def test_retry_action_called(self):
+ """Test that the retry_action is executed when provided."""
+ retry_action_called = False
+
+ def retry_action():
+ nonlocal retry_action_called
+ retry_action_called = True
+
+ with asserts.assert_raises(UnexpectedBehaviorError):
+ expect_with_retry(
+ predicate=lambda: False,
+ retry_action=retry_action,
+ max_retries=2,
+ retry_interval_sec=0,
+ )
+ asserts.assert_true(retry_action_called, "retry_action not called.")
diff --git a/staticlibs/tests/unit/host/python/run_tests.py b/staticlibs/tests/unit/host/python/run_tests.py
new file mode 100644
index 0000000..fa6a310
--- /dev/null
+++ b/staticlibs/tests/unit/host/python/run_tests.py
@@ -0,0 +1,35 @@
+# Copyright (C) 2024 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.
+
+"""Main entrypoint for all of unittest."""
+
+import sys
+from host.python.adb_utils_test import TestAdbUtils
+from host.python.apf_utils_test import TestApfUtils
+from host.python.assert_utils_test import TestAssertUtils
+from mobly import suite_runner
+
+
+if __name__ == "__main__":
+ # For MoblyBinaryHostTest, this entry point will be called twice:
+ # 1. List tests.
+ # <mobly-par-file-name> -- --list_tests
+ # 2. Run tests.
+ # <mobly-par-file-name> -- --config=<yaml-path> --device_serial=<device-serial> --log_path=<log-path>
+ # Strip the "--" since suite runner doesn't recognize it.
+ sys.argv.pop(1)
+ # TODO: make the tests can be executed without manually list classes.
+ suite_runner.run_suite(
+ [TestAssertUtils, TestAdbUtils, TestApfUtils], sys.argv
+ )
diff --git a/staticlibs/tests/unit/host/python/test_config.xml b/staticlibs/tests/unit/host/python/test_config.xml
new file mode 100644
index 0000000..d3b200a
--- /dev/null
+++ b/staticlibs/tests/unit/host/python/test_config.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+<configuration description="Config for NetworkStaticLibHostPythonTests">
+ <target_preparer class="com.android.tradefed.targetprep.PythonVirtualenvPreparer">
+ <option name="dep-module" value="absl-py" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.mobly.MoblyBinaryHostTest" >
+ <option name="mobly-par-file-name" value="NetworkStaticLibHostPythonTests" />
+ <option name="mobly-test-timeout" value="3m" />
+ </test>
+</configuration>
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DnsUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/DnsUtilsTest.kt
new file mode 100644
index 0000000..7b1f08a
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DnsUtilsTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 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.net.module.util
+
+import com.android.net.module.util.DnsUtils.equalsDnsLabelIgnoreDnsCase
+import com.android.net.module.util.DnsUtils.equalsIgnoreDnsCase
+import com.android.net.module.util.DnsUtils.toDnsLabelsUpperCase
+import com.android.net.module.util.DnsUtils.toDnsUpperCase
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(DevSdkIgnoreRunner::class)
+class DnsUtilsTest {
+ @Test
+ fun testToDnsUpperCase() {
+ assertEquals("TEST", toDnsUpperCase("TEST"))
+ assertEquals("TEST", toDnsUpperCase("TeSt"))
+ assertEquals("TEST", toDnsUpperCase("test"))
+ assertEquals("TÉST", toDnsUpperCase("TÉST"))
+ assertEquals("ţéST", toDnsUpperCase("ţést"))
+ // Unicode characters 0x10000 (𐀀), 0x10001 (𐀁), 0x10041 (𐁁)
+ // Note the last 2 bytes of 0x10041 are identical to 'A', but it should remain unchanged.
+ assertEquals(
+ "TEST: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- ",
+ toDnsUpperCase("Test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- ")
+ )
+ // Also test some characters where the first surrogate is not \ud800
+ assertEquals(
+ "TEST: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
+ "\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<",
+ toDnsUpperCase(
+ "Test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
+ "\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<"
+ )
+ )
+ }
+
+ @Test
+ fun testToDnsLabelsUpperCase() {
+ assertArrayEquals(
+ arrayOf("TEST", "TÉST", "ţéST"),
+ toDnsLabelsUpperCase(arrayOf("TeSt", "TÉST", "ţést"))
+ )
+ }
+
+ @Test
+ fun testEqualsIgnoreDnsCase() {
+ assertTrue(equalsIgnoreDnsCase("TEST", "Test"))
+ assertTrue(equalsIgnoreDnsCase("TEST", "test"))
+ assertTrue(equalsIgnoreDnsCase("test", "TeSt"))
+ assertTrue(equalsIgnoreDnsCase("Tést", "tést"))
+ assertFalse(equalsIgnoreDnsCase("ŢÉST", "ţést"))
+ // Unicode characters 0x10000 (𐀀), 0x10001 (𐀁), 0x10041 (𐁁)
+ // Note the last 2 bytes of 0x10041 are identical to 'A', but it should remain unchanged.
+ assertTrue(equalsIgnoreDnsCase(
+ "test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- ",
+ "Test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- "
+ ))
+ // Also test some characters where the first surrogate is not \ud800
+ assertTrue(equalsIgnoreDnsCase(
+ "test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
+ "\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<",
+ "Test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
+ "\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<"
+ ))
+ }
+
+ @Test
+ fun testEqualsLabelIgnoreDnsCase() {
+ assertTrue(equalsDnsLabelIgnoreDnsCase(arrayOf("TEST", "Test"), arrayOf("test", "test")))
+ assertFalse(equalsDnsLabelIgnoreDnsCase(arrayOf("TEST", "Test"), arrayOf("test")))
+ assertFalse(equalsDnsLabelIgnoreDnsCase(arrayOf("Test"), arrayOf("test", "test")))
+ assertFalse(equalsDnsLabelIgnoreDnsCase(arrayOf("TEST", "Test"), arrayOf("test", "tést")))
+ }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/GrowingIntArrayTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/GrowingIntArrayTest.kt
new file mode 100644
index 0000000..4b740e3
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/GrowingIntArrayTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 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.net.module.util
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import kotlin.test.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class GrowingIntArrayTest {
+ @Test
+ fun testAddAndGet() {
+ val array = GrowingIntArray(1)
+ array.add(-1)
+ array.add(0)
+ array.add(2)
+
+ assertEquals(-1, array.get(0))
+ assertEquals(0, array.get(1))
+ assertEquals(2, array.get(2))
+ assertEquals(3, array.length())
+ }
+
+ @Test
+ fun testForEach() {
+ val array = GrowingIntArray(10)
+ array.add(-1)
+ array.add(0)
+ array.add(2)
+
+ val actual = mutableListOf<Int>()
+ array.forEach { actual.add(it) }
+
+ val expected = listOf(-1, 0, 2)
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun testForEach_EmptyArray() {
+ val array = GrowingIntArray(10)
+ array.forEach {
+ fail("This should not be called")
+ }
+ }
+
+ @Test
+ fun testRemoveValues() {
+ val array = GrowingIntArray(10)
+ array.add(-1)
+ array.add(0)
+ array.add(2)
+
+ array.removeValues { it <= 0 }
+ assertEquals(1, array.length())
+ assertEquals(2, array.get(0))
+ }
+
+ @Test
+ fun testContains() {
+ val array = GrowingIntArray(10)
+ array.add(-1)
+ array.add(2)
+
+ assertTrue(array.contains(-1))
+ assertTrue(array.contains(2))
+
+ assertFalse(array.contains(0))
+ assertFalse(array.contains(3))
+ }
+
+ @Test
+ fun testClear() {
+ val array = GrowingIntArray(10)
+ array.add(-1)
+ array.add(2)
+ array.clear()
+
+ assertEquals(0, array.length())
+ }
+
+ @Test
+ fun testEnsureHasCapacity() {
+ val array = GrowingIntArray(0)
+ array.add(42)
+ array.ensureHasCapacity(2)
+
+ assertEquals(3, array.backingArrayLength)
+ }
+
+ @Test
+ fun testGetMinimizedBackingArray() {
+ val array = GrowingIntArray(10)
+ array.add(-1)
+ array.add(2)
+
+ assertContentEquals(intArrayOf(-1, 2), array.minimizedBackingArray)
+ }
+
+ @Test
+ fun testToString() {
+ assertEquals("[]", GrowingIntArray(10).toString())
+ assertEquals("[1,2,3]", GrowingIntArray(3).apply {
+ add(1)
+ add(2)
+ add(3)
+ }.toString())
+ }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt
index 2785ea9..baadad0 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt
@@ -17,13 +17,13 @@
package com.android.net.module.util
import android.net.NetworkStats
-import android.text.TextUtils
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
-import org.junit.Test
-import org.junit.runner.RunWith
+import com.android.testutils.assertEntryEquals
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
@@ -90,8 +90,6 @@
NetworkStats.ROAMING_NO, NetworkStats.DEFAULT_NETWORK_ALL, 1024, 8, 2048, 12,
0 /* operations */)
- // TODO: Use assertEquals once all downstreams accept null iface in
- // NetworkStats.Entry#equals.
assertEntryEquals(expectedEntry, entry)
}
@@ -121,22 +119,4 @@
doReturn(txPackets).`when`(ret).getTxPackets()
return ret
}
-
- /**
- * Assert that the two {@link NetworkStats.Entry} are equals.
- */
- private fun assertEntryEquals(left: NetworkStats.Entry, right: NetworkStats.Entry) {
- TextUtils.equals(left.iface, right.iface)
- assertEquals(left.uid, right.uid)
- assertEquals(left.set, right.set)
- assertEquals(left.tag, right.tag)
- assertEquals(left.metered, right.metered)
- assertEquals(left.roaming, right.roaming)
- assertEquals(left.defaultNetwork, right.defaultNetwork)
- assertEquals(left.rxBytes, right.rxBytes)
- assertEquals(left.rxPackets, right.rxPackets)
- assertEquals(left.txBytes, right.txBytes)
- assertEquals(left.txPackets, right.txPackets)
- assertEquals(left.operations, right.operations)
- }
}
\ No newline at end of file
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
index e40cd6b..886336c 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
@@ -21,9 +21,13 @@
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_FRAGMENT_ID_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_FRAGMENT_ID_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.TCPHDR_ACK;
import static com.android.net.module.util.NetworkStackConstants.TCP_HEADER_MIN_LEN;
import static com.android.net.module.util.NetworkStackConstants.UDP_HEADER_LEN;
@@ -54,6 +58,8 @@
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -489,10 +495,103 @@
(byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
};
+ private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_NO_FRAG =
+ new byte[] {
+ // packet = Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff", type='IPv6')/
+ // IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80, fl=0x515ca,
+ // hlim=0x40)/UDP(sport=9876, dport=433)/
+ // Raw([i%256 for i in range(0, 500)]);
+ // Ether header
+ (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+ (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+ (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+ (byte) 0x86, (byte) 0xdd,
+ // IPv6 header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x01, (byte) 0xfc, (byte) 0x11, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // UDP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x01, (byte) 0xfc, (byte) 0xd3, (byte) 0x9e,
+ // Data
+ // 500 bytes of repeated 0x00~0xff
+ };
+
+ private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_FRAG1 =
+ new byte[] {
+ // packet = Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff", type='IPv6')/
+ // IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80, fl=0x515ca,
+ // hlim=0x40)/UDP(sport=9876, dport=433)/
+ // Raw([i%256 for i in range(0, 500)]);
+ // packets=fragment6(packet, 400);
+ // Ether header
+ (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+ (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+ (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+ (byte) 0x86, (byte) 0xdd,
+ // IPv6 header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x01, (byte) 0x58, (byte) 0x2c, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // Fragement Header
+ (byte) 0x11, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ // UDP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x01, (byte) 0xfc, (byte) 0xd3, (byte) 0x9e,
+ // Data
+ // 328 bytes of repeated 0x00~0xff, start:0x00 end:0x47
+ };
+
+ private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_FRAG2 =
+ new byte[] {
+ // packet = Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff", type='IPv6')/
+ // IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80, fl=0x515ca,
+ // hlim=0x40)/UDP(sport=9876, dport=433)/
+ // Raw([i%256 for i in range(0, 500)]);
+ // packets=fragment6(packet, 400);
+ // Ether header
+ (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+ (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+ (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+ (byte) 0x86, (byte) 0xdd,
+ // IPv6 header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x00, (byte) 0xb4, (byte) 0x2c, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // Fragement Header
+ (byte) 0x11, (byte) 0x00, (byte) 0x01, (byte) 0x50,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ // Data
+ // 172 bytes of repeated 0x00~0xff, start:0x48 end:0xf3
+ };
+
/**
* Build a packet which has ether header, IP header, TCP/UDP header and data.
* The ethernet header and data are optional. Note that both source mac address and
- * destination mac address are required for ethernet header.
+ * destination mac address are required for ethernet header. The packet will be fragmented into
+ * multiple smaller packets if the packet size exceeds L2 mtu.
*
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Layer 2 header (EthernetHeader) | (optional)
@@ -511,11 +610,12 @@
* @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP}
* currently supported.
* @param payload the payload.
+ * @param l2mtu the Link MTU. It's the upper bound of each packet size. Zero means no limit.
*/
@NonNull
- private ByteBuffer buildPacket(@Nullable final MacAddress srcMac,
+ private List<ByteBuffer> buildPackets(@Nullable final MacAddress srcMac,
@Nullable final MacAddress dstMac, final int l3proto, final int l4proto,
- @Nullable final ByteBuffer payload)
+ @Nullable final ByteBuffer payload, int l2mtu)
throws Exception {
if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) {
fail("Unsupported layer 3 protocol " + l3proto);
@@ -562,7 +662,15 @@
payload.clear();
}
- return packetBuilder.finalizePacket();
+ return packetBuilder.finalizePacket(l2mtu > 0 ? l2mtu : Integer.MAX_VALUE);
+ }
+
+ @NonNull
+ private ByteBuffer buildPacket(@Nullable final MacAddress srcMac,
+ @Nullable final MacAddress dstMac, final int l3proto, final int l4proto,
+ @Nullable final ByteBuffer payload)
+ throws Exception {
+ return buildPackets(srcMac, dstMac, l3proto, l4proto, payload, 0).get(0);
}
/**
@@ -874,6 +982,66 @@
assertArrayEquals(TEST_PACKET_IPV6HDR_UDPHDR_DATA, packet.array());
}
+ private void checkIpv6PacketIgnoreFragmentId(byte[] expected, byte[] actual) {
+ final int offset = ETHER_HEADER_LEN + IPV6_HEADER_LEN + IPV6_FRAGMENT_ID_OFFSET;
+ assertArrayEquals(Arrays.copyOf(expected, offset), Arrays.copyOf(actual, offset));
+ assertArrayEquals(
+ Arrays.copyOfRange(expected, offset + IPV6_FRAGMENT_ID_LEN, expected.length),
+ Arrays.copyOfRange(actual, offset + IPV6_FRAGMENT_ID_LEN, actual.length));
+ }
+
+ @Test
+ public void testBuildPacketIPv6FragmentUdpData() throws Exception {
+ // A UDP packet with 500 bytes payload will be fragmented into two UDP packets each carrying
+ // 328 and 172 bytes of payload if the Link MTU is 400. Note that only the first packet
+ // contains the original UDP header.
+ final int payloadLen = 500;
+ final int payloadLen1 = 328;
+ final int payloadLen2 = 172;
+ final int l2mtu = 400;
+ final byte[] payload = new byte[payloadLen];
+ // Initialize the payload with repeated values from 0x00 to 0xff.
+ for (int i = 0; i < payload.length; i++) {
+ payload[i] = (byte) (i & 0xff);
+ }
+
+ // Verify original UDP packet.
+ final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IPV6, IPPROTO_UDP,
+ ByteBuffer.wrap(payload));
+ final int headerLen = TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_NO_FRAG.length;
+ assertArrayEquals(TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_NO_FRAG,
+ Arrays.copyOf(packet.array(), headerLen));
+ assertArrayEquals(payload,
+ Arrays.copyOfRange(packet.array(), headerLen, headerLen + payloadLen));
+
+ // Verify fragments of UDP packet.
+ final List<ByteBuffer> packets = buildPackets(SRC_MAC, DST_MAC, IPPROTO_IPV6, IPPROTO_UDP,
+ ByteBuffer.wrap(payload), l2mtu);
+ assertEquals(2, packets.size());
+
+ // Verify first fragment.
+ int headerLen1 = TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_FRAG1.length;
+ // (1) Compare packet content up to the UDP header, excluding the fragment ID as it's a
+ // random value.
+ checkIpv6PacketIgnoreFragmentId(TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_FRAG1,
+ Arrays.copyOf(packets.get(0).array(), headerLen1));
+ // (2) Compare UDP payload.
+ assertArrayEquals(Arrays.copyOf(payload, payloadLen1),
+ Arrays.copyOfRange(packets.get(0).array(), headerLen1, headerLen1 + payloadLen1));
+
+ // Verify second fragment (similar to the first one).
+ int headerLen2 = TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_FRAG2.length;
+ checkIpv6PacketIgnoreFragmentId(TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_FRAG2,
+ Arrays.copyOf(packets.get(1).array(), headerLen2));
+ assertArrayEquals(Arrays.copyOfRange(payload, payloadLen1, payloadLen1 + payloadLen2),
+ Arrays.copyOfRange(packets.get(1).array(), headerLen2, headerLen2 + payloadLen2));
+ // Verify that the fragment IDs in the first and second fragments are the same.
+ final int offset = ETHER_HEADER_LEN + IPV6_HEADER_LEN + IPV6_FRAGMENT_ID_OFFSET;
+ assertArrayEquals(
+ Arrays.copyOfRange(packets.get(0).array(), offset, offset + IPV6_FRAGMENT_ID_LEN),
+ Arrays.copyOfRange(packets.get(1).array(), offset, offset + IPV6_FRAGMENT_ID_LEN));
+ }
+
@Test
public void testFinalizePacketWithoutIpv4Header() throws Exception {
final ByteBuffer buffer = PacketBuilder.allocate(false /* hasEther */, IPPROTO_IP,
diff --git a/tests/unit/java/com/android/server/connectivity/RoutingCoordinatorServiceTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/RoutingCoordinatorServiceTest.kt
similarity index 97%
rename from tests/unit/java/com/android/server/connectivity/RoutingCoordinatorServiceTest.kt
rename to staticlibs/tests/unit/src/com/android/net/module/util/RoutingCoordinatorServiceTest.kt
index 4e15d5f..b04561c 100644
--- a/tests/unit/java/com/android/server/connectivity/RoutingCoordinatorServiceTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/RoutingCoordinatorServiceTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.connectivity
+package com.android.net.module.util
import android.net.INetd
import android.os.Build
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/SyncStateMachineTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/SyncStateMachineTest.kt
similarity index 98%
rename from Tethering/tests/unit/src/com/android/networkstack/tethering/util/SyncStateMachineTest.kt
rename to staticlibs/tests/unit/src/com/android/net/module/util/SyncStateMachineTest.kt
index 3a57fdd..d534054 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/SyncStateMachineTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/SyncStateMachineTest.kt
@@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.networkstack.tethering.util
+package com.android.net.module.util
import android.os.Message
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.util.State
-import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo
+import com.android.net.module.util.SyncStateMachine.StateInfo
import java.util.ArrayDeque
import java.util.ArrayList
import kotlin.test.assertFailsWith
@@ -45,7 +45,7 @@
@RunWith(AndroidJUnit4::class)
@SmallTest
-class SynStateMachineTest {
+class SyncStateMachineTest {
private val mState1 = spy(object : TestState(MSG_1) {})
private val mState2 = spy(object : TestState(MSG_2) {})
private val mState3 = spy(object : TestState(MSG_3) {})
diff --git a/staticlibs/tests/unit/src/com/android/testutils/DefaultNetworkRestoreMonitorTest.kt b/staticlibs/tests/unit/src/com/android/testutils/DefaultNetworkRestoreMonitorTest.kt
new file mode 100644
index 0000000..7e508fb
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/testutils/DefaultNetworkRestoreMonitorTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2024 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.testutils
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import org.junit.Test
+import org.junit.runner.Description
+import org.junit.runner.notification.RunListener
+import org.junit.runner.notification.RunNotifier
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.ArgumentMatchers.argThat
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+class DefaultNetworkRestoreMonitorTest {
+ private val restoreDefaultNetworkDesc =
+ Description.createSuiteDescription("RestoreDefaultNetwork")
+ private val testDesc = Description.createTestDescription("testClass", "testMethod")
+ private val wifiCap = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build()
+ private val cellCap = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build()
+ private val cm = mock(ConnectivityManager::class.java)
+ private val pm = mock(PackageManager::class.java).also {
+ doReturn(true).`when`(it).hasSystemFeature(anyString())
+ }
+ private val ctx = mock(Context::class.java).also {
+ doReturn(cm).`when`(it).getSystemService(ConnectivityManager::class.java)
+ doReturn(pm).`when`(it).getPackageManager()
+ }
+ private val notifier = mock(RunNotifier::class.java)
+ private val defaultNetworkMonitor = DefaultNetworkRestoreMonitor(
+ ctx,
+ notifier,
+ timeoutMs = 0
+ )
+
+ private fun getRunListener(): RunListener {
+ val captor = ArgumentCaptor.forClass(RunListener::class.java)
+ verify(notifier).addListener(captor.capture())
+ return captor.value
+ }
+
+ private fun mockDefaultNetworkCapabilities(cap: NetworkCapabilities?) {
+ if (cap == null) {
+ doNothing().`when`(cm).registerDefaultNetworkCallback(any())
+ return
+ }
+ doAnswer {
+ val callback = it.getArgument(0) as NetworkCallback
+ callback.onCapabilitiesChanged(Network(100), cap)
+ }.`when`(cm).registerDefaultNetworkCallback(any())
+ }
+
+ @Test
+ fun testDefaultNetworkRestoreMonitor_defaultNetworkRestored() {
+ mockDefaultNetworkCapabilities(wifiCap)
+ defaultNetworkMonitor.init(mock(ConnectUtil::class.java))
+
+ val listener = getRunListener()
+ listener.testFinished(testDesc)
+
+ defaultNetworkMonitor.reportResultAndCleanUp(restoreDefaultNetworkDesc)
+ val inOrder = inOrder(notifier)
+ inOrder.verify(notifier).fireTestStarted(restoreDefaultNetworkDesc)
+ inOrder.verify(notifier, never()).fireTestFailure(any())
+ inOrder.verify(notifier).fireTestFinished(restoreDefaultNetworkDesc)
+ inOrder.verify(notifier).removeListener(listener)
+ }
+
+ @Test
+ fun testDefaultNetworkRestoreMonitor_testStartWithoutDefaultNetwork() {
+ // There is no default network when the tests start
+ mockDefaultNetworkCapabilities(null)
+ defaultNetworkMonitor.init(mock(ConnectUtil::class.java))
+
+ mockDefaultNetworkCapabilities(wifiCap)
+ val listener = getRunListener()
+ listener.testFinished(testDesc)
+
+ defaultNetworkMonitor.reportResultAndCleanUp(restoreDefaultNetworkDesc)
+ val inOrder = inOrder(notifier)
+ inOrder.verify(notifier).fireTestStarted(restoreDefaultNetworkDesc)
+ // fireTestFailure is called
+ inOrder.verify(notifier).fireTestFailure(any())
+ inOrder.verify(notifier).fireTestFinished(restoreDefaultNetworkDesc)
+ inOrder.verify(notifier).removeListener(listener)
+ }
+
+ @Test
+ fun testDefaultNetworkRestoreMonitor_testEndWithoutDefaultNetwork() {
+ mockDefaultNetworkCapabilities(wifiCap)
+ defaultNetworkMonitor.init(mock(ConnectUtil::class.java))
+
+ // There is no default network after the test
+ mockDefaultNetworkCapabilities(null)
+ val listener = getRunListener()
+ listener.testFinished(testDesc)
+
+ defaultNetworkMonitor.reportResultAndCleanUp(restoreDefaultNetworkDesc)
+ val inOrder = inOrder(notifier)
+ inOrder.verify(notifier).fireTestStarted(restoreDefaultNetworkDesc)
+ // fireTestFailure is called with method name
+ inOrder.verify(
+ notifier
+ ).fireTestFailure(
+ argThat{failure -> failure.exception.message?.contains("testMethod") ?: false}
+ )
+ inOrder.verify(notifier).fireTestFinished(restoreDefaultNetworkDesc)
+ inOrder.verify(notifier).removeListener(listener)
+ }
+
+ @Test
+ fun testDefaultNetworkRestoreMonitor_testChangeDefaultNetwork() {
+ mockDefaultNetworkCapabilities(wifiCap)
+ defaultNetworkMonitor.init(mock(ConnectUtil::class.java))
+
+ // The default network transport types change after the test
+ mockDefaultNetworkCapabilities(cellCap)
+ val listener = getRunListener()
+ listener.testFinished(testDesc)
+
+ defaultNetworkMonitor.reportResultAndCleanUp(restoreDefaultNetworkDesc)
+ val inOrder = inOrder(notifier)
+ inOrder.verify(notifier).fireTestStarted(restoreDefaultNetworkDesc)
+ // fireTestFailure is called with method name
+ inOrder.verify(
+ notifier
+ ).fireTestFailure(
+ argThat{failure -> failure.exception.message?.contains("testMethod") ?: false}
+ )
+ inOrder.verify(notifier).fireTestFinished(restoreDefaultNetworkDesc)
+ inOrder.verify(notifier).removeListener(listener)
+ }
+}
diff --git a/staticlibs/tests/unit/src/com/android/testutils/NetworkStatsUtilsTest.kt b/staticlibs/tests/unit/src/com/android/testutils/NetworkStatsUtilsTest.kt
new file mode 100644
index 0000000..57920fc
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/testutils/NetworkStatsUtilsTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils
+
+import android.net.NetworkStats
+import android.net.NetworkStats.DEFAULT_NETWORK_NO
+import android.net.NetworkStats.METERED_NO
+import android.net.NetworkStats.ROAMING_NO
+import android.net.NetworkStats.SET_DEFAULT
+import android.net.NetworkStats.TAG_NONE
+import android.os.Build
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+private const val TEST_IFACE = "test0"
+private val TEST_IFACE2: String? = null
+private const val TEST_START = 1194220800000L
+
+@RunWith(JUnit4::class)
+class NetworkStatsUtilsTest {
+ // This is a unit test for a test utility that uses R APIs
+ @Rule @JvmField
+ val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.Q)
+
+ @Test
+ fun testOrderInsensitiveEquals() {
+ val testEntry = arrayOf(
+ NetworkStats.Entry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 128L, 8L, 0L, 2L, 20L),
+ NetworkStats.Entry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L)
+ )
+
+ // Verify equals of empty stats regardless of initial capacity.
+ val red = NetworkStats(TEST_START, 0)
+ val blue = NetworkStats(TEST_START, 1)
+ assertTrue(orderInsensitiveEquals(red, blue))
+ assertTrue(orderInsensitiveEquals(blue, red))
+
+ // Verify not equal.
+ red.combineValues(testEntry[1])
+ blue.combineValues(testEntry[0]).combineValues(testEntry[1])
+ assertFalse(orderInsensitiveEquals(red, blue))
+ assertFalse(orderInsensitiveEquals(blue, red))
+
+ // Verify equals even if the order of entries are not the same.
+ red.combineValues(testEntry[0])
+ assertTrue(orderInsensitiveEquals(red, blue))
+ assertTrue(orderInsensitiveEquals(blue, red))
+ }
+}
diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp
index 3843b90..8c71a91 100644
--- a/staticlibs/testutils/Android.bp
+++ b/staticlibs/testutils/Android.bp
@@ -86,8 +86,8 @@
java_test_host {
name: "net-tests-utils-host-common",
srcs: [
- "host/**/*.java",
- "host/**/*.kt",
+ "host/java/**/*.java",
+ "host/java/**/*.kt",
],
libs: ["tradefed"],
test_suites: [
@@ -104,3 +104,11 @@
],
data: [":ConnectivityTestPreparer"],
}
+
+python_library_host {
+ name: "net-tests-utils-host-python-common",
+ srcs: [
+ "host/python/*.py",
+ ],
+ pkg_path: "net_tests_utils",
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/AutoCloseTestInterfaceRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/AutoCloseTestInterfaceRule.kt
new file mode 100644
index 0000000..89de0b3
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/AutoCloseTestInterfaceRule.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 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.testutils
+
+import android.Manifest.permission.MANAGE_TEST_NETWORKS
+import android.content.Context
+import android.net.TestNetworkInterface
+import android.net.TestNetworkManager
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+class AutoCloseTestInterfaceRule(
+ private val context: Context,
+ ) : TestRule {
+ private val tnm = runAsShell(MANAGE_TEST_NETWORKS) {
+ context.getSystemService(TestNetworkManager::class.java)!!
+ }
+ private val ifaces = ArrayList<TestNetworkInterface>()
+
+ fun createTapInterface(): TestNetworkInterface {
+ return runAsShell(MANAGE_TEST_NETWORKS) {
+ tnm.createTapInterface()
+ }.also {
+ ifaces.add(it)
+ }
+ }
+
+ private fun closeAllInterfaces() {
+ // TODO: wait on RTM_DELLINK before proceeding.
+ for (iface in ifaces) {
+ // ParcelFileDescriptor prevents the fd from being double closed.
+ iface.getFileDescriptor().close()
+ }
+ }
+
+ private inner class AutoCloseTestInterfaceRuleStatement(
+ private val base: Statement,
+ private val description: Description
+ ) : Statement() {
+ override fun evaluate() {
+ tryTest {
+ base.evaluate()
+ } cleanup {
+ closeAllInterfaces()
+ }
+ }
+ }
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return AutoCloseTestInterfaceRuleStatement(base, description)
+ }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DefaultNetworkRestoreMonitor.kt b/staticlibs/testutils/devicetests/com/android/testutils/DefaultNetworkRestoreMonitor.kt
new file mode 100644
index 0000000..dd52d0b
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DefaultNetworkRestoreMonitor.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 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.testutils
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import com.android.internal.annotations.VisibleForTesting
+import com.android.net.module.util.BitUtils
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import org.junit.runner.Description
+import org.junit.runner.notification.Failure
+import org.junit.runner.notification.RunListener
+import org.junit.runner.notification.RunNotifier
+
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+class DefaultNetworkRestoreMonitor(
+ ctx: Context,
+ private val notifier: RunNotifier,
+ private val timeoutMs: Long = 30_000
+) {
+ var firstFailure: Exception? = null
+ var initialTransports = 0L
+ val cm = ctx.getSystemService(ConnectivityManager::class.java)!!
+ val pm = ctx.packageManager
+ val listener = object : RunListener() {
+ override fun testFinished(desc: Description) {
+ // Only the first method that does not restore the default network should be blamed.
+ if (firstFailure != null) {
+ return
+ }
+ val cb = TestableNetworkCallback()
+ cm.registerDefaultNetworkCallback(cb)
+ try {
+ cb.eventuallyExpect<RecorderCallback.CallbackEntry.CapabilitiesChanged>(
+ timeoutMs = timeoutMs
+ ) {
+ BitUtils.packBits(it.caps.transportTypes) == initialTransports &&
+ it.caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ }
+ } catch (e: AssertionError) {
+ firstFailure = IllegalStateException(desc.methodName + " does not restore the" +
+ "default network, initialTransports = $initialTransports", e)
+ } finally {
+ cm.unregisterNetworkCallback(cb)
+ }
+ }
+ }
+
+ fun init(connectUtil: ConnectUtil) {
+ // Ensure Wi-Fi and cellular connection before running test to avoid starting test
+ // with unexpected default network.
+ // ConnectivityTestTargetPreparer does the same thing, but it's possible that previous tests
+ // don't enable DefaultNetworkRestoreMonitor and the default network is not restored.
+ // This can be removed if all tests enable DefaultNetworkRestoreMonitor
+ if (pm.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ connectUtil.ensureWifiValidated()
+ }
+ if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ connectUtil.ensureCellularValidated()
+ }
+
+ val capFuture = CompletableFuture<NetworkCapabilities>()
+ val cb = object : ConnectivityManager.NetworkCallback() {
+ override fun onCapabilitiesChanged(
+ network: Network,
+ cap: NetworkCapabilities
+ ) {
+ capFuture.complete(cap)
+ }
+ }
+ cm.registerDefaultNetworkCallback(cb)
+ try {
+ val cap = capFuture.get(10_000, TimeUnit.MILLISECONDS)
+ initialTransports = BitUtils.packBits(cap.transportTypes)
+ } catch (e: Exception) {
+ firstFailure = IllegalStateException(
+ "Failed to get default network status before starting tests", e
+ )
+ } finally {
+ cm.unregisterNetworkCallback(cb)
+ }
+ notifier.addListener(listener)
+ }
+
+ fun reportResultAndCleanUp(desc: Description) {
+ notifier.fireTestStarted(desc)
+ if (firstFailure != null) {
+ notifier.fireTestFailure(
+ Failure(desc, firstFailure)
+ )
+ }
+ notifier.fireTestFinished(desc)
+ notifier.removeListener(listener)
+ }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
index 69fdbf8..a014834 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
@@ -16,6 +16,8 @@
package com.android.testutils
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
@@ -57,6 +59,10 @@
class DevSdkIgnoreRunner(private val klass: Class<*>) : Runner(), Filterable, Sortable {
private val leakMonitorDesc = Description.createTestDescription(klass, "ThreadLeakMonitor")
private val shouldThreadLeakFailTest = klass.isAnnotationPresent(MonitorThreadLeak::class.java)
+ private val restoreDefaultNetworkDesc =
+ Description.createTestDescription(klass, "RestoreDefaultNetwork")
+ private val restoreDefaultNetwork = klass.isAnnotationPresent(RestoreDefaultNetwork::class.java)
+ val ctx = ApplicationProvider.getApplicationContext<Context>()
// Inference correctly infers Runner & Filterable & Sortable for |baseRunner|, but the
// Java bytecode doesn't have a way to express this. Give this type a name by wrapping it.
@@ -71,6 +77,10 @@
// TODO(b/307693729): Remove this annotation and monitor thread leak by default.
annotation class MonitorThreadLeak
+ // Annotation for test classes to indicate the test runner should verify the default network is
+ // restored after each test.
+ annotation class RestoreDefaultNetwork
+
private val baseRunner: RunnerWrapper<*>? = klass.let {
val ignoreAfter = it.getAnnotation(IgnoreAfter::class.java)
val ignoreUpTo = it.getAnnotation(IgnoreUpTo::class.java)
@@ -90,25 +100,10 @@
Modifier.isStatic(it.modifiers) &&
it.isAnnotationPresent(Parameterized.Parameters::class.java) }
- override fun run(notifier: RunNotifier) {
- if (baseRunner == null) {
- // Report a single, skipped placeholder test for this class, as the class is expected to
- // report results when run. In practice runners that apply the Filterable implementation
- // would see a NoTestsRemainException and not call the run method.
- notifier.fireTestIgnored(
- Description.createTestDescription(klass, "skippedClassForDevSdkMismatch"))
- return
- }
- if (!shouldThreadLeakFailTest) {
- baseRunner.run(notifier)
- return
- }
-
- // Dump threads as a baseline to monitor thread leaks.
- val threadCountsBeforeTest = getAllThreadNameCounts()
-
- baseRunner.run(notifier)
-
+ private fun checkThreadLeak(
+ notifier: RunNotifier,
+ threadCountsBeforeTest: Map<String, Int>
+ ) {
notifier.fireTestStarted(leakMonitorDesc)
val threadCountsAfterTest = getAllThreadNameCounts()
// TODO : move CompareOrUpdateResult to its own util instead of LinkProperties.
@@ -122,13 +117,48 @@
val increasedThreads = threadsDiff.updated
.filter { threadCountsBeforeTest[it.key]!! < it.value }
if (threadsDiff.added.isNotEmpty() || increasedThreads.isNotEmpty()) {
- notifier.fireTestFailure(Failure(leakMonitorDesc,
- IllegalStateException("Unexpected thread changes: $threadsDiff")))
+ notifier.fireTestFailure(Failure(
+ leakMonitorDesc,
+ IllegalStateException("Unexpected thread changes: $threadsDiff")
+ ))
}
+ notifier.fireTestFinished(leakMonitorDesc)
+ }
+
+ override fun run(notifier: RunNotifier) {
+ if (baseRunner == null) {
+ // Report a single, skipped placeholder test for this class, as the class is expected to
+ // report results when run. In practice runners that apply the Filterable implementation
+ // would see a NoTestsRemainException and not call the run method.
+ notifier.fireTestIgnored(
+ Description.createTestDescription(klass, "skippedClassForDevSdkMismatch")
+ )
+ return
+ }
+
+ val networkRestoreMonitor = if (restoreDefaultNetwork) {
+ DefaultNetworkRestoreMonitor(ctx, notifier).apply{
+ init(ConnectUtil(ctx))
+ }
+ } else {
+ null
+ }
+ val threadCountsBeforeTest = if (shouldThreadLeakFailTest) {
+ // Dump threads as a baseline to monitor thread leaks.
+ getAllThreadNameCounts()
+ } else {
+ null
+ }
+
+ baseRunner.run(notifier)
+
+ if (threadCountsBeforeTest != null) {
+ checkThreadLeak(notifier, threadCountsBeforeTest)
+ }
+ networkRestoreMonitor?.reportResultAndCleanUp(restoreDefaultNetworkDesc)
// Clears up internal state of all inline mocks.
// TODO: Call clearInlineMocks() at the end of each test.
Mockito.framework().clearInlineMocks()
- notifier.fireTestFinished(leakMonitorDesc)
}
private fun getAllThreadNameCounts(): Map<String, Int> {
@@ -152,6 +182,9 @@
if (shouldThreadLeakFailTest) {
it.addChild(leakMonitorDesc)
}
+ if (restoreDefaultNetwork) {
+ it.addChild(restoreDefaultNetworkDesc)
+ }
}
}
@@ -162,7 +195,14 @@
// When ignoring the tests, a skipped placeholder test is reported, so test count is 1.
if (baseRunner == null) return 1
- return baseRunner.testCount() + if (shouldThreadLeakFailTest) 1 else 0
+ var testCount = baseRunner.testCount()
+ if (shouldThreadLeakFailTest) {
+ testCount += 1
+ }
+ if (restoreDefaultNetwork) {
+ testCount += 1
+ }
+ return testCount
}
@Throws(NoTestsRemainException::class)
diff --git a/tests/cts/net/src/android/net/cts/MdnsTestUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/MdnsTestUtils.kt
similarity index 99%
rename from tests/cts/net/src/android/net/cts/MdnsTestUtils.kt
rename to staticlibs/testutils/devicetests/com/android/testutils/MdnsTestUtils.kt
index 93cec9c..8b88224 100644
--- a/tests/cts/net/src/android/net/cts/MdnsTestUtils.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/MdnsTestUtils.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.net.cts
+package com.android.testutils
import android.net.DnsResolver
import android.net.Network
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt
index 8324b25..f2b14f5 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt
@@ -17,7 +17,14 @@
package com.android.testutils
import android.net.NetworkStats
+import android.text.TextUtils
+import com.android.modules.utils.build.SdkLevel
import kotlin.test.assertTrue
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.Mockito
@JvmOverloads
fun orderInsensitiveEquals(
@@ -26,7 +33,7 @@
compareTime: Boolean = false
): Boolean {
if (leftStats == rightStats) return true
- if (compareTime && leftStats.getElapsedRealtime() != rightStats.getElapsedRealtime()) {
+ if (compareTime && leftStats.elapsedRealtime != rightStats.elapsedRealtime) {
return false
}
@@ -47,12 +54,41 @@
left.metered, left.roaming, left.defaultNetwork, i)
if (j == -1) return false
rightTrimmedEmpty.getValues(j, right)
- if (left != right) return false
+ if (SdkLevel.isAtLeastT()) {
+ if (left != right) return false
+ } else {
+ if (!checkEntryEquals(left, right)) return false
+ }
}
return true
}
/**
+ * Assert that the two {@link NetworkStats.Entry} are equals.
+ */
+fun assertEntryEquals(left: NetworkStats.Entry, right: NetworkStats.Entry) {
+ assertTrue(checkEntryEquals(left, right))
+}
+
+// TODO: Make all callers use NetworkStats.Entry#equals once S- downstreams
+// are no longer supported. Because NetworkStats is mainlined on T+ and
+// NetworkStats.Entry#equals in S- does not support null iface.
+fun checkEntryEquals(left: NetworkStats.Entry, right: NetworkStats.Entry): Boolean {
+ return TextUtils.equals(left.iface, right.iface) &&
+ left.uid == right.uid &&
+ left.set == right.set &&
+ left.tag == right.tag &&
+ left.metered == right.metered &&
+ left.roaming == right.roaming &&
+ left.defaultNetwork == right.defaultNetwork &&
+ left.rxBytes == right.rxBytes &&
+ left.rxPackets == right.rxPackets &&
+ left.txBytes == right.txBytes &&
+ left.txPackets == right.txPackets &&
+ left.operations == right.operations
+}
+
+/**
* Assert that two {@link NetworkStats} are equals, assuming the order of the records are not
* necessarily the same.
*
@@ -66,7 +102,7 @@
compareTime: Boolean = false
) {
assertTrue(orderInsensitiveEquals(expected, actual, compareTime),
- "expected: " + expected + " but was: " + actual)
+ "expected: $expected but was: $actual")
}
/**
@@ -74,5 +110,5 @@
* object.
*/
fun assertParcelingIsLossless(stats: NetworkStats) {
- assertParcelingIsLossless(stats, { a, b -> orderInsensitiveEquals(a, b) })
+ assertParcelingIsLossless(stats) { a, b -> orderInsensitiveEquals(a, b) }
}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
index 66362d4..ae43c15 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
@@ -44,13 +44,18 @@
object ANY_NETWORK : Network(-2)
fun anyNetwork() = ANY_NETWORK
-open class RecorderCallback private constructor(
- private val backingRecord: ArrayTrackRecord<CallbackEntry>
-) : NetworkCallback() {
- public constructor() : this(ArrayTrackRecord())
- protected constructor(src: RecorderCallback?) : this(src?.backingRecord ?: ArrayTrackRecord())
+private val DEFAULT_TAG = RecorderCallback::class.simpleName
+ ?: fail("Could not determine class name")
- private val TAG = this::class.simpleName
+open class RecorderCallback private constructor(
+ private val backingRecord: ArrayTrackRecord<CallbackEntry>,
+ val logTag: String
+) : NetworkCallback() {
+ public constructor(logTag: String = DEFAULT_TAG) : this(ArrayTrackRecord(), logTag)
+ protected constructor(src: RecorderCallback?, logTag: String) : this(
+ src?.backingRecord ?: ArrayTrackRecord(),
+ logTag
+ )
sealed class CallbackEntry {
// To get equals(), hashcode(), componentN() etc for free, the child classes of
@@ -123,7 +128,7 @@
val mark get() = history.mark
override fun onAvailable(network: Network) {
- Log.d(TAG, "onAvailable $network")
+ Log.d(logTag, "onAvailable $network")
history.add(Available(network))
}
@@ -131,22 +136,22 @@
// expect the callbacks not to record this, do not listen to PreCheck here.
override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) {
- Log.d(TAG, "onCapabilitiesChanged $network $caps")
+ Log.d(logTag, "onCapabilitiesChanged $network $caps")
history.add(CapabilitiesChanged(network, caps))
}
override fun onLinkPropertiesChanged(network: Network, lp: LinkProperties) {
- Log.d(TAG, "onLinkPropertiesChanged $network $lp")
+ Log.d(logTag, "onLinkPropertiesChanged $network $lp")
history.add(LinkPropertiesChanged(network, lp))
}
override fun onLocalNetworkInfoChanged(network: Network, info: LocalNetworkInfo) {
- Log.d(TAG, "onLocalNetworkInfoChanged $network $info")
+ Log.d(logTag, "onLocalNetworkInfoChanged $network $info")
history.add(LocalInfoChanged(network, info))
}
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
- Log.d(TAG, "onBlockedStatusChanged $network $blocked")
+ Log.d(logTag, "onBlockedStatusChanged $network $blocked")
history.add(BlockedStatus(network, blocked))
}
@@ -154,27 +159,27 @@
// fun onBlockedStatusChanged(network: Network, blocked: Int) {
// because on S, that needs to be "override fun", and on R, that cannot be "override fun".
override fun onNetworkSuspended(network: Network) {
- Log.d(TAG, "onNetworkSuspended $network $network")
+ Log.d(logTag, "onNetworkSuspended $network $network")
history.add(Suspended(network))
}
override fun onNetworkResumed(network: Network) {
- Log.d(TAG, "$network onNetworkResumed $network")
+ Log.d(logTag, "$network onNetworkResumed $network")
history.add(Resumed(network))
}
override fun onLosing(network: Network, maxMsToLive: Int) {
- Log.d(TAG, "onLosing $network $maxMsToLive")
+ Log.d(logTag, "onLosing $network $maxMsToLive")
history.add(Losing(network, maxMsToLive))
}
override fun onLost(network: Network) {
- Log.d(TAG, "onLost $network")
+ Log.d(logTag, "onLost $network")
history.add(Lost(network))
}
override fun onUnavailable() {
- Log.d(TAG, "onUnavailable")
+ Log.d(logTag, "onUnavailable")
history.add(Unavailable())
}
}
@@ -188,10 +193,11 @@
*/
open class TestableNetworkCallback private constructor(
src: TestableNetworkCallback?,
- val defaultTimeoutMs: Long = DEFAULT_TIMEOUT,
- val defaultNoCallbackTimeoutMs: Long = DEFAULT_NO_CALLBACK_TIMEOUT,
- val waiterFunc: Runnable = NOOP // "() -> Unit" would forbid calling with a void func from Java
-) : RecorderCallback(src) {
+ val defaultTimeoutMs: Long,
+ val defaultNoCallbackTimeoutMs: Long,
+ val waiterFunc: Runnable,
+ logTag: String
+) : RecorderCallback(src, logTag) {
/**
* Construct a testable network callback.
* @param timeoutMs the default timeout for expecting a callback. Default 30 seconds. This
@@ -213,14 +219,16 @@
constructor(
timeoutMs: Long = DEFAULT_TIMEOUT,
noCallbackTimeoutMs: Long = DEFAULT_NO_CALLBACK_TIMEOUT,
- waiterFunc: Runnable = NOOP
- ) : this(null, timeoutMs, noCallbackTimeoutMs, waiterFunc)
+ waiterFunc: Runnable = NOOP, // "() -> Unit" would forbid calling with a void func from Java
+ logTag: String = DEFAULT_TAG
+ ) : this(null, timeoutMs, noCallbackTimeoutMs, waiterFunc, logTag)
fun createLinkedCopy() = TestableNetworkCallback(
this,
defaultTimeoutMs,
defaultNoCallbackTimeoutMs,
- waiterFunc
+ waiterFunc,
+ logTag
)
// The last available network, or null if any network was lost since the last call to
diff --git a/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt b/staticlibs/testutils/host/java/com/android/testutils/ConnectivityTestTargetPreparer.kt
similarity index 100%
rename from staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
rename to staticlibs/testutils/host/java/com/android/testutils/ConnectivityTestTargetPreparer.kt
diff --git a/staticlibs/testutils/host/com/android/testutils/DisableConfigSyncTargetPreparer.kt b/staticlibs/testutils/host/java/com/android/testutils/DisableConfigSyncTargetPreparer.kt
similarity index 100%
rename from staticlibs/testutils/host/com/android/testutils/DisableConfigSyncTargetPreparer.kt
rename to staticlibs/testutils/host/java/com/android/testutils/DisableConfigSyncTargetPreparer.kt
diff --git a/staticlibs/testutils/host/python/adb_utils.py b/staticlibs/testutils/host/python/adb_utils.py
new file mode 100644
index 0000000..13c0646
--- /dev/null
+++ b/staticlibs/testutils/host/python/adb_utils.py
@@ -0,0 +1,118 @@
+# Copyright (C) 2024 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.
+
+import re
+from mobly.controllers import android_device
+from net_tests_utils.host.python import assert_utils
+
+BYTE_DECODE_UTF_8 = "utf-8"
+
+
+def set_doze_mode(ad: android_device.AndroidDevice, enable: bool) -> None:
+ if enable:
+ adb_shell(ad, "cmd battery unplug")
+ expect_dumpsys_state_with_retry(
+ ad, "deviceidle", key="mCharging", expected_state=False
+ )
+ _set_screen_state(ad, False)
+ adb_shell(ad, "dumpsys deviceidle enable deep")
+ expect_dumpsys_state_with_retry(
+ ad, "deviceidle", key="mDeepEnabled", expected_state=True
+ )
+ adb_shell(ad, "dumpsys deviceidle force-idle deep")
+ expect_dumpsys_state_with_retry(
+ ad, "deviceidle", key="mForceIdle", expected_state=True
+ )
+ else:
+ adb_shell(ad, "cmd battery reset")
+ expect_dumpsys_state_with_retry(
+ ad, "deviceidle", key="mCharging", expected_state=True
+ )
+ adb_shell(ad, "dumpsys deviceidle unforce")
+ expect_dumpsys_state_with_retry(
+ ad, "deviceidle", key="mForceIdle", expected_state=False
+ )
+
+
+def _set_screen_state(
+ ad: android_device.AndroidDevice, target_state: bool
+) -> None:
+ assert_utils.expect_with_retry(
+ predicate=lambda: _get_screen_state(ad) == target_state,
+ retry_action=lambda: adb_shell(
+ ad, "input keyevent KEYCODE_POWER"
+ ), # Toggle power key again when retry.
+ )
+
+
+def _get_screen_state(ad: android_device.AndroidDevice) -> bool:
+ return get_value_of_key_from_dumpsys(ad, "power", "mWakefulness") == "Awake"
+
+
+def get_value_of_key_from_dumpsys(
+ ad: android_device.AndroidDevice, service: str, key: str
+) -> str:
+ output = get_dumpsys_for_service(ad, service)
+ # Search for key=value pattern from the dumpsys output.
+ # e.g. mWakefulness=Awake
+ pattern = rf"{key}=(.*)"
+ # Only look for the first occurrence.
+ match = re.search(pattern, output)
+ if match:
+ ad.log.debug(
+ "Getting key-value from dumpsys: " + key + "=" + match.group(1)
+ )
+ return match.group(1)
+ else:
+ return None
+
+
+def expect_dumpsys_state_with_retry(
+ ad: android_device.AndroidDevice,
+ service: str,
+ key: str,
+ expected_state: bool,
+ retry_interval_sec: int = 1,
+) -> None:
+ def predicate():
+ value = get_value_of_key_from_dumpsys(ad, service, key)
+ if value is None:
+ return False
+ return value.lower() == str(expected_state).lower()
+
+ assert_utils.expect_with_retry(
+ predicate=predicate,
+ retry_interval_sec=retry_interval_sec,
+ )
+
+
+def get_dumpsys_for_service(
+ ad: android_device.AndroidDevice, service: str
+) -> str:
+ return adb_shell(ad, "dumpsys " + service)
+
+
+def adb_shell(ad: android_device.AndroidDevice, shell_cmd: str) -> str:
+ """Runs adb shell command.
+
+ Args:
+ ad: Android device object.
+ shell_cmd: string of list of strings, adb shell command.
+
+ Returns:
+ string, replies from adb shell command.
+ """
+ ad.log.debug("Executing adb shell %s", shell_cmd)
+ data = ad.adb.shell(shell_cmd)
+ return data.decode(BYTE_DECODE_UTF_8).strip()
diff --git a/staticlibs/testutils/host/python/apf_test_base.py b/staticlibs/testutils/host/python/apf_test_base.py
new file mode 100644
index 0000000..7203265
--- /dev/null
+++ b/staticlibs/testutils/host/python/apf_test_base.py
@@ -0,0 +1,80 @@
+# Copyright (C) 2024 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.
+
+from mobly import asserts
+from net_tests_utils.host.python import adb_utils, apf_utils, assert_utils, multi_devices_test_base, tether_utils
+from net_tests_utils.host.python.tether_utils import UpstreamType
+
+
+class ApfTestBase(multi_devices_test_base.MultiDevicesTestBase):
+
+ def setup_class(self):
+ super().setup_class()
+
+ # Check test preconditions.
+ tether_utils.assume_hotspot_test_preconditions(
+ self.serverDevice, self.clientDevice, UpstreamType.NONE
+ )
+ asserts.abort_class_if(
+ not apf_utils.is_send_raw_packet_downstream_supported(
+ self.serverDevice
+ ),
+ "NetworkStack is too old to support send raw packet, skip test.",
+ )
+
+ # Fetch device properties and storing them locally for later use.
+ client = self.clientDevice.connectivity_multi_devices_snippet
+ self.server_iface_name, client_network = (
+ tether_utils.setup_hotspot_and_client_for_upstream_type(
+ self.serverDevice, self.clientDevice, UpstreamType.NONE
+ )
+ )
+ self.client_iface_name = client.getInterfaceNameFromNetworkHandle(
+ client_network
+ )
+ self.server_mac_address = apf_utils.get_hardware_address(
+ self.serverDevice, self.server_iface_name
+ )
+
+ # Enable doze mode to activate APF.
+ adb_utils.set_doze_mode(self.clientDevice, True)
+
+ def teardown_class(self):
+ adb_utils.set_doze_mode(self.clientDevice, False)
+ tether_utils.cleanup_tethering_for_upstream_type(
+ self.serverDevice, UpstreamType.NONE
+ )
+
+ def send_packet_and_expect_counter_increased(
+ self, packet: str, counter_name: str
+ ) -> None:
+ count_before_test = apf_utils.get_apf_counter(
+ self.clientDevice,
+ self.client_iface_name,
+ counter_name,
+ )
+ apf_utils.send_raw_packet_downstream(
+ self.serverDevice, self.server_iface_name, packet
+ )
+
+ assert_utils.expect_with_retry(
+ lambda: apf_utils.get_apf_counter(
+ self.clientDevice,
+ self.client_iface_name,
+ counter_name,
+ )
+ > count_before_test
+ )
+
+ # TODO: Verify the packet is not actually received.
diff --git a/staticlibs/testutils/host/python/apf_utils.py b/staticlibs/testutils/host/python/apf_utils.py
new file mode 100644
index 0000000..a3ec6e9
--- /dev/null
+++ b/staticlibs/testutils/host/python/apf_utils.py
@@ -0,0 +1,243 @@
+# Copyright (C) 2024 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.
+
+from dataclasses import dataclass
+import re
+from mobly import asserts
+from mobly.controllers import android_device
+from mobly.controllers.android_device_lib.adb import AdbError
+from net_tests_utils.host.python import adb_utils, assert_utils
+
+
+class PatternNotFoundException(Exception):
+ """Raised when the given pattern cannot be found."""
+
+
+class UnsupportedOperationException(Exception):
+ pass
+
+
+def get_apf_counter(
+ ad: android_device.AndroidDevice, iface: str, counter_name: str
+) -> int:
+ counters = get_apf_counters_from_dumpsys(ad, iface)
+ return counters.get(counter_name, 0)
+
+
+def get_apf_counters_from_dumpsys(
+ ad: android_device.AndroidDevice, iface_name: str
+) -> dict:
+ dumpsys = adb_utils.get_dumpsys_for_service(ad, "network_stack")
+
+ # Extract IpClient section of the specified interface.
+ # This takes inputs like:
+ # IpClient.wlan0
+ # ...
+ # IpClient.wlan1
+ # ...
+ iface_pattern = re.compile(
+ r"^IpClient\." + iface_name + r"\n" + r"((^\s.*\n)+)", re.MULTILINE
+ )
+ iface_result = iface_pattern.search(dumpsys)
+ if iface_result is None:
+ raise PatternNotFoundException("Cannot find IpClient for " + iface_name)
+
+ # Extract APF counters section from IpClient section, which looks like:
+ # APF packet counters:
+ # COUNTER_NAME: VALUE
+ # ....
+ apf_pattern = re.compile(
+ r"APF packet counters:.*\n.(\s+[A-Z_0-9]+: \d+\n)+", re.MULTILINE
+ )
+ apf_result = apf_pattern.search(iface_result.group(0))
+ if apf_result is None:
+ raise PatternNotFoundException(
+ "Cannot find APF counters in text: " + iface_result.group(0)
+ )
+
+ # Extract key-value pairs from APF counters section into a list of tuples,
+ # e.g. [('COUNTER1', '1'), ('COUNTER2', '2')].
+ counter_pattern = re.compile(r"(?P<name>[A-Z_0-9]+): (?P<value>\d+)")
+ counter_result = counter_pattern.findall(apf_result.group(0))
+ if counter_result is None:
+ raise PatternNotFoundException(
+ "Cannot extract APF counters in text: " + apf_result.group(0)
+ )
+
+ # Convert into a dict.
+ result = {}
+ for key, value_str in counter_result:
+ result[key] = int(value_str)
+
+ ad.log.debug("Getting apf counters: " + str(result))
+ return result
+
+
+def get_hardware_address(
+ ad: android_device.AndroidDevice, iface_name: str
+) -> str:
+ """Retrieves the hardware (MAC) address for a given network interface.
+
+ Returns:
+ The hex representative of the MAC address in uppercase.
+ E.g. 12:34:56:78:90:AB
+
+ Raises:
+ PatternNotFoundException: If the MAC address is not found in the command
+ output.
+ """
+
+ # Run the "ip link" command and get its output.
+ ip_link_output = adb_utils.adb_shell(ad, f"ip link show {iface_name}")
+
+ # Regular expression to extract the MAC address.
+ # Parse hardware address from ip link output like below:
+ # 46: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq ...
+ # link/ether 72:05:77:82:21:e0 brd ff:ff:ff:ff:ff:ff
+ pattern = r"link/ether (([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2})"
+ match = re.search(pattern, ip_link_output)
+
+ if match:
+ return match.group(1).upper() # Extract the MAC address string.
+ else:
+ raise PatternNotFoundException(
+ "Cannot get hardware address for " + iface_name
+ )
+
+
+def is_send_raw_packet_downstream_supported(
+ ad: android_device.AndroidDevice,
+) -> bool:
+ try:
+ # Invoke the shell command with empty argument and see how NetworkStack respond.
+ # If supported, an IllegalArgumentException with help page will be printed.
+ send_raw_packet_downstream(ad, "", "")
+ except assert_utils.UnexpectedBehaviorError:
+ return True
+ except UnsupportedOperationException:
+ return False
+
+
+def send_raw_packet_downstream(
+ ad: android_device.AndroidDevice,
+ iface_name: str,
+ packet_in_hex: str,
+) -> None:
+ """Sends a raw packet over the specified downstream interface.
+
+ This function constructs and sends a raw packet using the
+ `send-raw-packet-downstream`
+ command provided by NetworkStack process. It's primarily intended for testing
+ purposes.
+
+ Args:
+ ad: The AndroidDevice object representing the connected device.
+ iface_name: The name of the network interface to use (e.g., "wlan0",
+ "eth0").
+ packet_in_hex: The raw packet data starting from L2 header encoded in
+ hexadecimal string format.
+
+ Raises:
+ UnsupportedOperationException: If the NetworkStack doesn't support
+ the `send-raw-packet` command.
+ UnexpectedBehaviorException: If the command execution produces unexpected
+ output other than an empty response or "Unknown command".
+
+ Important Considerations:
+ Security: This method only works on tethering downstream interfaces due
+ to security restrictions.
+ Packet Format: The `packet_in_hex` must be a valid hexadecimal
+ representation of a packet starting from L2 header.
+ """
+
+ cmd = (
+ "cmd network_stack send-raw-packet-downstream"
+ f" {iface_name} {packet_in_hex}"
+ )
+
+ # Expect no output or Unknown command if NetworkStack is too old. Throw otherwise.
+ try:
+ output = adb_utils.adb_shell(ad, cmd)
+ except AdbError as e:
+ output = str(e.stdout)
+ if output:
+ if "Unknown command" in output:
+ raise UnsupportedOperationException(
+ "send-raw-packet-downstream command is not supported."
+ )
+ raise assert_utils.UnexpectedBehaviorError(
+ f"Got unexpected output: {output} for command: {cmd}."
+ )
+
+
+@dataclass
+class ApfCapabilities:
+ """APF program support capabilities.
+
+ See android.net.apf.ApfCapabilities.
+
+ Attributes:
+ apf_version_supported (int): Version of APF instruction set supported for
+ packet filtering. 0 indicates no support for packet filtering using APF
+ programs.
+ apf_ram_size (int): Size of APF ram.
+ apf_packet_format (int): Format of packets passed to APF filter. Should be
+ one of ARPHRD_*
+ """
+
+ apf_version_supported: int
+ apf_ram_size: int
+ apf_packet_format: int
+
+ def __init__(
+ self,
+ apf_version_supported: int,
+ apf_ram_size: int,
+ apf_packet_format: int,
+ ):
+ self.apf_version_supported = apf_version_supported
+ self.apf_ram_size = apf_ram_size
+ self.apf_packet_format = apf_packet_format
+
+ def __str__(self):
+ """Returns a user-friendly string representation of the APF capabilities."""
+ return (
+ f"APF Version: {self.apf_version_supported}\n"
+ f"Ram Size: {self.apf_ram_size} bytes\n"
+ f"Packet Format: {self.apf_packet_format}"
+ )
+
+
+def get_apf_capabilities(
+ ad: android_device.AndroidDevice, iface_name: str
+) -> ApfCapabilities:
+ output = adb_utils.adb_shell(
+ ad, f"cmd network_stack apf {iface_name} capabilities"
+ )
+ try:
+ values = [int(value_str) for value_str in output.split(",")]
+ except ValueError:
+ return ApfCapabilities(0, 0, 0) # Conversion to integer failed
+ return ApfCapabilities(values[0], values[1], values[2])
+
+
+def assume_apf_version_support_at_least(
+ ad: android_device.AndroidDevice, iface_name: str, expected_version: int
+) -> None:
+ caps = get_apf_capabilities(ad, iface_name)
+ asserts.skip_if(
+ caps.apf_version_supported < expected_version,
+ f"Supported apf version {caps.apf_version_supported} < expected version"
+ f" {expected_version}",
+ )
diff --git a/staticlibs/testutils/host/python/assert_utils.py b/staticlibs/testutils/host/python/assert_utils.py
new file mode 100644
index 0000000..da1bb9e
--- /dev/null
+++ b/staticlibs/testutils/host/python/assert_utils.py
@@ -0,0 +1,43 @@
+# Copyright (C) 2024 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.
+
+import time
+from typing import Callable
+
+
+class UnexpectedBehaviorError(Exception):
+ """Raised when there is an unexpected behavior during applying a procedure."""
+
+
+def expect_with_retry(
+ predicate: Callable[[], bool],
+ retry_action: Callable[[], None] = None,
+ max_retries: int = 10,
+ retry_interval_sec: int = 1,
+) -> None:
+ """Executes a predicate and retries if it doesn't return True."""
+
+ for retry in range(max_retries):
+ if predicate():
+ return None
+ else:
+ if retry == max_retries - 1:
+ break
+ if retry_action:
+ retry_action()
+ time.sleep(retry_interval_sec)
+
+ raise UnexpectedBehaviorError(
+ "Predicate didn't become true after " + str(max_retries) + " retries."
+ )
diff --git a/staticlibs/testutils/host/python/mdns_utils.py b/staticlibs/testutils/host/python/mdns_utils.py
new file mode 100644
index 0000000..1234e54
--- /dev/null
+++ b/staticlibs/testutils/host/python/mdns_utils.py
@@ -0,0 +1,57 @@
+# Copyright (C) 2024 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.
+
+from mobly import asserts
+from mobly.controllers import android_device
+
+
+def assume_mdns_test_preconditions(
+ advertising_device: android_device, discovery_device: android_device
+) -> None:
+ advertising = advertising_device.connectivity_multi_devices_snippet
+ discovery = discovery_device.connectivity_multi_devices_snippet
+
+ asserts.skip_if(
+ not advertising.isAtLeastT(), "Advertising device SDK is lower than T."
+ )
+ asserts.skip_if(
+ not discovery.isAtLeastT(), "Discovery device SDK is lower than T."
+ )
+
+
+def register_mdns_service_and_discover_resolve(
+ advertising_device: android_device, discovery_device: android_device
+) -> None:
+ """Test mdns advertising, discovery and resolution
+
+ One device registers an mDNS service, and another device discovers and
+ resolves that service.
+ """
+ advertising = advertising_device.connectivity_multi_devices_snippet
+ discovery = discovery_device.connectivity_multi_devices_snippet
+
+ # Register a mDns service
+ advertising.registerMDnsService()
+
+ # Ensure the discovery and resolution of the mDNS service
+ discovery.ensureMDnsServiceDiscoveryAndResolution()
+
+
+def cleanup_mdns_service(
+ advertising_device: android_device, discovery_device: android_device
+) -> None:
+ # Unregister the mDns service
+ advertising_device.connectivity_multi_devices_snippet.unregisterMDnsService()
+ # Stop discovery
+ discovery_device.connectivity_multi_devices_snippet.stopMDnsServiceDiscovery()
diff --git a/staticlibs/testutils/host/python/multi_devices_test_base.py b/staticlibs/testutils/host/python/multi_devices_test_base.py
new file mode 100644
index 0000000..f8a92f3
--- /dev/null
+++ b/staticlibs/testutils/host/python/multi_devices_test_base.py
@@ -0,0 +1,54 @@
+# Copyright (C) 2024 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.
+#
+# 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.
+
+from mobly import base_test
+from mobly import utils
+from mobly.controllers import android_device
+
+CONNECTIVITY_MULTI_DEVICES_SNIPPET_PACKAGE = "com.google.snippet.connectivity"
+
+
+class MultiDevicesTestBase(base_test.BaseTestClass):
+
+ def setup_class(self):
+ # Declare that two Android devices are needed.
+ self.clientDevice, self.serverDevice = self.register_controller(
+ android_device, min_number=2
+ )
+
+ def setup_device(device):
+ device.load_snippet(
+ "connectivity_multi_devices_snippet",
+ CONNECTIVITY_MULTI_DEVICES_SNIPPET_PACKAGE,
+ )
+
+ # Set up devices in parallel to save time.
+ utils.concurrent_exec(
+ setup_device,
+ ((self.clientDevice,), (self.serverDevice,)),
+ max_workers=2,
+ raise_on_exception=True,
+ )
diff --git a/tests/cts/multidevices/tether_utils.py b/staticlibs/testutils/host/python/tether_utils.py
similarity index 86%
rename from tests/cts/multidevices/tether_utils.py
rename to staticlibs/testutils/host/python/tether_utils.py
index 61f5c43..702b596 100644
--- a/tests/cts/multidevices/tether_utils.py
+++ b/staticlibs/testutils/host/python/tether_utils.py
@@ -11,18 +11,6 @@
# 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.
-#
-# 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.
import base64
import uuid
@@ -32,6 +20,7 @@
class UpstreamType:
+ NONE = 0
CELLULAR = 1
WIFI = 2
@@ -68,6 +57,8 @@
not server.isStaApConcurrencySupported(),
"Server requires Wifi AP + STA concurrency",
)
+ elif upstream_type == UpstreamType.NONE:
+ pass
else:
raise ValueError(f"Invalid upstream type: {upstream_type}")
@@ -90,6 +81,8 @@
server.requestCellularAndEnsureDefault()
elif upstream_type == UpstreamType.WIFI:
server.ensureWifiIsDefault()
+ elif upstream_type == UpstreamType.NONE:
+ pass
else:
raise ValueError(f"Invalid upstream type: {upstream_type}")
diff --git a/staticlibs/testutils/host/python/wifip2p_utils.py b/staticlibs/testutils/host/python/wifip2p_utils.py
new file mode 100644
index 0000000..8b4ffa5
--- /dev/null
+++ b/staticlibs/testutils/host/python/wifip2p_utils.py
@@ -0,0 +1,50 @@
+# Copyright (C) 2024 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.
+
+from mobly import asserts
+from mobly.controllers import android_device
+
+
+def assume_wifi_p2p_test_preconditions(
+ server_device: android_device, client_device: android_device
+) -> None:
+ server = server_device.connectivity_multi_devices_snippet
+ client = client_device.connectivity_multi_devices_snippet
+
+ # Assert pre-conditions
+ asserts.skip_if(not server.hasWifiFeature(), "Server requires Wifi feature")
+ asserts.skip_if(not client.hasWifiFeature(), "Client requires Wifi feature")
+ asserts.skip_if(
+ not server.isP2pSupported(), "Server requires Wi-fi P2P feature"
+ )
+ asserts.skip_if(
+ not client.isP2pSupported(), "Client requires Wi-fi P2P feature"
+ )
+
+
+def setup_wifi_p2p_server_and_client(
+ server_device: android_device, client_device: android_device
+) -> None:
+ """Set up the Wi-Fi P2P server and client."""
+ # Start Wi-Fi P2P on both server and client.
+ server_device.connectivity_multi_devices_snippet.startWifiP2p()
+ client_device.connectivity_multi_devices_snippet.startWifiP2p()
+
+
+def cleanup_wifi_p2p(
+ server_device: android_device, client_device: android_device
+) -> None:
+ # Stop Wi-Fi P2P
+ server_device.connectivity_multi_devices_snippet.stopWifiP2p()
+ client_device.connectivity_multi_devices_snippet.stopWifiP2p()
diff --git a/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt b/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt
index 8cef6aa..17f5e96 100644
--- a/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt
+++ b/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt
@@ -28,18 +28,26 @@
import android.net.NetworkStats.SET_DEFAULT
import android.net.NetworkStats.SET_FOREGROUND
import android.net.NetworkStats.TAG_NONE
+import android.os.Build
import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.assertNetworkStatsEquals
import com.android.testutils.assertParcelingIsLossless
import kotlin.test.assertEquals
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-@RunWith(JUnit4::class)
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner::class)
@SmallTest
class NetworkStatsApiTest {
+ @get:Rule
+ val ignoreRule = DevSdkIgnoreRule()
private val testStatsEmpty = NetworkStats(0L, 0)
// Note that these variables need to be initialized outside of constructor, initialize
@@ -49,6 +57,7 @@
// be merged if performing add on these 2 stats.
private lateinit var testStats1: NetworkStats
private lateinit var testStats2: NetworkStats
+ private lateinit var expectedEntriesInStats2: List<Entry>
// This is a result of adding stats1 and stats2, while the merging of common key items is
// subject to test later, this should not be initialized with for a loop to add stats1
@@ -84,19 +93,23 @@
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 1, 6, 2, 0))
assertEquals(8, testStats1.size())
- testStats2 = NetworkStats(0L, 0)
- // Entries which are common for set1 and set2.
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1))
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45))
- .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7))
- .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0))
- // Entry which only appears in set2.
- .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
+ expectedEntriesInStats2 = listOf(
+ // Entries which are common for set1 and set2.
+ Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1),
+ Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45),
+ Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7),
+ Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0),
+ // Entry which only appears in set2.
+ Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
+ testStats2 = NetworkStats(0L, 5)
+ for (entry in expectedEntriesInStats2) {
+ testStats2 = testStats2.addEntry(entry)
+ }
assertEquals(5, testStats2.size())
testStats3 = NetworkStats(0L, 9)
@@ -125,18 +138,6 @@
@Test
fun testAddEntry() {
- val expectedEntriesInStats2 = arrayOf(
- Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1),
- Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45),
- Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7),
- Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0),
- Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
-
// While testStats* are already initialized with addEntry, verify content added
// matches expectation.
for (i in expectedEntriesInStats2.indices) {
@@ -150,6 +151,27 @@
assertEquals(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 16, -2, 9, 1, 9),
stats.getValues(3, null))
+
+ // Verify the original ststs object is not altered.
+ for (i in expectedEntriesInStats2.indices) {
+ val entry = testStats2.getValues(i, null)
+ assertEquals(expectedEntriesInStats2[i], entry)
+ }
+ }
+
+ @ConnectivityModuleTest
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2) // Mainlined NetworkStats only runs on T+
+ @Test
+ fun testAddEntries() {
+ val baseStats = NetworkStats(0L, 1)
+ .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 12, -5, 7, 0, 9))
+ val statsUnderTest = baseStats.addEntries(expectedEntriesInStats2)
+ // Assume the correctness of addEntry is verified in other tests.
+ val expectedStats = testStats2
+ .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 12, -5, 7, 0, 9))
+ assertNetworkStatsEquals(expectedStats, statsUnderTest)
}
@Test
diff --git a/tests/cts/hostside-network-policy/Android.bp b/tests/cts/hostside-network-policy/Android.bp
deleted file mode 100644
index c3ce0b9..0000000
--- a/tests/cts/hostside-network-policy/Android.bp
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (C) 2024 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 {
- default_team: "trendy_team_framework_backstage_power",
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_test_host {
- name: "CtsHostsideNetworkPolicyTests",
- defaults: ["cts_defaults"],
- // Only compile source java files in this apk.
- srcs: [
- "src/**/*.java",
- ":ArgumentConstants",
- ],
- libs: [
- "cts-tradefed",
- "tradefed",
- ],
- static_libs: [
- "modules-utils-build-testing",
- ],
- // Tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
- "sts",
- ],
- data: [
- ":CtsHostsideNetworkPolicyTestsApp",
- ":CtsHostsideNetworkPolicyTestsApp2",
- ],
- per_testcase_directory: true,
-}
diff --git a/tests/cts/hostside-network-policy/AndroidTest.xml b/tests/cts/hostside-network-policy/AndroidTest.xml
deleted file mode 100644
index 44f77f8..0000000
--- a/tests/cts/hostside-network-policy/AndroidTest.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2024 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.
--->
-<configuration description="Config for CTS network policy host test cases">
- <option name="test-suite-tag" value="cts" />
- <option name="config-descriptor:metadata" key="component" value="networking" />
- <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
- <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
- <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
- <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-
- <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
- <target_preparer class="com.android.cts.netpolicy.NetworkPolicyTestsPreparer" />
-
- <!-- Enabling change id ALLOW_TEST_API_ACCESS allows that package to access @TestApi methods -->
- <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS com.android.cts.netpolicy.hostside.app2" />
- <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS com.android.cts.netpolicy.hostside.app2" />
- <option name="teardown-command" value="cmd power set-mode 0" />
- <option name="teardown-command" value="cmd battery reset" />
- <option name="teardown-command" value="cmd netpolicy stop-watching" />
- </target_preparer>
-
- <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
- <option name="force-skip-system-props" value="true" />
- <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
- <option name="set-global-setting" key="low_power_standby_enabled" value="0" />
- </target_preparer>
-
- <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
- <option name="jar" value="CtsHostsideNetworkPolicyTests.jar" />
- <option name="runtime-hint" value="3m56s" />
- </test>
-
- <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
- <option name="directory-keys" value="/sdcard/CtsHostsideNetworkPolicyTests" />
- <option name="collect-on-run-ended-only" value="true" />
- </metrics_collector>
-</configuration>
diff --git a/tests/cts/hostside-network-policy/OWNERS b/tests/cts/hostside-network-policy/OWNERS
deleted file mode 100644
index ea83e61..0000000
--- a/tests/cts/hostside-network-policy/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-# Bug component: 61373
-# Inherits parent owners
-include platform/frameworks/base:/services/core/java/com/android/server/net/OWNERS
diff --git a/tests/cts/hostside-network-policy/TEST_MAPPING b/tests/cts/hostside-network-policy/TEST_MAPPING
deleted file mode 100644
index 57ac4f7..0000000
--- a/tests/cts/hostside-network-policy/TEST_MAPPING
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "presubmit-large": [
- {
- "name": "CtsHostsideNetworkPolicyTests",
- "options": [
- {
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
- },
- {
- "exclude-annotation": "android.platform.test.annotations.RequiresDevice"
- }
- ]
- }
- ],
- "postsubmit": [
- {
- // Postsubmit on virtual devices to monitor flakiness of all tests that don't require a
- // physical device
- "name": "CtsHostsideNetworkPolicyTests",
- "options": [
- {
- "exclude-annotation": "androidx.test.filters.RequiresDevice"
- }
- ]
- }
- ]
-}
diff --git a/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/IMyService.aidl b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/IMyService.aidl
deleted file mode 100644
index 068d9d8..0000000
--- a/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/IMyService.aidl
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import android.app.job.JobInfo;
-
-import com.android.cts.netpolicy.hostside.INetworkCallback;
-import com.android.cts.netpolicy.hostside.NetworkCheckResult;
-
-interface IMyService {
- void registerBroadcastReceiver();
- int getCounters(String receiverName, String action);
- NetworkCheckResult checkNetworkStatus(String customUrl);
- String getRestrictBackgroundStatus();
- void sendNotification(int notificationId, String notificationType);
- void registerNetworkCallback(in NetworkRequest request, in INetworkCallback cb);
- void unregisterNetworkCallback();
- int scheduleJob(in JobInfo jobInfo);
-}
diff --git a/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkCallback.aidl b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkCallback.aidl
deleted file mode 100644
index 38efc7b..0000000
--- a/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkCallback.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2019 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.cts.netpolicy.hostside;
-
-import android.net.Network;
-import android.net.NetworkCapabilities;
-
-interface INetworkCallback {
- void onBlockedStatusChanged(in Network network, boolean blocked);
- void onAvailable(in Network network);
- void onLost(in Network network);
- void onCapabilitiesChanged(in Network network, in NetworkCapabilities cap);
-}
diff --git a/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkStateObserver.aidl b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkStateObserver.aidl
deleted file mode 100644
index c6b7a1c..0000000
--- a/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkStateObserver.aidl
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import android.net.NetworkInfo;
-
-import com.android.cts.netpolicy.hostside.NetworkCheckResult;
-
-interface INetworkStateObserver {
- void onNetworkStateChecked(int resultCode, in NetworkCheckResult networkCheckResult);
-
- const int RESULT_SUCCESS_NETWORK_STATE_CHECKED = 0;
- const int RESULT_ERROR_UNEXPECTED_PROC_STATE = 1;
- const int RESULT_ERROR_UNEXPECTED_CAPABILITIES = 2;
- const int RESULT_ERROR_OTHER = 3;
-}
\ No newline at end of file
diff --git a/tests/cts/hostside-network-policy/app/Android.bp b/tests/cts/hostside-network-policy/app/Android.bp
deleted file mode 100644
index a31c843..0000000
--- a/tests/cts/hostside-network-policy/app/Android.bp
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-// Copyright (C) 2024 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 {
- default_team: "trendy_team_framework_backstage_power",
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_defaults {
- name: "CtsHostsideNetworkPolicyTestsAppDefaults",
- platform_apis: true,
- static_libs: [
- "CtsHostsideNetworkPolicyTestsAidl",
- "androidx.test.ext.junit",
- "androidx.test.rules",
- "androidx.test.uiautomator_uiautomator",
- "compatibility-device-util-axt",
- "cts-net-utils",
- "ctstestrunner-axt",
- "modules-utils-build",
- ],
- libs: [
- "android.test.runner",
- "android.test.base",
- ],
- srcs: [
- "src/**/*.java",
- ":ArgumentConstants",
- ],
- // Tag this module as a cts test artifact
- test_suites: [
- "general-tests",
- "sts",
- ],
-}
-
-android_test_helper_app {
- name: "CtsHostsideNetworkPolicyTestsApp",
- defaults: [
- "cts_support_defaults",
- "framework-connectivity-test-defaults",
- "CtsHostsideNetworkPolicyTestsAppDefaults",
- ],
-}
diff --git a/tests/cts/hostside-network-policy/app/AndroidManifest.xml b/tests/cts/hostside-network-policy/app/AndroidManifest.xml
deleted file mode 100644
index f19e35f..0000000
--- a/tests/cts/hostside-network-policy/app/AndroidManifest.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2024 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.cts.netpolicy.hostside">
-
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
- <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
- <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
- <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
- <uses-permission android:name="android.permission.INTERNET"/>
- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
- <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
- <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
- <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
- <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
- <uses-permission android:name="android.permission.WAKE_LOCK" />
-
- <application android:requestLegacyExternalStorage="true">
- <uses-library android:name="android.test.runner"/>
- <service android:name=".MyNotificationListenerService"
- android:label="MyNotificationListenerService"
- android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
- android:exported="true">
- <intent-filter>
- <action android:name="android.service.notification.NotificationListenerService"/>
- </intent-filter>
- </service>
- </application>
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.cts.netpolicy.hostside"/>
-
-</manifest>
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractAppIdleTestCase.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractAppIdleTestCase.java
deleted file mode 100644
index 19e4364..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractAppIdleTestCase.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.APP_STANDBY_MODE;
-import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
-
-import static org.junit.Assert.assertEquals;
-
-import android.os.SystemClock;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Base class for metered and non-metered tests on idle apps.
- */
-@RequiredProperties({APP_STANDBY_MODE})
-abstract class AbstractAppIdleTestCase extends AbstractRestrictBackgroundNetworkTestCase {
-
- @Before
- public final void setUp() throws Exception {
- super.setUp();
-
- // Set initial state.
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- setAppIdle(false);
- turnBatteryOn();
-
- registerBroadcastReceiver();
- }
-
- @After
- public final void tearDown() throws Exception {
- super.tearDown();
-
- resetBatteryState();
- setAppIdle(false);
- }
-
- @Test
- public void testBackgroundNetworkAccess_enabled() throws Exception {
- setAppIdle(true);
- assertBackgroundNetworkAccess(false);
-
- assertsForegroundAlwaysHasNetworkAccess();
- setAppIdle(true);
- assertBackgroundNetworkAccess(false);
-
- // Make sure foreground app doesn't lose access upon enabling it.
- setAppIdle(true);
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
- finishActivity();
- assertAppIdle(false); // verify - not idle anymore, since activity was launched...
- assertBackgroundNetworkAccess(true);
- setAppIdle(true);
- assertBackgroundNetworkAccess(false);
-
- // Same for foreground service.
- setAppIdle(true);
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
- stopForegroundService();
- assertAppIdle(true);
- assertBackgroundNetworkAccess(false);
-
- // Set Idle after foreground service start.
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
- setAppIdle(true);
- addPowerSaveModeWhitelist(TEST_PKG);
- removePowerSaveModeWhitelist(TEST_PKG);
- assertForegroundServiceNetworkAccess();
- stopForegroundService();
- assertAppIdle(true);
- assertBackgroundNetworkAccess(false);
-
- }
-
- @Test
- public void testBackgroundNetworkAccess_whitelisted() throws Exception {
- setAppIdle(true);
- assertBackgroundNetworkAccess(false);
-
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertAppIdle(false); // verify - not idle anymore, since whitelisted
- assertBackgroundNetworkAccess(true);
-
- setAppIdleNoAssert(true);
- assertAppIdle(false); // app is still whitelisted
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertAppIdle(true); // verify - idle again, once whitelisted was removed
- assertBackgroundNetworkAccess(false);
-
- setAppIdle(true);
- addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertAppIdle(false); // verify - not idle anymore, since whitelisted
- assertBackgroundNetworkAccess(true);
-
- setAppIdleNoAssert(true);
- assertAppIdle(false); // app is still whitelisted
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertAppIdle(true); // verify - idle again, once whitelisted was removed
- assertBackgroundNetworkAccess(false);
-
- assertsForegroundAlwaysHasNetworkAccess();
-
- // verify - no whitelist, no access!
- setAppIdle(true);
- assertBackgroundNetworkAccess(false);
- }
-
- @Test
- public void testBackgroundNetworkAccess_tempWhitelisted() throws Exception {
- setAppIdle(true);
- assertBackgroundNetworkAccess(false);
-
- addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
- assertBackgroundNetworkAccess(true);
- // Wait until the whitelist duration is expired.
- SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
- assertBackgroundNetworkAccess(false);
- }
-
- @Test
- public void testBackgroundNetworkAccess_disabled() throws Exception {
- assertBackgroundNetworkAccess(true);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(true);
- }
-
- @RequiredProperties({BATTERY_SAVER_MODE})
- @Test
- public void testAppIdleNetworkAccess_whenCharging() throws Exception {
- // Check that app is paroled when charging
- setAppIdle(true);
- assertBackgroundNetworkAccess(false);
- turnBatteryOff();
- assertBackgroundNetworkAccess(true);
- turnBatteryOn();
- assertBackgroundNetworkAccess(false);
-
- // Check that app is restricted when not idle but power-save is on
- setAppIdle(false);
- assertBackgroundNetworkAccess(true);
- setBatterySaverMode(true);
- assertBackgroundNetworkAccess(false);
- // Use setBatterySaverMode API to leave power-save mode instead of plugging in charger
- setBatterySaverMode(false);
- turnBatteryOff();
- assertBackgroundNetworkAccess(true);
-
- // And when no longer charging, it still has network access, since it's not idle
- turnBatteryOn();
- assertBackgroundNetworkAccess(true);
- }
-
- @Test
- public void testAppIdleNetworkAccess_idleWhitelisted() throws Exception {
- setAppIdle(true);
- assertAppIdle(true);
- assertBackgroundNetworkAccess(false);
-
- addAppIdleWhitelist(mUid);
- assertBackgroundNetworkAccess(true);
-
- removeAppIdleWhitelist(mUid);
- assertBackgroundNetworkAccess(false);
-
- // Make sure whitelisting a random app doesn't affect the tested app.
- addAppIdleWhitelist(mUid + 1);
- assertBackgroundNetworkAccess(false);
- removeAppIdleWhitelist(mUid + 1);
- }
-
- @Test
- public void testAppIdle_toast() throws Exception {
- setAppIdle(true);
- assertAppIdle(true);
- assertEquals("Shown", showToast());
- assertAppIdle(true);
- // Wait for a couple of seconds for the toast to actually be shown
- SystemClock.sleep(2000);
- assertAppIdle(true);
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractBatterySaverModeTestCase.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractBatterySaverModeTestCase.java
deleted file mode 100644
index ae226e2..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractBatterySaverModeTestCase.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Base class for metered and non-metered Battery Saver Mode tests.
- */
-@RequiredProperties({BATTERY_SAVER_MODE})
-abstract class AbstractBatterySaverModeTestCase extends AbstractRestrictBackgroundNetworkTestCase {
-
- @Before
- public final void setUp() throws Exception {
- super.setUp();
-
- // Set initial state.
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- setBatterySaverMode(false);
-
- registerBroadcastReceiver();
- }
-
- @After
- public final void tearDown() throws Exception {
- super.tearDown();
-
- setBatterySaverMode(false);
- }
-
- @Test
- public void testBackgroundNetworkAccess_enabled() throws Exception {
- setBatterySaverMode(true);
- assertBackgroundNetworkAccess(false);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
-
- // Make sure foreground app doesn't lose access upon Battery Saver.
- setBatterySaverMode(false);
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
- setBatterySaverMode(true);
- assertTopNetworkAccess(true);
-
- // Although it should not have access while the screen is off.
- turnScreenOff();
- assertBackgroundNetworkAccess(false);
- turnScreenOn();
- assertTopNetworkAccess(true);
-
- // Goes back to background state.
- finishActivity();
- assertBackgroundNetworkAccess(false);
-
- // Make sure foreground service doesn't lose access upon enabling Battery Saver.
- setBatterySaverMode(false);
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
- setBatterySaverMode(true);
- assertForegroundServiceNetworkAccess();
- stopForegroundService();
- assertBackgroundNetworkAccess(false);
- }
-
- @Test
- public void testBackgroundNetworkAccess_whitelisted() throws Exception {
- setBatterySaverMode(true);
- assertBackgroundNetworkAccess(false);
-
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
-
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
-
- addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
-
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
- }
-
- @Test
- public void testBackgroundNetworkAccess_disabled() throws Exception {
- assertBackgroundNetworkAccess(true);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(true);
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDefaultRestrictionsTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDefaultRestrictionsTest.java
deleted file mode 100644
index 00f67f4..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDefaultRestrictionsTest.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2024 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.cts.netpolicy.hostside;
-
-import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
-
-import static org.junit.Assume.assumeTrue;
-
-import android.os.SystemClock;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Base class for default, always-on network restrictions.
- */
-abstract class AbstractDefaultRestrictionsTest extends AbstractRestrictBackgroundNetworkTestCase {
-
- @Before
- public final void setUp() throws Exception {
- super.setUp();
-
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
-
- registerBroadcastReceiver();
- assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
- }
-
- @After
- public final void tearDown() throws Exception {
- super.tearDown();
-
- stopApp();
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- }
-
- @Test
- public void testFgsNetworkAccess() throws Exception {
- assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
- SystemClock.sleep(mProcessStateTransitionShortDelayMs);
- assertNetworkAccess(false, null);
-
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
- }
-
- @Test
- public void testActivityNetworkAccess() throws Exception {
- assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
- SystemClock.sleep(mProcessStateTransitionShortDelayMs);
- assertNetworkAccess(false, null);
-
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
- }
-
- @Test
- public void testBackgroundNetworkAccess_inFullAllowlist() throws Exception {
- assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
- SystemClock.sleep(mProcessStateTransitionShortDelayMs);
- assertNetworkAccess(false, null);
-
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
- assertNetworkAccess(true, null);
- }
-
- @Test
- public void testBackgroundNetworkAccess_inExceptIdleAllowlist() throws Exception {
- assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
- SystemClock.sleep(mProcessStateTransitionShortDelayMs);
- assertNetworkAccess(false, null);
-
- addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
- assertNetworkAccess(true, null);
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDozeModeTestCase.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDozeModeTestCase.java
deleted file mode 100644
index 0c8cb70..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDozeModeTestCase.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-
-import static com.android.cts.netpolicy.hostside.Property.DOZE_MODE;
-import static com.android.cts.netpolicy.hostside.Property.NOT_LOW_RAM_DEVICE;
-
-import android.os.SystemClock;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Base class for metered and non-metered Doze Mode tests.
- */
-@RequiredProperties({DOZE_MODE})
-abstract class AbstractDozeModeTestCase extends AbstractRestrictBackgroundNetworkTestCase {
-
- @Before
- public final void setUp() throws Exception {
- super.setUp();
-
- // Set initial state.
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- setDozeMode(false);
-
- registerBroadcastReceiver();
- }
-
- @After
- public final void tearDown() throws Exception {
- super.tearDown();
-
- setDozeMode(false);
- }
-
- @Test
- public void testBackgroundNetworkAccess_enabled() throws Exception {
- setDozeMode(true);
- assertBackgroundNetworkAccess(false);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
-
- // Make sure foreground service doesn't lose network access upon enabling doze.
- setDozeMode(false);
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
- setDozeMode(true);
- assertForegroundServiceNetworkAccess();
- stopForegroundService();
- assertProcessStateBelow(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
- assertBackgroundNetworkAccess(false);
- }
-
- @Test
- public void testBackgroundNetworkAccess_whitelisted() throws Exception {
- setDozeMode(true);
- assertBackgroundNetworkAccess(false);
-
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
-
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
-
- addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
-
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
- }
-
- @Test
- public void testBackgroundNetworkAccess_disabled() throws Exception {
- assertBackgroundNetworkAccess(true);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(true);
- }
-
- @RequiredProperties({NOT_LOW_RAM_DEVICE})
- @Test
- public void testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction()
- throws Exception {
- setPendingIntentAllowlistDuration(NETWORK_TIMEOUT_MS);
- try {
- registerNotificationListenerService();
- setDozeMode(true);
- assertBackgroundNetworkAccess(false);
-
- testNotification(4, NOTIFICATION_TYPE_CONTENT);
- testNotification(8, NOTIFICATION_TYPE_DELETE);
- testNotification(15, NOTIFICATION_TYPE_FULL_SCREEN);
- testNotification(16, NOTIFICATION_TYPE_BUNDLE);
- testNotification(23, NOTIFICATION_TYPE_ACTION);
- testNotification(42, NOTIFICATION_TYPE_ACTION_BUNDLE);
- testNotification(108, NOTIFICATION_TYPE_ACTION_REMOTE_INPUT);
- } finally {
- resetDeviceIdleSettings();
- }
- }
-
- private void testNotification(int id, String type) throws Exception {
- sendNotification(id, type);
- assertBackgroundNetworkAccess(true);
- if (type.equals(NOTIFICATION_TYPE_ACTION)) {
- // Make sure access is disabled after it expires. Since this check considerably slows
- // downs the CTS tests, do it just once.
- SystemClock.sleep(NETWORK_TIMEOUT_MS);
- assertBackgroundNetworkAccess(false);
- }
- }
-
- // Must override so it only tests foreground service - once an app goes to foreground, device
- // leaves Doze Mode.
- @Override
- protected void assertsForegroundAlwaysHasNetworkAccess() throws Exception {
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
- stopForegroundService();
- assertProcessStateBelow(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractExpeditedJobTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractExpeditedJobTest.java
deleted file mode 100644
index 5435920..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractExpeditedJobTest.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.netpolicy.hostside.Property.APP_STANDBY_MODE;
-import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
-import static com.android.cts.netpolicy.hostside.Property.DOZE_MODE;
-import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class AbstractExpeditedJobTest extends AbstractRestrictBackgroundNetworkTestCase {
- @Before
- public final void setUp() throws Exception {
- super.setUp();
- resetDeviceState();
- }
-
- @After
- public final void tearDown() throws Exception {
- super.tearDown();
- resetDeviceState();
- }
-
- private void resetDeviceState() throws Exception {
- resetBatteryState();
- setBatterySaverMode(false);
- setRestrictBackground(false);
- setAppIdle(false);
- setDozeMode(false);
- }
-
- @Test
- @RequiredProperties({BATTERY_SAVER_MODE})
- public void testNetworkAccess_batterySaverMode() throws Exception {
- assertBackgroundNetworkAccess(true);
- assertExpeditedJobHasNetworkAccess();
-
- setBatterySaverMode(true);
- assertBackgroundNetworkAccess(false);
- assertExpeditedJobHasNetworkAccess();
- }
-
- @Test
- @RequiredProperties({DATA_SAVER_MODE, METERED_NETWORK})
- public void testNetworkAccess_dataSaverMode() throws Exception {
- assertBackgroundNetworkAccess(true);
- assertExpeditedJobHasNetworkAccess();
-
- setRestrictBackground(true);
- assertBackgroundNetworkAccess(false);
- assertExpeditedJobHasNoNetworkAccess();
- }
-
- @Test
- @RequiredProperties({APP_STANDBY_MODE})
- public void testNetworkAccess_appIdleState() throws Exception {
- turnBatteryOn();
- setAppIdle(false);
- assertBackgroundNetworkAccess(true);
- assertExpeditedJobHasNetworkAccess();
-
- setAppIdle(true);
- assertBackgroundNetworkAccess(false);
- assertExpeditedJobHasNetworkAccess();
- }
-
- @Test
- @RequiredProperties({DOZE_MODE})
- public void testNetworkAccess_dozeMode() throws Exception {
- assertBackgroundNetworkAccess(true);
- assertExpeditedJobHasNetworkAccess();
-
- setDozeMode(true);
- assertBackgroundNetworkAccess(false);
- assertExpeditedJobHasNetworkAccess();
- }
-
- @Test
- @RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE, METERED_NETWORK})
- public void testNetworkAccess_dataAndBatterySaverMode() throws Exception {
- assertBackgroundNetworkAccess(true);
- assertExpeditedJobHasNetworkAccess();
-
- setRestrictBackground(true);
- setBatterySaverMode(true);
- assertBackgroundNetworkAccess(false);
- assertExpeditedJobHasNoNetworkAccess();
- }
-
- @Test
- @RequiredProperties({DOZE_MODE, DATA_SAVER_MODE, METERED_NETWORK})
- public void testNetworkAccess_dozeAndDataSaverMode() throws Exception {
- assertBackgroundNetworkAccess(true);
- assertExpeditedJobHasNetworkAccess();
-
- setRestrictBackground(true);
- setDozeMode(true);
- assertBackgroundNetworkAccess(false);
- assertExpeditedJobHasNoNetworkAccess();
- }
-
- @Test
- @RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE, METERED_NETWORK, DOZE_MODE,
- APP_STANDBY_MODE})
- public void testNetworkAccess_allRestrictionsEnabled() throws Exception {
- assertBackgroundNetworkAccess(true);
- assertExpeditedJobHasNetworkAccess();
-
- setRestrictBackground(true);
- setBatterySaverMode(true);
- setAppIdle(true);
- setDozeMode(true);
- assertBackgroundNetworkAccess(false);
- assertExpeditedJobHasNoNetworkAccess();
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractRestrictBackgroundNetworkTestCase.java
deleted file mode 100644
index 0f5f58c..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ /dev/null
@@ -1,1165 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
-import static android.app.ActivityManager.PROCESS_STATE_TOP;
-import static android.app.job.JobScheduler.RESULT_SUCCESS;
-import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
-import static android.os.BatteryManager.BATTERY_PLUGGED_ANY;
-
-import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_CONNECTION_CHECK_CUSTOM_URL;
-import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.executeShellCommand;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.forceRunJob;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.getConnectivityManager;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.getContext;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.getInstrumentation;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isAppStandbySupported;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.restrictBackgroundValueToString;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackgroundInternal;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.annotation.NonNull;
-import android.app.Instrumentation;
-import android.app.NotificationManager;
-import android.app.job.JobInfo;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkInfo.State;
-import android.net.NetworkRequest;
-import android.os.BatteryManager;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.PowerManager;
-import android.os.RemoteCallback;
-import android.os.SystemClock;
-import android.provider.DeviceConfig;
-import android.service.notification.NotificationListenerService;
-import android.util.Log;
-import android.util.Pair;
-
-import androidx.annotation.Nullable;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.AmUtils;
-import com.android.compatibility.common.util.BatteryUtils;
-import com.android.compatibility.common.util.DeviceConfigStateHelper;
-import com.android.compatibility.common.util.ThrowingRunnable;
-import com.android.modules.utils.build.SdkLevel;
-
-import org.junit.Rule;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Predicate;
-
-/**
- * Superclass for tests related to background network restrictions.
- */
-@RunWith(NetworkPolicyTestRunner.class)
-public abstract class AbstractRestrictBackgroundNetworkTestCase {
- public static final String TAG = "RestrictBackgroundNetworkTests";
-
- protected static final String TEST_PKG = "com.android.cts.netpolicy.hostside";
- protected static final String TEST_APP2_PKG = "com.android.cts.netpolicy.hostside.app2";
- // TODO(b/321797685): Configure it via device-config once it is available.
- protected final long mProcessStateTransitionLongDelayMs =
- useDifferentDelaysForBackgroundChain() ? TimeUnit.SECONDS.toMillis(20)
- : TimeUnit.SECONDS.toMillis(5);
- protected final long mProcessStateTransitionShortDelayMs =
- useDifferentDelaysForBackgroundChain() ? TimeUnit.SECONDS.toMillis(2)
- : TimeUnit.SECONDS.toMillis(5);
-
- private static final String TEST_APP2_ACTIVITY_CLASS = TEST_APP2_PKG + ".MyActivity";
- private static final String TEST_APP2_SERVICE_CLASS = TEST_APP2_PKG + ".MyForegroundService";
- private static final String TEST_APP2_JOB_SERVICE_CLASS = TEST_APP2_PKG + ".MyJobService";
-
- private static final ComponentName TEST_JOB_COMPONENT = new ComponentName(
- TEST_APP2_PKG, TEST_APP2_JOB_SERVICE_CLASS);
- private static final int TEST_JOB_ID = 7357437;
-
- private static final int SLEEP_TIME_SEC = 1;
-
- // Constants below must match values defined on app2's Common.java
- private static final String MANIFEST_RECEIVER = "ManifestReceiver";
- private static final String DYNAMIC_RECEIVER = "DynamicReceiver";
- private static final String ACTION_FINISH_ACTIVITY =
- "com.android.cts.netpolicy.hostside.app2.action.FINISH_ACTIVITY";
- private static final String ACTION_FINISH_JOB =
- "com.android.cts.netpolicy.hostside.app2.action.FINISH_JOB";
- // Copied from com.android.server.net.NetworkPolicyManagerService class
- private static final String ACTION_SNOOZE_WARNING =
- "com.android.server.net.action.SNOOZE_WARNING";
-
- private static final String ACTION_RECEIVER_READY =
- "com.android.cts.netpolicy.hostside.app2.action.RECEIVER_READY";
- static final String ACTION_SHOW_TOAST =
- "com.android.cts.netpolicy.hostside.app2.action.SHOW_TOAST";
-
- protected static final String NOTIFICATION_TYPE_CONTENT = "CONTENT";
- protected static final String NOTIFICATION_TYPE_DELETE = "DELETE";
- protected static final String NOTIFICATION_TYPE_FULL_SCREEN = "FULL_SCREEN";
- protected static final String NOTIFICATION_TYPE_BUNDLE = "BUNDLE";
- protected static final String NOTIFICATION_TYPE_ACTION = "ACTION";
- protected static final String NOTIFICATION_TYPE_ACTION_BUNDLE = "ACTION_BUNDLE";
- protected static final String NOTIFICATION_TYPE_ACTION_REMOTE_INPUT = "ACTION_REMOTE_INPUT";
-
- private static final String NETWORK_STATUS_SEPARATOR = "\\|";
- private static final int SECOND_IN_MS = 1000;
- static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS;
-
- private static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
- private static final String KEY_SKIP_VALIDATION_CHECKS = TEST_PKG + ".skip_validation_checks";
- private static final String KEY_CUSTOM_URL = TEST_PKG + ".custom_url";
-
- private static final String EMPTY_STRING = "";
-
- protected static final int TYPE_COMPONENT_ACTIVTIY = 0;
- protected static final int TYPE_COMPONENT_FOREGROUND_SERVICE = 1;
- protected static final int TYPE_EXPEDITED_JOB = 2;
-
- private static final int BATTERY_STATE_TIMEOUT_MS = 5000;
- private static final int BATTERY_STATE_CHECK_INTERVAL_MS = 500;
-
- private static final int ACTIVITY_NETWORK_STATE_TIMEOUT_MS = 10_000;
- private static final int JOB_NETWORK_STATE_TIMEOUT_MS = 10_000;
- private static final int LAUNCH_ACTIVITY_TIMEOUT_MS = 10_000;
-
- // Must be higher than NETWORK_TIMEOUT_MS
- private static final int ORDERED_BROADCAST_TIMEOUT_MS = NETWORK_TIMEOUT_MS * 4;
-
- private static final IntentFilter BATTERY_CHANGED_FILTER =
- new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
-
- protected static final long TEMP_POWERSAVE_WHITELIST_DURATION_MS = 20_000; // 20 sec
-
- private static final long BROADCAST_TIMEOUT_MS = 5_000;
-
- protected Context mContext;
- protected Instrumentation mInstrumentation;
- protected ConnectivityManager mCm;
- protected int mUid;
- private int mMyUid;
- private @Nullable String mCustomUrl;
- private MyServiceClient mServiceClient;
- private DeviceConfigStateHelper mDeviceIdleDeviceConfigStateHelper;
- private PowerManager mPowerManager;
- private PowerManager.WakeLock mLock;
-
- @Rule
- public final RuleChain mRuleChain = RuleChain.outerRule(new RequiredPropertiesRule())
- .around(new MeterednessConfigurationRule());
-
- protected void setUp() throws Exception {
- mInstrumentation = getInstrumentation();
- mContext = getContext();
- mCm = getConnectivityManager();
- mDeviceIdleDeviceConfigStateHelper =
- new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_DEVICE_IDLE);
- mUid = getUid(TEST_APP2_PKG);
- mMyUid = getUid(mContext.getPackageName());
- mServiceClient = new MyServiceClient(mContext);
-
- final Bundle args = InstrumentationRegistry.getArguments();
- mCustomUrl = args.getString(ARG_CONNECTION_CHECK_CUSTOM_URL);
- if (mCustomUrl != null) {
- Log.d(TAG, "Using custom URL " + mCustomUrl + " for network checks");
- }
-
- final int bindPriorityFlags;
- if (Boolean.valueOf(args.getString(ARG_WAIVE_BIND_PRIORITY, "false"))) {
- bindPriorityFlags = Context.BIND_WAIVE_PRIORITY;
- } else {
- bindPriorityFlags = Context.BIND_NOT_FOREGROUND;
- }
- mServiceClient.bind(bindPriorityFlags);
-
- mPowerManager = mContext.getSystemService(PowerManager.class);
- executeShellCommand("cmd netpolicy start-watching " + mUid);
- // Some of the test cases assume that Data saver mode is initially disabled, which might not
- // always be the case. Therefore, explicitly disable it before running the tests.
- // Invoke setRestrictBackgroundInternal() directly instead of going through
- // setRestrictBackground(), as some devices do not fully support the Data saver mode but
- // still have certain parts of it enabled by default.
- setRestrictBackgroundInternal(false);
- setAppIdle(false);
- mLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
-
- Log.i(TAG, "Apps status:\n"
- + "\ttest app: uid=" + mMyUid + ", state=" + getProcessStateByUid(mMyUid) + "\n"
- + "\tapp2: uid=" + mUid + ", state=" + getProcessStateByUid(mUid));
- }
-
- protected void tearDown() throws Exception {
- executeShellCommand("cmd netpolicy stop-watching");
- mServiceClient.unbind();
- final PowerManager.WakeLock lock = mLock;
- if (null != lock && lock.isHeld()) lock.release();
- }
-
- /**
- * Check if the feature blocking network for top_sleeping and lower priority proc-states is
- * enabled. This is a manual check because the feature flag infrastructure may not be available
- * in all the branches that will get this code.
- * TODO: b/322115994 - Use @RequiresFlagsEnabled with
- * Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE once the tests are moved to cts.
- */
- protected boolean isNetworkBlockedForTopSleepingAndAbove() {
- if (!SdkLevel.isAtLeastV()) {
- return false;
- }
- final String output = executeShellCommand("device_config get backstage_power"
- + " com.android.server.net.network_blocked_for_top_sleeping_and_above");
- return Boolean.parseBoolean(output);
- }
-
- /**
- * Check if the flag to use different delays for sensitive proc-states is enabled.
- * This is a manual check because the feature flag infrastructure may not be available
- * in all the branches that will get this code.
- * TODO: b/322115994 - Use @RequiresFlagsEnabled with
- * Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN once the tests are moved to cts.
- */
- private boolean useDifferentDelaysForBackgroundChain() {
- if (!SdkLevel.isAtLeastV()) {
- return false;
- }
- final String output = executeShellCommand("device_config get backstage_power"
- + " com.android.server.net.use_different_delays_for_background_chain");
- return Boolean.parseBoolean(output);
- }
-
- protected int getUid(String packageName) throws Exception {
- return mContext.getPackageManager().getPackageUid(packageName, 0);
- }
-
- protected void assertRestrictBackgroundChangedReceived(int expectedCount) throws Exception {
- assertRestrictBackgroundChangedReceived(DYNAMIC_RECEIVER, expectedCount);
- assertRestrictBackgroundChangedReceived(MANIFEST_RECEIVER, 0);
- }
-
- protected void assertRestrictBackgroundChangedReceived(String receiverName, int expectedCount)
- throws Exception {
- int attempts = 0;
- int count = 0;
- final int maxAttempts = 5;
- do {
- attempts++;
- count = getNumberBroadcastsReceived(receiverName, ACTION_RESTRICT_BACKGROUND_CHANGED);
- assertFalse("Expected count " + expectedCount + " but actual is " + count,
- count > expectedCount);
- if (count == expectedCount) {
- break;
- }
- Log.d(TAG, "Expecting count " + expectedCount + " but actual is " + count + " after "
- + attempts + " attempts; sleeping "
- + SLEEP_TIME_SEC + " seconds before trying again");
- // No sleep after the last turn
- if (attempts <= maxAttempts) {
- SystemClock.sleep(SLEEP_TIME_SEC * SECOND_IN_MS);
- }
- } while (attempts <= maxAttempts);
- assertEquals("Number of expected broadcasts for " + receiverName + " not reached after "
- + maxAttempts * SLEEP_TIME_SEC + " seconds", expectedCount, count);
- }
-
- protected void assertSnoozeWarningNotReceived() throws Exception {
- // Wait for a while to take broadcast queue delays into account
- SystemClock.sleep(BROADCAST_TIMEOUT_MS);
- assertEquals(0, getNumberBroadcastsReceived(DYNAMIC_RECEIVER, ACTION_SNOOZE_WARNING));
- }
-
- protected String sendOrderedBroadcast(Intent intent) throws Exception {
- return sendOrderedBroadcast(intent, ORDERED_BROADCAST_TIMEOUT_MS);
- }
-
- protected String sendOrderedBroadcast(Intent intent, int timeoutMs) throws Exception {
- final LinkedBlockingQueue<String> result = new LinkedBlockingQueue<>(1);
- Log.d(TAG, "Sending ordered broadcast: " + intent);
- mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final String resultData = getResultData();
- if (resultData == null) {
- Log.e(TAG, "Received null data from ordered intent");
- // Offer an empty string so that the code waiting for the result can return.
- result.offer(EMPTY_STRING);
- return;
- }
- result.offer(resultData);
- }
- }, null, 0, null, null);
-
- final String resultData = result.poll(timeoutMs, TimeUnit.MILLISECONDS);
- Log.d(TAG, "Ordered broadcast response after " + timeoutMs + "ms: " + resultData );
- return resultData;
- }
-
- protected int getNumberBroadcastsReceived(String receiverName, String action) throws Exception {
- return mServiceClient.getCounters(receiverName, action);
- }
-
- protected void assertRestrictBackgroundStatus(int expectedStatus) throws Exception {
- final String status = mServiceClient.getRestrictBackgroundStatus();
- assertNotNull("didn't get API status from app2", status);
- assertEquals(restrictBackgroundValueToString(expectedStatus),
- restrictBackgroundValueToString(Integer.parseInt(status)));
- }
-
- /**
- * @deprecated The definition of "background" can be ambiguous. Use separate calls to
- * {@link #assertProcessStateBelow(int)} with
- * {@link #assertNetworkAccess(boolean, boolean, String)} to be explicit, instead.
- */
- @Deprecated
- protected void assertBackgroundNetworkAccess(boolean expectAllowed) throws Exception {
- assertProcessStateBelow(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
- assertNetworkAccess(expectAllowed, false, null);
- }
-
- protected void assertTopNetworkAccess(boolean expectAllowed) throws Exception {
- assertTopState();
- assertNetworkAccess(expectAllowed, true /* needScreenOn */);
- }
-
- protected void assertForegroundServiceNetworkAccess() throws Exception {
- assertForegroundServiceState();
- assertNetworkAccess(true /* expectAvailable */, false /* needScreenOn */);
- }
-
- /**
- * Asserts that an app always have access while on foreground or running a foreground service.
- *
- * <p>This method will launch an activity, a foreground service to make
- * the assertion, but will finish the activity / stop the service afterwards.
- */
- protected void assertsForegroundAlwaysHasNetworkAccess() throws Exception{
- // Checks foreground first.
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
- finishActivity();
-
- // Then foreground service
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
- stopForegroundService();
- }
-
- protected void assertExpeditedJobHasNetworkAccess() throws Exception {
- launchComponentAndAssertNetworkAccess(TYPE_EXPEDITED_JOB);
- finishExpeditedJob();
- }
-
- protected void assertExpeditedJobHasNoNetworkAccess() throws Exception {
- launchComponentAndAssertNetworkAccess(TYPE_EXPEDITED_JOB, false);
- finishExpeditedJob();
- }
-
- /**
- * Asserts that the process state of the test app is below, in priority, to the given
- * {@link android.app.ActivityManager.ProcessState}.
- */
- protected final void assertProcessStateBelow(int processState) throws Exception {
- assertProcessState(ps -> ps.state > processState, null);
- }
-
- protected final void assertTopState() throws Exception {
- assertProcessState(ps -> ps.state == PROCESS_STATE_TOP, () -> turnScreenOn());
- }
-
- protected final void assertForegroundServiceState() throws Exception {
- assertProcessState(ps -> ps.state == PROCESS_STATE_FOREGROUND_SERVICE, null);
- }
-
- private void assertProcessState(Predicate<ProcessState> statePredicate,
- ThrowingRunnable onRetry) throws Exception {
- final int maxTries = 30;
- ProcessState state = null;
- for (int i = 1; i <= maxTries; i++) {
- if (onRetry != null) {
- onRetry.run();
- }
- state = getProcessStateByUid(mUid);
- Log.v(TAG, "assertProcessState(): status for app2 (" + mUid + ") on attempt #" + i
- + ": " + state);
- if (statePredicate.test(state)) {
- return;
- }
- Log.i(TAG, "App not in desired process state on attempt #" + i
- + "; sleeping 1s before trying again");
- if (i < maxTries) {
- SystemClock.sleep(SECOND_IN_MS);
- }
- }
- fail("App2 (" + mUid + ") is not in the desired process state after " + maxTries
- + " attempts: " + state);
- }
-
- /**
- * Asserts whether the active network is available or not. If the network is unavailable, also
- * checks whether it is blocked by the expected error.
- *
- * @param expectAllowed expect background network access to be allowed or not.
- * @param expectedUnavailableError the expected error when {@code expectAllowed} is false. It's
- * meaningful only when the {@code expectAllowed} is 'false'.
- * Throws an IllegalArgumentException when {@code expectAllowed}
- * is true and this parameter is not null. When the
- * {@code expectAllowed} is 'false' and this parameter is null,
- * this function does not compare error type of the networking
- * access failure.
- */
- protected void assertNetworkAccess(boolean expectAllowed, String expectedUnavailableError)
- throws Exception {
- if (expectAllowed && expectedUnavailableError != null) {
- throw new IllegalArgumentException("expectedUnavailableError is not null");
- }
- assertNetworkAccess(expectAllowed, false, expectedUnavailableError);
- }
-
- /**
- * Asserts whether the active network is available or not.
- */
- private void assertNetworkAccess(boolean expectAvailable, boolean needScreenOn)
- throws Exception {
- assertNetworkAccess(expectAvailable, needScreenOn, null);
- }
-
- private void assertNetworkAccess(boolean expectAvailable, boolean needScreenOn,
- @Nullable final String expectedUnavailableError) throws Exception {
- final int maxTries = 5;
- String error = null;
- int timeoutMs = 500;
-
- for (int i = 1; i <= maxTries; i++) {
- error = checkNetworkAccess(expectAvailable, expectedUnavailableError);
-
- if (error == null) return;
-
- // TODO: ideally, it should retry only when it cannot connect to an external site,
- // or no retry at all! But, currently, the initial change fails almost always on
- // battery saver tests because the netd changes are made asynchronously.
- // Once b/27803922 is fixed, this retry mechanism should be revisited.
-
- Log.w(TAG, "Network status didn't match for expectAvailable=" + expectAvailable
- + " on attempt #" + i + ": " + error + "\n"
- + "Sleeping " + timeoutMs + "ms before trying again");
- if (needScreenOn) {
- turnScreenOn();
- }
- // No sleep after the last turn
- if (i < maxTries) {
- SystemClock.sleep(timeoutMs);
- }
- // Exponential back-off.
- timeoutMs = Math.min(timeoutMs*2, NETWORK_TIMEOUT_MS);
- }
- fail("Invalid state for " + mUid + "; expectAvailable=" + expectAvailable + " after "
- + maxTries + " attempts.\nLast error: " + error);
- }
-
- /**
- * Asserts whether the network is blocked by accessing bpf maps if command-line tool supports.
- */
- void assertNetworkAccessBlockedByBpf(boolean expectBlocked, int uid, boolean metered) {
- final String result;
- try {
- result = executeShellCommand(
- "cmd network_stack is-uid-networking-blocked " + uid + " " + metered);
- } catch (AssertionError e) {
- // If NetworkStack is too old to support this command, ignore and continue
- // this test to verify other parts.
- if (e.getMessage().contains("No shell command implementation.")) {
- return;
- }
- throw e;
- }
-
- // Tethering module is too old.
- if (result.contains("API is unsupported")) {
- return;
- }
-
- assertEquals(expectBlocked, parseBooleanOrThrow(result.trim()));
- }
-
- /**
- * Similar to {@link Boolean#parseBoolean} but throws when the input
- * is unexpected instead of returning false.
- */
- private static boolean parseBooleanOrThrow(@NonNull String s) {
- // Don't use Boolean.parseBoolean
- if ("true".equalsIgnoreCase(s)) return true;
- if ("false".equalsIgnoreCase(s)) return false;
- throw new IllegalArgumentException("Unexpected: " + s);
- }
-
- /**
- * Checks whether the network is available as expected.
- *
- * @return error message with the mismatch (or empty if assertion passed).
- */
- private String checkNetworkAccess(boolean expectAvailable,
- @Nullable final String expectedUnavailableError) throws Exception {
- final NetworkCheckResult checkResult = mServiceClient.checkNetworkStatus(mCustomUrl);
- return checkForAvailabilityInNetworkCheckResult(checkResult, expectAvailable,
- expectedUnavailableError);
- }
-
- private String checkForAvailabilityInNetworkCheckResult(NetworkCheckResult networkCheckResult,
- boolean expectAvailable, @Nullable final String expectedUnavailableError) {
- assertNotNull("NetworkCheckResult from app2 is null", networkCheckResult);
-
- final NetworkInfo networkInfo = networkCheckResult.networkInfo;
- assertNotNull("NetworkInfo from app2 is null", networkInfo);
-
- final State state = networkInfo.getState();
- final DetailedState detailedState = networkInfo.getDetailedState();
-
- final boolean connected = networkCheckResult.connected;
- final String connectionCheckDetails = networkCheckResult.details;
-
- final StringBuilder errors = new StringBuilder();
- final State expectedState;
- final DetailedState expectedDetailedState;
- if (expectAvailable) {
- expectedState = State.CONNECTED;
- expectedDetailedState = DetailedState.CONNECTED;
- } else {
- expectedState = State.DISCONNECTED;
- expectedDetailedState = DetailedState.BLOCKED;
- }
-
- if (expectAvailable != connected) {
- errors.append(String.format("External site connection failed: expected %s, got %s\n",
- expectAvailable, connected));
- }
- if (expectedState != state || expectedDetailedState != detailedState) {
- errors.append(String.format("Connection state mismatch: expected %s/%s, got %s/%s\n",
- expectedState, expectedDetailedState, state, detailedState));
- } else if (!expectAvailable && (expectedUnavailableError != null)
- && !connectionCheckDetails.contains(expectedUnavailableError)) {
- errors.append("Connection unavailable reason mismatch: expected "
- + expectedUnavailableError + "\n");
- }
-
- if (errors.length() > 0) {
- errors.append("\tnetworkInfo: " + networkInfo + "\n");
- errors.append("\tconnectionCheckDetails: " + connectionCheckDetails + "\n");
- }
- return errors.length() == 0 ? null : errors.toString();
- }
-
- /**
- * Runs a Shell command which is not expected to generate output.
- */
- protected void executeSilentShellCommand(String command) {
- final String result = executeShellCommand(command);
- assertTrue("Command '" + command + "' failed: " + result, result.trim().isEmpty());
- }
-
- /**
- * Asserts the result of a command, wait and re-running it a couple times if necessary.
- */
- protected void assertDelayedShellCommand(String command, final String expectedResult)
- throws Exception {
- assertDelayedShellCommand(command, 5, 1, expectedResult);
- }
-
- protected void assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds,
- final String expectedResult) throws Exception {
- assertDelayedShellCommand(command, maxTries, napTimeSeconds, new ExpectResultChecker() {
-
- @Override
- public boolean isExpected(String result) {
- return expectedResult.equals(result);
- }
-
- @Override
- public String getExpected() {
- return expectedResult;
- }
- });
- }
-
- protected void assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds,
- ExpectResultChecker checker) throws Exception {
- String result = "";
- for (int i = 1; i <= maxTries; i++) {
- result = executeShellCommand(command).trim();
- if (checker.isExpected(result)) return;
- Log.v(TAG, "Command '" + command + "' returned '" + result + " instead of '"
- + checker.getExpected() + "' on attempt #" + i
- + "; sleeping " + napTimeSeconds + "s before trying again");
- // No sleep after the last turn
- if (i < maxTries) {
- SystemClock.sleep(napTimeSeconds * SECOND_IN_MS);
- }
- }
- fail("Command '" + command + "' did not return '" + checker.getExpected() + "' after "
- + maxTries
- + " attempts. Last result: '" + result + "'");
- }
-
- protected void addRestrictBackgroundWhitelist(int uid) throws Exception {
- executeShellCommand("cmd netpolicy add restrict-background-whitelist " + uid);
- assertRestrictBackgroundWhitelist(uid, true);
- // UID policies live by the Highlander rule: "There can be only one".
- // Hence, if app is whitelisted, it should not be blacklisted.
- assertRestrictBackgroundBlacklist(uid, false);
- }
-
- protected void removeRestrictBackgroundWhitelist(int uid) throws Exception {
- executeShellCommand("cmd netpolicy remove restrict-background-whitelist " + uid);
- assertRestrictBackgroundWhitelist(uid, false);
- }
-
- protected void assertRestrictBackgroundWhitelist(int uid, boolean expected) throws Exception {
- assertRestrictBackground("restrict-background-whitelist", uid, expected);
- }
-
- protected void addRestrictBackgroundBlacklist(int uid) throws Exception {
- executeShellCommand("cmd netpolicy add restrict-background-blacklist " + uid);
- assertRestrictBackgroundBlacklist(uid, true);
- // UID policies live by the Highlander rule: "There can be only one".
- // Hence, if app is blacklisted, it should not be whitelisted.
- assertRestrictBackgroundWhitelist(uid, false);
- }
-
- protected void removeRestrictBackgroundBlacklist(int uid) throws Exception {
- executeShellCommand("cmd netpolicy remove restrict-background-blacklist " + uid);
- assertRestrictBackgroundBlacklist(uid, false);
- }
-
- protected void assertRestrictBackgroundBlacklist(int uid, boolean expected) throws Exception {
- assertRestrictBackground("restrict-background-blacklist", uid, expected);
- }
-
- protected void addAppIdleWhitelist(int uid) throws Exception {
- executeShellCommand("cmd netpolicy add app-idle-whitelist " + uid);
- assertAppIdleWhitelist(uid, true);
- }
-
- protected void removeAppIdleWhitelist(int uid) throws Exception {
- executeShellCommand("cmd netpolicy remove app-idle-whitelist " + uid);
- assertAppIdleWhitelist(uid, false);
- }
-
- protected void assertAppIdleWhitelist(int uid, boolean expected) throws Exception {
- assertRestrictBackground("app-idle-whitelist", uid, expected);
- }
-
- private void assertRestrictBackground(String list, int uid, boolean expected) throws Exception {
- final int maxTries = 5;
- boolean actual = false;
- final String expectedUid = Integer.toString(uid);
- String uids = "";
- for (int i = 1; i <= maxTries; i++) {
- final String output =
- executeShellCommand("cmd netpolicy list " + list);
- uids = output.split(":")[1];
- for (String candidate : uids.split(" ")) {
- actual = candidate.trim().equals(expectedUid);
- if (expected == actual) {
- return;
- }
- }
- Log.v(TAG, list + " check for uid " + uid + " doesn't match yet (expected "
- + expected + ", got " + actual + "); sleeping 1s before polling again");
- // No sleep after the last turn
- if (i < maxTries) {
- SystemClock.sleep(SECOND_IN_MS);
- }
- }
- fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual
- + ". Full list: " + uids);
- }
-
- protected void addTempPowerSaveModeWhitelist(String packageName, long duration)
- throws Exception {
- Log.i(TAG, "Adding pkg " + packageName + " to temp-power-save-mode whitelist");
- executeShellCommand("dumpsys deviceidle tempwhitelist -d " + duration + " " + packageName);
- }
-
- protected void assertPowerSaveModeWhitelist(String packageName, boolean expected)
- throws Exception {
- // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
- // need to use netpolicy for whitelisting
- assertDelayedShellCommand("dumpsys deviceidle whitelist =" + packageName,
- Boolean.toString(expected));
- }
-
- protected void addPowerSaveModeWhitelist(String packageName) throws Exception {
- Log.i(TAG, "Adding package " + packageName + " to power-save-mode whitelist");
- // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
- // need to use netpolicy for whitelisting
- executeShellCommand("dumpsys deviceidle whitelist +" + packageName);
- assertPowerSaveModeWhitelist(packageName, true);
- }
-
- protected void removePowerSaveModeWhitelist(String packageName) throws Exception {
- Log.i(TAG, "Removing package " + packageName + " from power-save-mode whitelist");
- // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
- // need to use netpolicy for whitelisting
- executeShellCommand("dumpsys deviceidle whitelist -" + packageName);
- assertPowerSaveModeWhitelist(packageName, false);
- }
-
- protected void assertPowerSaveModeExceptIdleWhitelist(String packageName, boolean expected)
- throws Exception {
- // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
- // need to use netpolicy for whitelisting
- assertDelayedShellCommand("dumpsys deviceidle except-idle-whitelist =" + packageName,
- Boolean.toString(expected));
- }
-
- protected void addPowerSaveModeExceptIdleWhitelist(String packageName) throws Exception {
- Log.i(TAG, "Adding package " + packageName + " to power-save-mode-except-idle whitelist");
- // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
- // need to use netpolicy for whitelisting
- executeShellCommand("dumpsys deviceidle except-idle-whitelist +" + packageName);
- assertPowerSaveModeExceptIdleWhitelist(packageName, true);
- }
-
- protected void removePowerSaveModeExceptIdleWhitelist(String packageName) throws Exception {
- Log.i(TAG, "Removing package " + packageName
- + " from power-save-mode-except-idle whitelist");
- // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
- // need to use netpolicy for whitelisting
- executeShellCommand("dumpsys deviceidle except-idle-whitelist reset");
- assertPowerSaveModeExceptIdleWhitelist(packageName, false);
- }
-
- protected void turnBatteryOn() throws Exception {
- executeSilentShellCommand("cmd battery unplug");
- executeSilentShellCommand("cmd battery set status "
- + BatteryManager.BATTERY_STATUS_DISCHARGING);
- assertBatteryState(false);
- }
-
- protected void turnBatteryOff() throws Exception {
- executeSilentShellCommand("cmd battery set ac " + BATTERY_PLUGGED_ANY);
- executeSilentShellCommand("cmd battery set level 100");
- executeSilentShellCommand("cmd battery set status "
- + BatteryManager.BATTERY_STATUS_CHARGING);
- assertBatteryState(true);
- }
-
- protected void resetBatteryState() {
- BatteryUtils.runDumpsysBatteryReset();
- }
-
- private void assertBatteryState(boolean pluggedIn) throws Exception {
- final long endTime = SystemClock.elapsedRealtime() + BATTERY_STATE_TIMEOUT_MS;
- while (isDevicePluggedIn() != pluggedIn && SystemClock.elapsedRealtime() <= endTime) {
- Thread.sleep(BATTERY_STATE_CHECK_INTERVAL_MS);
- }
- if (isDevicePluggedIn() != pluggedIn) {
- fail("Timed out waiting for the plugged-in state to change,"
- + " expected pluggedIn: " + pluggedIn);
- }
- }
-
- private boolean isDevicePluggedIn() {
- final Intent batteryIntent = mContext.registerReceiver(null, BATTERY_CHANGED_FILTER);
- return batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) > 0;
- }
-
- protected void turnScreenOff() throws Exception {
- if (!mLock.isHeld()) mLock.acquire();
- executeSilentShellCommand("input keyevent KEYCODE_SLEEP");
- }
-
- protected void turnScreenOn() throws Exception {
- executeSilentShellCommand("input keyevent KEYCODE_WAKEUP");
- if (mLock.isHeld()) mLock.release();
- executeSilentShellCommand("wm dismiss-keyguard");
- }
-
- protected void setBatterySaverMode(boolean enabled) throws Exception {
- if (!isBatterySaverSupported()) {
- return;
- }
- Log.i(TAG, "Setting Battery Saver Mode to " + enabled);
- if (enabled) {
- turnBatteryOn();
- AmUtils.waitForBroadcastBarrier();
- executeSilentShellCommand("cmd power set-mode 1");
- } else {
- executeSilentShellCommand("cmd power set-mode 0");
- turnBatteryOff();
- AmUtils.waitForBroadcastBarrier();
- }
- }
-
- protected void setDozeMode(boolean enabled) throws Exception {
- if (!isDozeModeSupported()) {
- return;
- }
-
- Log.i(TAG, "Setting Doze Mode to " + enabled);
- if (enabled) {
- turnBatteryOn();
- turnScreenOff();
- executeShellCommand("dumpsys deviceidle force-idle deep");
- } else {
- turnScreenOn();
- turnBatteryOff();
- executeShellCommand("dumpsys deviceidle unforce");
- }
- assertDozeMode(enabled);
- }
-
- protected void assertDozeMode(boolean enabled) throws Exception {
- assertDelayedShellCommand("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE");
- }
-
- protected void stopApp() {
- executeSilentShellCommand("am stop-app " + TEST_APP2_PKG);
- }
-
- protected void setAppIdle(boolean isIdle) throws Exception {
- setAppIdleNoAssert(isIdle);
- assertAppIdle(isIdle);
- }
-
- protected void setAppIdleNoAssert(boolean isIdle) throws Exception {
- if (!isAppStandbySupported()) {
- return;
- }
- Log.i(TAG, "Setting app idle to " + isIdle);
- final String bucketName = isIdle ? "rare" : "active";
- executeSilentShellCommand("am set-standby-bucket " + TEST_APP2_PKG + " " + bucketName);
- }
-
- protected void assertAppIdle(boolean isIdle) throws Exception {
- try {
- assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG,
- 30 /* maxTries */, 1 /* napTimeSeconds */, "Idle=" + isIdle);
- } catch (Throwable e) {
- throw e;
- }
- }
-
- /**
- * Starts a service that will register a broadcast receiver to receive
- * {@code RESTRICT_BACKGROUND_CHANGE} intents.
- * <p>
- * The service must run in a separate app because otherwise it would be killed every time
- * {@link #runDeviceTests(String, String)} is executed.
- */
- protected void registerBroadcastReceiver() throws Exception {
- mServiceClient.registerBroadcastReceiver();
-
- final Intent intent = new Intent(ACTION_RECEIVER_READY)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- // Wait until receiver is ready.
- final int maxTries = 10;
- for (int i = 1; i <= maxTries; i++) {
- final String message = sendOrderedBroadcast(intent, SECOND_IN_MS * 4);
- Log.d(TAG, "app2 receiver acked: " + message);
- if (message != null) {
- return;
- }
- Log.v(TAG, "app2 receiver is not ready yet; sleeping 1s before polling again");
- // No sleep after the last turn
- if (i < maxTries) {
- SystemClock.sleep(SECOND_IN_MS);
- }
- }
- fail("app2 receiver is not ready in " + mUid);
- }
-
- protected void registerNetworkCallback(final NetworkRequest request, INetworkCallback cb)
- throws Exception {
- Log.i(TAG, "Registering network callback for request: " + request);
- mServiceClient.registerNetworkCallback(request, cb);
- }
-
- protected void unregisterNetworkCallback() throws Exception {
- mServiceClient.unregisterNetworkCallback();
- }
-
- /**
- * Registers a {@link NotificationListenerService} implementation that will execute the
- * notification actions right after the notification is sent.
- */
- protected void registerNotificationListenerService() throws Exception {
- executeShellCommand("cmd notification allow_listener "
- + MyNotificationListenerService.getId());
- final NotificationManager nm = mContext.getSystemService(NotificationManager.class);
- final ComponentName listenerComponent = MyNotificationListenerService.getComponentName();
- assertTrue(listenerComponent + " has not been granted access",
- nm.isNotificationListenerAccessGranted(listenerComponent));
- }
-
- protected void setPendingIntentAllowlistDuration(long durationMs) {
- mDeviceIdleDeviceConfigStateHelper.set("notification_allowlist_duration_ms",
- String.valueOf(durationMs));
- }
-
- protected void resetDeviceIdleSettings() {
- mDeviceIdleDeviceConfigStateHelper.restoreOriginalValues();
- }
-
- protected void launchActivity() throws Exception {
- turnScreenOn();
- final CountDownLatch latch = new CountDownLatch(1);
- final Intent launchIntent = getIntentForComponent(TYPE_COMPONENT_ACTIVTIY);
- final RemoteCallback callback = new RemoteCallback(result -> latch.countDown());
- launchIntent.putExtra(Intent.EXTRA_REMOTE_CALLBACK, callback);
- mContext.startActivity(launchIntent);
- // There might be a race when app2 is launched but ACTION_FINISH_ACTIVITY has not registered
- // before test calls finishActivity(). When the issue is happened, there is no way to fix
- // it, so have a callback design to make sure that the app is launched completely and
- // ACTION_FINISH_ACTIVITY will be registered before leaving this method.
- if (!latch.await(LAUNCH_ACTIVITY_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
- fail("Timed out waiting for launching activity");
- }
- }
-
- protected void launchComponentAndAssertNetworkAccess(int type) throws Exception {
- launchComponentAndAssertNetworkAccess(type, true);
- }
-
- protected void launchComponentAndAssertNetworkAccess(int type, boolean expectAvailable)
- throws Exception {
- if (type == TYPE_COMPONENT_FOREGROUND_SERVICE) {
- startForegroundService();
- assertForegroundServiceNetworkAccess();
- } else if (type == TYPE_COMPONENT_ACTIVTIY) {
- turnScreenOn();
- final CountDownLatch latch = new CountDownLatch(1);
- final Intent launchIntent = getIntentForComponent(type);
- final Bundle extras = new Bundle();
- final AtomicReference<Pair<Integer, NetworkCheckResult>> result =
- new AtomicReference<>();
- extras.putBinder(KEY_NETWORK_STATE_OBSERVER, getNewNetworkStateObserver(latch, result));
- extras.putBoolean(KEY_SKIP_VALIDATION_CHECKS, !expectAvailable);
- extras.putString(KEY_CUSTOM_URL, mCustomUrl);
- launchIntent.putExtras(extras);
- mContext.startActivity(launchIntent);
- if (latch.await(ACTIVITY_NETWORK_STATE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
- final int resultCode = result.get().first;
- final NetworkCheckResult networkCheckResult = result.get().second;
- if (resultCode == INetworkStateObserver.RESULT_SUCCESS_NETWORK_STATE_CHECKED) {
- final String error = checkForAvailabilityInNetworkCheckResult(
- networkCheckResult, expectAvailable,
- null /* expectedUnavailableError */);
- if (error != null) {
- fail("Network is not available for activity in app2 (" + mUid + "): "
- + error);
- }
- } else if (resultCode == INetworkStateObserver.RESULT_ERROR_UNEXPECTED_PROC_STATE) {
- Log.d(TAG, networkCheckResult.details);
- // App didn't come to foreground when the activity is started, so try again.
- assertTopNetworkAccess(true);
- } else {
- fail("Unexpected resultCode=" + resultCode
- + "; networkCheckResult=[" + networkCheckResult + "]");
- }
- } else {
- fail("Timed out waiting for network availability status from app2's activity ("
- + mUid + ")");
- }
- } else if (type == TYPE_EXPEDITED_JOB) {
- final Bundle extras = new Bundle();
- final AtomicReference<Pair<Integer, NetworkCheckResult>> result =
- new AtomicReference<>();
- final CountDownLatch latch = new CountDownLatch(1);
- extras.putBinder(KEY_NETWORK_STATE_OBSERVER, getNewNetworkStateObserver(latch, result));
- extras.putBoolean(KEY_SKIP_VALIDATION_CHECKS, !expectAvailable);
- extras.putString(KEY_CUSTOM_URL, mCustomUrl);
- final JobInfo jobInfo = new JobInfo.Builder(TEST_JOB_ID, TEST_JOB_COMPONENT)
- .setExpedited(true)
- .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
- .setTransientExtras(extras)
- .build();
- assertEquals("Error scheduling " + jobInfo,
- RESULT_SUCCESS, mServiceClient.scheduleJob(jobInfo));
- forceRunJob(TEST_APP2_PKG, TEST_JOB_ID);
- if (latch.await(JOB_NETWORK_STATE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
- final int resultCode = result.get().first;
- final NetworkCheckResult networkCheckResult = result.get().second;
- if (resultCode == INetworkStateObserver.RESULT_SUCCESS_NETWORK_STATE_CHECKED) {
- final String error = checkForAvailabilityInNetworkCheckResult(
- networkCheckResult, expectAvailable,
- null /* expectedUnavailableError */);
- if (error != null) {
- Log.d(TAG, "Network state is unexpected, checking again. " + error);
- // Right now we could end up in an unexpected state if expedited job
- // doesn't have network access immediately after starting, so check again.
- assertNetworkAccess(expectAvailable, false /* needScreenOn */);
- }
- } else {
- fail("Unexpected resultCode=" + resultCode
- + "; networkCheckResult=[" + networkCheckResult + "]");
- }
- } else {
- fail("Timed out waiting for network availability status from app2's expedited job ("
- + mUid + ")");
- }
- } else {
- throw new IllegalArgumentException("Unknown type: " + type);
- }
- }
-
- 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);
- assertForegroundServiceState();
- }
-
- private Intent getIntentForComponent(int type) {
- final Intent intent = new Intent();
- if (type == TYPE_COMPONENT_ACTIVTIY) {
- intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_ACTIVITY_CLASS))
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
- } else if (type == TYPE_COMPONENT_FOREGROUND_SERVICE) {
- intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_SERVICE_CLASS))
- .setFlags(1);
- } else {
- fail("Unknown type: " + type);
- }
- return intent;
- }
-
- protected void stopForegroundService() throws Exception {
- executeShellCommand(String.format("am startservice -f 2 %s/%s",
- TEST_APP2_PKG, TEST_APP2_SERVICE_CLASS));
- // NOTE: cannot assert state because it depends on whether activity was on top before.
- }
-
- private Binder getNewNetworkStateObserver(final CountDownLatch latch,
- final AtomicReference<Pair<Integer, NetworkCheckResult>> result) {
- return new INetworkStateObserver.Stub() {
- @Override
- public void onNetworkStateChecked(int resultCode,
- NetworkCheckResult networkCheckResult) {
- result.set(Pair.create(resultCode, networkCheckResult));
- latch.countDown();
- }
- };
- }
-
- /**
- * Finishes an activity on app2 so its process is demoted from foreground status.
- */
- protected void finishActivity() throws Exception {
- final Intent intent = new Intent(ACTION_FINISH_ACTIVITY)
- .setPackage(TEST_APP2_PKG)
- .setFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- sendOrderedBroadcast(intent);
- }
-
- /**
- * Finishes the expedited job on app2 so its process is demoted from foreground status.
- */
- private void finishExpeditedJob() throws Exception {
- final Intent intent = new Intent(ACTION_FINISH_JOB)
- .setPackage(TEST_APP2_PKG)
- .setFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- sendOrderedBroadcast(intent);
- }
-
- protected void sendNotification(int notificationId, String notificationType) throws Exception {
- Log.d(TAG, "Sending notification broadcast (id=" + notificationId
- + ", type=" + notificationType);
- mServiceClient.sendNotification(notificationId, notificationType);
- }
-
- protected String showToast() {
- final Intent intent = new Intent(ACTION_SHOW_TOAST);
- intent.setPackage(TEST_APP2_PKG);
- Log.d(TAG, "Sending request to show toast");
- try {
- return sendOrderedBroadcast(intent, 3 * SECOND_IN_MS);
- } catch (Exception e) {
- return "";
- }
- }
-
- private ProcessState getProcessStateByUid(int uid) throws Exception {
- return new ProcessState(executeShellCommand("cmd activity get-uid-state " + uid));
- }
-
- private static class ProcessState {
- private final String fullState;
- final int state;
-
- ProcessState(String fullState) {
- this.fullState = fullState;
- try {
- this.state = Integer.parseInt(fullState.split(" ")[0]);
- } catch (Exception e) {
- throw new IllegalArgumentException("Could not parse " + fullState);
- }
- }
-
- @Override
- public String toString() {
- return fullState;
- }
- }
-
- /**
- * Helper class used to assert the result of a Shell command.
- */
- protected static interface ExpectResultChecker {
- /**
- * Checkes whether the result of the command matched the expectation.
- */
- boolean isExpected(String result);
- /**
- * Gets the expected result so it's displayed on log and failure messages.
- */
- String getExpected();
- }
-
- protected void setRestrictedNetworkingMode(boolean enabled) throws Exception {
- executeSilentShellCommand(
- "settings put global restricted_networking_mode " + (enabled ? 1 : 0));
- assertRestrictedNetworkingModeState(enabled);
- }
-
- protected void assertRestrictedNetworkingModeState(boolean enabled) throws Exception {
- assertDelayedShellCommand("cmd netpolicy get restricted-mode",
- "Restricted mode status: " + (enabled ? "enabled" : "disabled"));
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleMeteredTest.java
deleted file mode 100644
index 6b802f6..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleMeteredTest.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
-
-@RequiredProperties({METERED_NETWORK})
-public class AppIdleMeteredTest extends AbstractAppIdleTestCase {
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleNonMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleNonMeteredTest.java
deleted file mode 100644
index 2e725ae..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleNonMeteredTest.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
-
-@RequiredProperties({NON_METERED_NETWORK})
-public class AppIdleNonMeteredTest extends AbstractAppIdleTestCase {
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeMeteredTest.java
deleted file mode 100644
index 2e421f6..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeMeteredTest.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
-
-@RequiredProperties({METERED_NETWORK})
-public class BatterySaverModeMeteredTest extends AbstractBatterySaverModeTestCase {
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeNonMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeNonMeteredTest.java
deleted file mode 100644
index 0be5644..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeNonMeteredTest.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-
-import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
-
-@RequiredProperties({NON_METERED_NETWORK})
-public class BatterySaverModeNonMeteredTest extends AbstractBatterySaverModeTestCase {
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ConnOnActivityStartTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ConnOnActivityStartTest.java
deleted file mode 100644
index bfccce9..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ConnOnActivityStartTest.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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.cts.netpolicy.hostside;
-
-
-import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
-
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.getUiDevice;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.netpolicy.hostside.Property.APP_STANDBY_MODE;
-import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
-import static com.android.cts.netpolicy.hostside.Property.DOZE_MODE;
-import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
-import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
-
-import static org.junit.Assume.assumeTrue;
-
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.android.compatibility.common.util.ThrowingRunnable;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-@RequiredProperties({NON_METERED_NETWORK})
-public class ConnOnActivityStartTest extends AbstractRestrictBackgroundNetworkTestCase {
- private static final int TEST_ITERATION_COUNT = 5;
-
- @Before
- public final void setUp() throws Exception {
- super.setUp();
- resetDeviceState();
- }
-
- @After
- public final void tearDown() throws Exception {
- super.tearDown();
- stopApp();
- resetDeviceState();
- }
-
- private void resetDeviceState() throws Exception {
- resetBatteryState();
- setBatterySaverMode(false);
- setRestrictBackground(false);
- setAppIdle(false);
- setDozeMode(false);
- }
-
-
- @Test
- @RequiredProperties({BATTERY_SAVER_MODE})
- public void testStartActivity_batterySaver() throws Exception {
- setBatterySaverMode(true);
- assertNetworkAccess(false, null);
- assertLaunchedActivityHasNetworkAccess("testStartActivity_batterySaver", null);
- }
-
- @Test
- @RequiredProperties({DATA_SAVER_MODE, METERED_NETWORK})
- public void testStartActivity_dataSaver() throws Exception {
- setRestrictBackground(true);
- assertNetworkAccess(false, null);
- assertLaunchedActivityHasNetworkAccess("testStartActivity_dataSaver", null);
- }
-
- @Test
- @RequiredProperties({DOZE_MODE})
- public void testStartActivity_doze() throws Exception {
- setDozeMode(true);
- assertNetworkAccess(false, null);
- // TODO (235284115): We need to turn on Doze every time before starting
- // the activity.
- assertLaunchedActivityHasNetworkAccess("testStartActivity_doze", null);
- }
-
- @Test
- @RequiredProperties({APP_STANDBY_MODE})
- public void testStartActivity_appStandby() throws Exception {
- turnBatteryOn();
- setAppIdle(true);
- assertNetworkAccess(false, null);
- // TODO (235284115): We need to put the app into app standby mode every
- // time before starting the activity.
- assertLaunchedActivityHasNetworkAccess("testStartActivity_appStandby", null);
- }
-
- @Test
- public void testStartActivity_default() throws Exception {
- assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
- assertLaunchedActivityHasNetworkAccess("testStartActivity_default", () -> {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(mProcessStateTransitionLongDelayMs);
- assertNetworkAccess(false, null);
- });
- }
-
- private void assertLaunchedActivityHasNetworkAccess(String testName,
- ThrowingRunnable onBeginIteration) throws Exception {
- for (int i = 0; i < TEST_ITERATION_COUNT; ++i) {
- if (onBeginIteration != null) {
- onBeginIteration.run();
- }
- Log.i(TAG, testName + " start #" + i);
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
- getUiDevice().pressHome();
- assertProcessStateBelow(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
- Log.i(TAG, testName + " end #" + i);
- }
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataSaverModeTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataSaverModeTest.java
deleted file mode 100644
index 66e0d00..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataSaverModeTest.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
-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.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
-import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
-import static com.android.cts.netpolicy.hostside.Property.NO_DATA_SAVER_MODE;
-
-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;
-
-@RequiredProperties({DATA_SAVER_MODE, METERED_NETWORK})
-@LargeTest
-public class DataSaverModeTest extends AbstractRestrictBackgroundNetworkTestCase {
-
- private static final String[] REQUIRED_WHITELISTED_PACKAGES = {
- "com.android.providers.downloads"
- };
-
- @Before
- public void setUp() throws Exception {
- super.setUp();
-
- // Set initial state.
- setRestrictBackground(false);
- removeRestrictBackgroundWhitelist(mUid);
- removeRestrictBackgroundBlacklist(mUid);
-
- registerBroadcastReceiver();
- assertRestrictBackgroundChangedReceived(0);
- }
-
- @After
- public void tearDown() throws Exception {
- super.tearDown();
-
- setRestrictBackground(false);
- }
-
- @Test
- public void testGetRestrictBackgroundStatus_disabled() throws Exception {
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
-
- // Verify status is always disabled, never whitelisted
- addRestrictBackgroundWhitelist(mUid);
- assertRestrictBackgroundChangedReceived(0);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
- }
-
- @Test
- public void testGetRestrictBackgroundStatus_whitelisted() throws Exception {
- setRestrictBackground(true);
- assertRestrictBackgroundChangedReceived(1);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
-
- addRestrictBackgroundWhitelist(mUid);
- assertRestrictBackgroundChangedReceived(2);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_WHITELISTED);
-
- removeRestrictBackgroundWhitelist(mUid);
- assertRestrictBackgroundChangedReceived(3);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
- }
-
- @Test
- public void testGetRestrictBackgroundStatus_enabled() throws Exception {
- setRestrictBackground(true);
- assertRestrictBackgroundChangedReceived(1);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
-
- // Make sure foreground app doesn't lose access upon enabling Data Saver.
- setRestrictBackground(false);
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
- setRestrictBackground(true);
- assertTopNetworkAccess(true);
-
- // Although it should not have access while the screen is off.
- 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();
- }
- assertTopNetworkAccess(true);
-
- // Goes back to background state.
- finishActivity();
- assertBackgroundNetworkAccess(false);
-
- // Make sure foreground service doesn't lose access upon enabling Data Saver.
- setRestrictBackground(false);
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
- setRestrictBackground(true);
- assertForegroundServiceNetworkAccess();
- stopForegroundService();
- assertBackgroundNetworkAccess(false);
- }
-
- @Test
- public void testGetRestrictBackgroundStatus_blacklisted() throws Exception {
- addRestrictBackgroundBlacklist(mUid);
- assertRestrictBackgroundChangedReceived(1);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertRestrictBackgroundChangedReceived(1);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
-
- // UID policies live by the Highlander rule: "There can be only one".
- // Hence, if app is whitelisted, it should not be blacklisted anymore.
- setRestrictBackground(true);
- assertRestrictBackgroundChangedReceived(2);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
- addRestrictBackgroundWhitelist(mUid);
- assertRestrictBackgroundChangedReceived(3);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_WHITELISTED);
-
- // Check status after removing blacklist.
- // ...re-enables first
- addRestrictBackgroundBlacklist(mUid);
- assertRestrictBackgroundChangedReceived(4);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
- assertsForegroundAlwaysHasNetworkAccess();
- // ... remove blacklist - access's still rejected because Data Saver is on
- removeRestrictBackgroundBlacklist(mUid);
- assertRestrictBackgroundChangedReceived(4);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
- assertsForegroundAlwaysHasNetworkAccess();
- // ... finally, disable Data Saver
- setRestrictBackground(false);
- assertRestrictBackgroundChangedReceived(5);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
- assertsForegroundAlwaysHasNetworkAccess();
- }
-
- @Test
- public void testGetRestrictBackgroundStatus_requiredWhitelistedPackages() throws Exception {
- final StringBuilder error = new StringBuilder();
- for (String packageName : REQUIRED_WHITELISTED_PACKAGES) {
- int uid = -1;
- try {
- uid = getUid(packageName);
- assertRestrictBackgroundWhitelist(uid, true);
- } catch (Throwable t) {
- error.append("\nFailed for '").append(packageName).append("'");
- if (uid > 0) {
- error.append(" (uid ").append(uid).append(")");
- }
- error.append(": ").append(t).append("\n");
- }
- }
- if (error.length() > 0) {
- fail(error.toString());
- }
- }
-
- @RequiredProperties({NO_DATA_SAVER_MODE})
- @CddTest(requirement="7.4.7/C-2-2")
- @Test
- public void testBroadcastNotSentOnUnsupportedDevices() throws Exception {
- setRestrictBackground(true);
- assertRestrictBackgroundChangedReceived(0);
-
- setRestrictBackground(false);
- assertRestrictBackgroundChangedReceived(0);
-
- setRestrictBackground(true);
- assertRestrictBackgroundChangedReceived(0);
- }
-
- private void assertDataSaverStatusOnBackground(int expectedStatus) throws Exception {
- assertRestrictBackgroundStatus(expectedStatus);
- assertBackgroundNetworkAccess(expectedStatus != RESTRICT_BACKGROUND_STATUS_ENABLED);
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataWarningReceiverTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataWarningReceiverTest.java
deleted file mode 100644
index 69ca206..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataWarningReceiverTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.clearSnoozeTimestamps;
-
-import android.content.pm.PackageManager;
-import android.telephony.SubscriptionManager;
-import android.telephony.SubscriptionPlan;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.Direction;
-import androidx.test.uiautomator.UiDevice;
-import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
-
-import com.android.compatibility.common.util.SystemUtil;
-import com.android.compatibility.common.util.UiAutomatorUtils2;
-
-import org.junit.After;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.time.Period;
-import java.time.ZonedDateTime;
-import java.util.Arrays;
-import java.util.List;
-
-public class DataWarningReceiverTest extends AbstractRestrictBackgroundNetworkTestCase {
-
- @Before
- public void setUp() throws Exception {
- super.setUp();
-
- clearSnoozeTimestamps();
- registerBroadcastReceiver();
- turnScreenOn();
- }
-
- @After
- public void tearDown() throws Exception {
- super.tearDown();
- }
-
- @Test
- public void testSnoozeWarningNotReceived() throws Exception {
- Assume.assumeTrue("Feature not supported: " + PackageManager.FEATURE_TELEPHONY,
- mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
- final SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
- final int subId = SubscriptionManager.getDefaultDataSubscriptionId();
- Assume.assumeTrue("Valid subId not found",
- subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-
- setSubPlanOwner(subId, TEST_PKG);
- final List<SubscriptionPlan> originalPlans = sm.getSubscriptionPlans(subId);
- try {
- // In NetworkPolicyManagerService class, we set the data warning bytes to 90% of
- // data limit bytes. So, create the subscription plan in such a way this data warning
- // threshold is already reached.
- final SubscriptionPlan plan = SubscriptionPlan.Builder
- .createRecurring(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"),
- Period.ofMonths(1))
- .setTitle("CTS")
- .setDataLimit(1_000_000_000, SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED)
- .setDataUsage(999_000_000, System.currentTimeMillis())
- .build();
- sm.setSubscriptionPlans(subId, Arrays.asList(plan));
- final UiDevice uiDevice = UiDevice.getInstance(mInstrumentation);
- uiDevice.openNotification();
- try {
- final UiObject2 uiObject = UiAutomatorUtils2.waitFindObject(
- By.text("Data warning"));
- Assume.assumeNotNull(uiObject);
- uiObject.wait(Until.clickable(true), 10_000L);
- uiObject.getParent().swipe(Direction.RIGHT, 1.0f);
- } catch (Throwable t) {
- Assume.assumeNoException(
- "Error occurred while finding and swiping the notification", t);
- }
- assertSnoozeWarningNotReceived();
- uiDevice.pressHome();
- } finally {
- sm.setSubscriptionPlans(subId, originalPlans);
- setSubPlanOwner(subId, "");
- }
- }
-
- private static void setSubPlanOwner(int subId, String packageName) throws Exception {
- SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
- "cmd netpolicy set sub-plan-owner " + subId + " " + packageName);
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsMeteredTest.java
deleted file mode 100644
index 810fd19..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsMeteredTest.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2024 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
-
-@RequiredProperties({METERED_NETWORK})
-public class DefaultRestrictionsMeteredTest extends AbstractDefaultRestrictionsTest {
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsNonMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsNonMeteredTest.java
deleted file mode 100644
index fef546c..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsNonMeteredTest.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2024 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
-
-@RequiredProperties({NON_METERED_NETWORK})
-public class DefaultRestrictionsNonMeteredTest extends AbstractDefaultRestrictionsTest {
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeMeteredTest.java
deleted file mode 100644
index 741dd7e..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeMeteredTest.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
-
-@RequiredProperties({METERED_NETWORK})
-public class DozeModeMeteredTest extends AbstractDozeModeTestCase {
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DumpOnFailureRule.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DumpOnFailureRule.java
deleted file mode 100644
index 2dc6cc4..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DumpOnFailureRule.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2019 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
-import static com.android.cts.netpolicy.hostside.AbstractRestrictBackgroundNetworkTestCase.TEST_APP2_PKG;
-import static com.android.cts.netpolicy.hostside.AbstractRestrictBackgroundNetworkTestCase.TEST_PKG;
-
-import android.os.Environment;
-import android.os.FileUtils;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.uiautomator.UiDevice;
-
-import com.android.compatibility.common.util.OnFailureRule;
-
-import org.junit.AssumptionViolatedException;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-
-public class DumpOnFailureRule extends OnFailureRule {
- private File mDumpDir = new File(Environment.getExternalStorageDirectory(),
- "CtsHostsideNetworkPolicyTests");
-
- @Override
- public void onTestFailure(Statement base, Description description, Throwable throwable) {
- if (throwable instanceof AssumptionViolatedException) {
- final String testName = description.getClassName() + "_" + description.getMethodName();
- Log.d(TAG, "Skipping test " + testName + ": " + throwable);
- return;
- }
-
- prepareDumpRootDir();
- final String shortenedTestName = getShortenedTestName(description);
- final File dumpFile = new File(mDumpDir, "dump-" + shortenedTestName);
- Log.i(TAG, "Dumping debug info for " + description + ": " + dumpFile.getPath());
- try (FileOutputStream out = new FileOutputStream(dumpFile)) {
- for (String cmd : new String[] {
- "dumpsys netpolicy",
- "dumpsys network_management",
- "dumpsys usagestats " + TEST_PKG + " " + TEST_APP2_PKG,
- "dumpsys usagestats appstandby",
- "dumpsys connectivity trafficcontroller",
- "dumpsys netd trafficcontroller",
- "dumpsys platform_compat", // TODO (b/279829773): Remove this dump
- "dumpsys jobscheduler " + TEST_APP2_PKG, // TODO (b/288220398): Remove this dump
- }) {
- dumpCommandOutput(out, cmd);
- }
- } catch (FileNotFoundException e) {
- Log.e(TAG, "Error opening file: " + dumpFile, e);
- } catch (IOException e) {
- Log.e(TAG, "Error closing file: " + dumpFile, e);
- }
- final UiDevice uiDevice = UiDevice.getInstance(
- InstrumentationRegistry.getInstrumentation());
- final File screenshotFile = new File(mDumpDir, "sc-" + shortenedTestName + ".png");
- uiDevice.takeScreenshot(screenshotFile);
- final File windowHierarchyFile = new File(mDumpDir, "wh-" + shortenedTestName + ".xml");
- try {
- uiDevice.dumpWindowHierarchy(windowHierarchyFile);
- } catch (IOException e) {
- Log.e(TAG, "Error dumping window hierarchy", e);
- }
- }
-
- private String getShortenedTestName(Description description) {
- final String qualifiedClassName = description.getClassName();
- final String className = qualifiedClassName.substring(
- qualifiedClassName.lastIndexOf(".") + 1);
- final String shortenedClassName = className.chars()
- .filter(Character::isUpperCase)
- .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
- .toString();
- return shortenedClassName + "_" + description.getMethodName();
- }
-
- void dumpCommandOutput(FileOutputStream out, String cmd) {
- final ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation()
- .getUiAutomation().executeShellCommand(cmd);
- try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
- out.write(("Output of '" + cmd + "':\n").getBytes(StandardCharsets.UTF_8));
- FileUtils.copy(in, out);
- out.write("\n\n=================================================================\n\n"
- .getBytes(StandardCharsets.UTF_8));
- } catch (IOException e) {
- Log.e(TAG, "Error dumping '" + cmd + "'", e);
- }
- }
-
- void prepareDumpRootDir() {
- if (!mDumpDir.exists() && !mDumpDir.mkdir()) {
- Log.e(TAG, "Error creating " + mDumpDir);
- }
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobMeteredTest.java
deleted file mode 100644
index d56a50b..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobMeteredTest.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
-
-@RequiredProperties({METERED_NETWORK})
-public class ExpeditedJobMeteredTest extends AbstractExpeditedJobTest {
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobNonMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobNonMeteredTest.java
deleted file mode 100644
index 0a776ee..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobNonMeteredTest.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
-
-@RequiredProperties({NON_METERED_NETWORK})
-public class ExpeditedJobNonMeteredTest extends AbstractExpeditedJobTest {
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MeterednessConfigurationRule.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MeterednessConfigurationRule.java
deleted file mode 100644
index 4f4e68e..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MeterednessConfigurationRule.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2019 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setupActiveNetworkMeteredness;
-import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
-import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
-
-import android.util.ArraySet;
-
-import com.android.compatibility.common.util.BeforeAfterRule;
-import com.android.compatibility.common.util.ThrowingRunnable;
-
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-public class MeterednessConfigurationRule extends BeforeAfterRule {
- private ThrowingRunnable mMeterednessResetter;
-
- @Override
- public void onBefore(Statement base, Description description) throws Throwable {
- final ArraySet<Property> requiredProperties
- = RequiredPropertiesRule.getRequiredProperties();
- if (requiredProperties.contains(METERED_NETWORK)) {
- configureNetworkMeteredness(true);
- } else if (requiredProperties.contains(NON_METERED_NETWORK)) {
- configureNetworkMeteredness(false);
- }
- }
-
- @Override
- public void onAfter(Statement base, Description description) throws Throwable {
- resetNetworkMeteredness();
- }
-
- public void configureNetworkMeteredness(boolean metered) throws Exception {
- mMeterednessResetter = setupActiveNetworkMeteredness(metered);
- }
-
- public void resetNetworkMeteredness() throws Exception {
- if (mMeterednessResetter != null) {
- mMeterednessResetter.run();
- mMeterednessResetter = null;
- }
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MixedModesTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MixedModesTest.java
deleted file mode 100644
index b0fa106..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MixedModesTest.java
+++ /dev/null
@@ -1,370 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.netpolicy.hostside.Property.APP_STANDBY_MODE;
-import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
-import static com.android.cts.netpolicy.hostside.Property.DOZE_MODE;
-import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
-import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
-
-import android.os.SystemClock;
-import android.util.Log;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Test cases for the more complex scenarios where multiple restrictions (like Battery Saver Mode
- * and Data Saver Mode) are applied simultaneously.
- * <p>
- * <strong>NOTE: </strong>it might sound like the test methods on this class are testing too much,
- * which would make it harder to diagnose individual failures, but the assumption is that such
- * failure most likely will happen when the restriction is tested individually as well.
- */
-public class MixedModesTest extends AbstractRestrictBackgroundNetworkTestCase {
- private static final String TAG = "MixedModesTest";
-
- @Before
- public void setUp() throws Exception {
- super.setUp();
-
- // Set initial state.
- removeRestrictBackgroundWhitelist(mUid);
- removeRestrictBackgroundBlacklist(mUid);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
-
- registerBroadcastReceiver();
- }
-
- @After
- public void tearDown() throws Exception {
- super.tearDown();
-
- try {
- setRestrictBackground(false);
- } finally {
- setBatterySaverMode(false);
- }
- }
-
- /**
- * Tests all DS ON and BS ON scenarios from network-policy-restrictions.md on metered networks.
- */
- @RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE, METERED_NETWORK})
- @Test
- public void testDataAndBatterySaverModes_meteredNetwork() throws Exception {
- final MeterednessConfigurationRule meterednessConfiguration
- = new MeterednessConfigurationRule();
- meterednessConfiguration.configureNetworkMeteredness(true);
- try {
- setRestrictBackground(true);
- setBatterySaverMode(true);
-
- Log.v(TAG, "Not whitelisted for any.");
- assertBackgroundNetworkAccess(false);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
-
- Log.v(TAG, "Whitelisted for Data Saver but not for Battery Saver.");
- addRestrictBackgroundWhitelist(mUid);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
- removeRestrictBackgroundWhitelist(mUid);
-
- Log.v(TAG, "Whitelisted for Battery Saver but not for Data Saver.");
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- removeRestrictBackgroundWhitelist(mUid);
- assertBackgroundNetworkAccess(false);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
-
- Log.v(TAG, "Whitelisted for both.");
- addRestrictBackgroundWhitelist(mUid);
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(true);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- removeRestrictBackgroundWhitelist(mUid);
-
- Log.v(TAG, "Blacklisted for Data Saver, not whitelisted for Battery Saver.");
- addRestrictBackgroundBlacklist(mUid);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
- removeRestrictBackgroundBlacklist(mUid);
-
- Log.v(TAG, "Blacklisted for Data Saver, whitelisted for Battery Saver.");
- addRestrictBackgroundBlacklist(mUid);
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
- removeRestrictBackgroundBlacklist(mUid);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- } finally {
- meterednessConfiguration.resetNetworkMeteredness();
- }
- }
-
- /**
- * Tests all DS ON and BS ON scenarios from network-policy-restrictions.md on non-metered
- * networks.
- */
- @RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE, NON_METERED_NETWORK})
- @Test
- public void testDataAndBatterySaverModes_nonMeteredNetwork() throws Exception {
- final MeterednessConfigurationRule meterednessConfiguration
- = new MeterednessConfigurationRule();
- meterednessConfiguration.configureNetworkMeteredness(false);
- try {
- setRestrictBackground(true);
- setBatterySaverMode(true);
-
- Log.v(TAG, "Not whitelisted for any.");
- assertBackgroundNetworkAccess(false);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
-
- Log.v(TAG, "Whitelisted for Data Saver but not for Battery Saver.");
- addRestrictBackgroundWhitelist(mUid);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
- removeRestrictBackgroundWhitelist(mUid);
-
- Log.v(TAG, "Whitelisted for Battery Saver but not for Data Saver.");
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- removeRestrictBackgroundWhitelist(mUid);
- assertBackgroundNetworkAccess(true);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(true);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
-
- Log.v(TAG, "Whitelisted for both.");
- addRestrictBackgroundWhitelist(mUid);
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(true);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- removeRestrictBackgroundWhitelist(mUid);
-
- Log.v(TAG, "Blacklisted for Data Saver, not whitelisted for Battery Saver.");
- addRestrictBackgroundBlacklist(mUid);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
- removeRestrictBackgroundBlacklist(mUid);
-
- Log.v(TAG, "Blacklisted for Data Saver, whitelisted for Battery Saver.");
- addRestrictBackgroundBlacklist(mUid);
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(true);
- removeRestrictBackgroundBlacklist(mUid);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- } finally {
- meterednessConfiguration.resetNetworkMeteredness();
- }
- }
-
- /**
- * Tests that powersave whitelists works as expected when doze and battery saver modes
- * are enabled.
- */
- @RequiredProperties({DOZE_MODE, BATTERY_SAVER_MODE})
- @Test
- public void testDozeAndBatterySaverMode_powerSaveWhitelists() throws Exception {
- setBatterySaverMode(true);
- setDozeMode(true);
-
- try {
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
-
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
-
- addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
-
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- } finally {
- setBatterySaverMode(false);
- setDozeMode(false);
- }
- }
-
- /**
- * Tests that powersave whitelists works as expected when doze and appIdle modes
- * are enabled.
- */
- @RequiredProperties({DOZE_MODE, APP_STANDBY_MODE})
- @Test
- public void testDozeAndAppIdle_powerSaveWhitelists() throws Exception {
- setDozeMode(true);
- setAppIdle(true);
-
- try {
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
-
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
-
- addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
-
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- } finally {
- setAppIdle(false);
- setDozeMode(false);
- }
- }
-
- @RequiredProperties({APP_STANDBY_MODE, DOZE_MODE})
- @Test
- public void testAppIdleAndDoze_tempPowerSaveWhitelists() throws Exception {
- setDozeMode(true);
- setAppIdle(true);
-
- try {
- assertBackgroundNetworkAccess(false);
-
- addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
- assertBackgroundNetworkAccess(true);
-
- // Wait until the whitelist duration is expired.
- SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
- assertBackgroundNetworkAccess(false);
- } finally {
- setAppIdle(false);
- setDozeMode(false);
- }
- }
-
- @RequiredProperties({APP_STANDBY_MODE, BATTERY_SAVER_MODE})
- @Test
- public void testAppIdleAndBatterySaver_tempPowerSaveWhitelists() throws Exception {
- setBatterySaverMode(true);
- setAppIdle(true);
-
- try {
- assertBackgroundNetworkAccess(false);
-
- addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
- assertBackgroundNetworkAccess(true);
-
- // Wait until the whitelist duration is expired.
- SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
- assertBackgroundNetworkAccess(false);
- } finally {
- setAppIdle(false);
- setBatterySaverMode(false);
- }
- }
-
- /**
- * Tests that the app idle whitelist works as expected when doze and appIdle mode are enabled.
- */
- @RequiredProperties({DOZE_MODE, APP_STANDBY_MODE})
- @Test
- public void testDozeAndAppIdle_appIdleWhitelist() throws Exception {
- setDozeMode(true);
- setAppIdle(true);
-
- try {
- assertBackgroundNetworkAccess(false);
-
- // UID still shouldn't have access because of Doze.
- addAppIdleWhitelist(mUid);
- assertBackgroundNetworkAccess(false);
-
- removeAppIdleWhitelist(mUid);
- assertBackgroundNetworkAccess(false);
- } finally {
- setAppIdle(false);
- setDozeMode(false);
- }
- }
-
- @RequiredProperties({APP_STANDBY_MODE, DOZE_MODE})
- @Test
- public void testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists() throws Exception {
- setDozeMode(true);
- setAppIdle(true);
-
- try {
- assertBackgroundNetworkAccess(false);
-
- addAppIdleWhitelist(mUid);
- assertBackgroundNetworkAccess(false);
-
- addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
- assertBackgroundNetworkAccess(true);
-
- // Wait until the whitelist duration is expired.
- SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
- assertBackgroundNetworkAccess(false);
- } finally {
- setAppIdle(false);
- setDozeMode(false);
- removeAppIdleWhitelist(mUid);
- }
- }
-
- @RequiredProperties({APP_STANDBY_MODE, BATTERY_SAVER_MODE})
- @Test
- public void testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists() throws Exception {
- setBatterySaverMode(true);
- setAppIdle(true);
-
- try {
- assertBackgroundNetworkAccess(false);
-
- addAppIdleWhitelist(mUid);
- assertBackgroundNetworkAccess(false);
-
- addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
- assertBackgroundNetworkAccess(true);
-
- // Wait until the whitelist duration is expired.
- SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
- assertBackgroundNetworkAccess(false);
- } finally {
- setAppIdle(false);
- setBatterySaverMode(false);
- removeAppIdleWhitelist(mUid);
- }
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyNotificationListenerService.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyNotificationListenerService.java
deleted file mode 100644
index 6dc9921..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyNotificationListenerService.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
-import android.app.RemoteInput;
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
-import android.util.Log;
-
-/**
- * NotificationListenerService implementation that executes the notification actions once they're
- * created.
- */
-public class MyNotificationListenerService extends NotificationListenerService {
- private static final String TAG = "MyNotificationListenerService";
-
- @Override
- public void onListenerConnected() {
- Log.d(TAG, "onListenerConnected()");
- }
-
- @Override
- public void onNotificationPosted(StatusBarNotification sbn) {
- Log.d(TAG, "onNotificationPosted(): " + sbn);
- if (!sbn.getPackageName().startsWith(getPackageName())) {
- Log.v(TAG, "ignoring notification from a different package");
- return;
- }
- final PendingIntentSender sender = new PendingIntentSender();
- final Notification notification = sbn.getNotification();
- if (notification.contentIntent != null) {
- sender.send("content", notification.contentIntent);
- }
- if (notification.deleteIntent != null) {
- sender.send("delete", notification.deleteIntent);
- }
- if (notification.fullScreenIntent != null) {
- sender.send("full screen", notification.fullScreenIntent);
- }
- if (notification.actions != null) {
- for (Notification.Action action : notification.actions) {
- sender.send("action", action.actionIntent);
- sender.send("action extras", action.getExtras());
- final RemoteInput[] remoteInputs = action.getRemoteInputs();
- if (remoteInputs != null && remoteInputs.length > 0) {
- for (RemoteInput remoteInput : remoteInputs) {
- sender.send("remote input extras", remoteInput.getExtras());
- }
- }
- }
- }
- sender.send("notification extras", notification.extras);
- }
-
- static String getId() {
- return String.format("%s/%s", MyNotificationListenerService.class.getPackage().getName(),
- MyNotificationListenerService.class.getName());
- }
-
- static ComponentName getComponentName() {
- return new ComponentName(MyNotificationListenerService.class.getPackage().getName(),
- MyNotificationListenerService.class.getName());
- }
-
- private static final class PendingIntentSender {
- private PendingIntent mSentIntent = null;
- private String mReason = null;
-
- private void send(String reason, PendingIntent pendingIntent) {
- if (pendingIntent == null) {
- // Could happen on action that only has extras
- Log.v(TAG, "Not sending null pending intent for " + reason);
- return;
- }
- if (mSentIntent != null || mReason != null) {
- // Sanity check: make sure test case set up just one pending intent in the
- // notification, otherwise it could pass because another pending intent caused the
- // whitelisting.
- throw new IllegalStateException("Already sent a PendingIntent (" + mSentIntent
- + ") for reason '" + mReason + "' when requested another for '" + reason
- + "' (" + pendingIntent + ")");
- }
- Log.i(TAG, "Sending pending intent for " + reason + ":" + pendingIntent);
- try {
- pendingIntent.send();
- mSentIntent = pendingIntent;
- mReason = reason;
- } catch (CanceledException e) {
- Log.w(TAG, "Pending intent " + pendingIntent + " canceled");
- }
- }
-
- private void send(String reason, Bundle extras) {
- if (extras != null) {
- for (String key : extras.keySet()) {
- Object value = extras.get(key);
- if (value instanceof PendingIntent) {
- send(reason + " with key '" + key + "'", (PendingIntent) value);
- }
- }
- }
- }
-
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyServiceClient.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyServiceClient.java
deleted file mode 100644
index 71b28f6..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyServiceClient.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import android.app.job.JobInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.net.NetworkRequest;
-import android.os.ConditionVariable;
-import android.os.IBinder;
-import android.os.RemoteException;
-
-public class MyServiceClient {
- private static final int TIMEOUT_MS = 20_000;
- private static final String PACKAGE = MyServiceClient.class.getPackage().getName();
- private static final String APP2_PACKAGE = PACKAGE + ".app2";
- private static final String SERVICE_NAME = APP2_PACKAGE + ".MyService";
-
- private Context mContext;
- private ServiceConnection mServiceConnection;
- private volatile IMyService mService;
- private final ConditionVariable mServiceCondition = new ConditionVariable();
-
- public MyServiceClient(Context context) {
- mContext = context;
- }
-
- /**
- * Binds to a service in the test app to communicate state.
- * @param bindPriorityFlags Flags to influence the process-state of the bound app.
- */
- public void bind(int bindPriorityFlags) {
- if (mService != null) {
- throw new IllegalStateException("Already bound");
- }
- mServiceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- mService = IMyService.Stub.asInterface(service);
- mServiceCondition.open();
- }
- @Override
- public void onServiceDisconnected(ComponentName name) {
- mServiceCondition.close();
- mService = null;
- }
- };
-
- final Intent intent = new Intent();
- intent.setComponent(new ComponentName(APP2_PACKAGE, SERVICE_NAME));
- // Needs to use BIND_NOT_FOREGROUND so app2 does not run in
- // the same process state as app
- mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE
- | bindPriorityFlags);
- ensureServiceConnection();
- }
-
- public void unbind() {
- if (mService != null) {
- mContext.unbindService(mServiceConnection);
- }
- }
-
- private void ensureServiceConnection() {
- if (mService != null) {
- return;
- }
- mServiceCondition.block(TIMEOUT_MS);
- if (mService == null) {
- throw new IllegalStateException(
- "Could not bind to MyService service after " + TIMEOUT_MS + "ms");
- }
- }
-
- public void registerBroadcastReceiver() throws RemoteException {
- ensureServiceConnection();
- mService.registerBroadcastReceiver();
- }
-
- public int getCounters(String receiverName, String action) throws RemoteException {
- ensureServiceConnection();
- return mService.getCounters(receiverName, action);
- }
-
- /** Retrieves the network state as observed from the bound test app */
- public NetworkCheckResult checkNetworkStatus(String address) throws RemoteException {
- ensureServiceConnection();
- return mService.checkNetworkStatus(address);
- }
-
- public String getRestrictBackgroundStatus() throws RemoteException {
- ensureServiceConnection();
- return mService.getRestrictBackgroundStatus();
- }
-
- public void sendNotification(int notificationId, String notificationType)
- throws RemoteException {
- ensureServiceConnection();
- mService.sendNotification(notificationId, notificationType);
- }
-
- public void registerNetworkCallback(final NetworkRequest request, INetworkCallback cb)
- throws RemoteException {
- ensureServiceConnection();
- mService.registerNetworkCallback(request, cb);
- }
-
- public void unregisterNetworkCallback() throws RemoteException {
- ensureServiceConnection();
- mService.unregisterNetworkCallback();
- }
-
- public int scheduleJob(JobInfo jobInfo) throws RemoteException {
- ensureServiceConnection();
- return mService.scheduleJob(jobInfo);
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkCallbackTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkCallbackTest.java
deleted file mode 100644
index 3934cfa..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkCallbackTest.java
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
- * Copyright (C) 2019 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.cts.netpolicy.hostside;
-
-import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
-import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED;
-
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.getActiveNetworkCapabilities;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
-import android.net.cts.util.CtsNetUtils;
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.android.modules.utils.build.SdkLevel;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.Objects;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-public class NetworkCallbackTest extends AbstractRestrictBackgroundNetworkTestCase {
- private Network mNetwork;
- private final TestNetworkCallback mTestNetworkCallback = new TestNetworkCallback();
- private CtsNetUtils mCtsNetUtils;
- private static final String GOOGLE_PRIVATE_DNS_SERVER = "dns.google";
-
- @Rule
- public final MeterednessConfigurationRule mMeterednessConfiguration
- = new MeterednessConfigurationRule();
-
- enum CallbackState {
- NONE,
- AVAILABLE,
- LOST,
- BLOCKED_STATUS,
- CAPABILITIES
- }
-
- private static class CallbackInfo {
- public final CallbackState state;
- public final Network network;
- public final Object arg;
-
- CallbackInfo(CallbackState s, Network n, Object o) {
- state = s; network = n; arg = o;
- }
-
- public String toString() {
- return String.format("%s (%s) (%s)", state, network, arg);
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof CallbackInfo)) return false;
- // Ignore timeMs, since it's unpredictable.
- final CallbackInfo other = (CallbackInfo) o;
- return (state == other.state) && Objects.equals(network, other.network)
- && Objects.equals(arg, other.arg);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(state, network, arg);
- }
- }
-
- private class TestNetworkCallback extends INetworkCallback.Stub {
- private static final int TEST_CONNECT_TIMEOUT_MS = 30_000;
- private static final int TEST_CALLBACK_TIMEOUT_MS = 5_000;
-
- private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>();
-
- protected void setLastCallback(CallbackState state, Network network, Object o) {
- mCallbacks.offer(new CallbackInfo(state, network, o));
- }
-
- CallbackInfo nextCallback(int timeoutMs) {
- CallbackInfo cb = null;
- try {
- cb = mCallbacks.poll(timeoutMs, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- }
- if (cb == null) {
- fail("Did not receive callback after " + timeoutMs + "ms");
- }
- return cb;
- }
-
- CallbackInfo expectCallback(CallbackState state, Network expectedNetwork, Object o) {
- final CallbackInfo expected = new CallbackInfo(state, expectedNetwork, o);
- final CallbackInfo actual = nextCallback(TEST_CALLBACK_TIMEOUT_MS);
- assertEquals("Unexpected callback:", expected, actual);
- return actual;
- }
-
- @Override
- public void onAvailable(Network network) {
- setLastCallback(CallbackState.AVAILABLE, network, null);
- }
-
- @Override
- public void onLost(Network network) {
- setLastCallback(CallbackState.LOST, network, null);
- }
-
- @Override
- public void onBlockedStatusChanged(Network network, boolean blocked) {
- setLastCallback(CallbackState.BLOCKED_STATUS, network, blocked);
- }
-
- @Override
- public void onCapabilitiesChanged(Network network, NetworkCapabilities cap) {
- setLastCallback(CallbackState.CAPABILITIES, network, cap);
- }
-
- public Network expectAvailableCallbackAndGetNetwork() {
- final CallbackInfo cb = nextCallback(TEST_CONNECT_TIMEOUT_MS);
- if (cb.state != CallbackState.AVAILABLE) {
- fail("Network is not available. Instead obtained the following callback :" + cb);
- }
- return cb.network;
- }
-
- public void drainAndWaitForIdle() {
- try {
- do {
- mCallbacks.drainTo(new ArrayList<>());
- } while (mCallbacks.poll(TEST_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS) != null);
- } catch (InterruptedException ie) {
- Log.e(TAG, "Interrupted while draining callback queue", ie);
- Thread.currentThread().interrupt();
- }
- }
-
- public void expectBlockedStatusCallback(Network expectedNetwork, boolean expectBlocked) {
- expectCallback(CallbackState.BLOCKED_STATUS, expectedNetwork, expectBlocked);
- }
-
- public void expectBlockedStatusCallbackEventually(Network expectedNetwork,
- boolean expectBlocked) {
- final long deadline = System.currentTimeMillis() + TEST_CALLBACK_TIMEOUT_MS;
- do {
- final CallbackInfo cb = nextCallback((int) (deadline - System.currentTimeMillis()));
- if (cb.state == CallbackState.BLOCKED_STATUS
- && cb.network.equals(expectedNetwork)) {
- assertEquals(expectBlocked, cb.arg);
- return;
- }
- } while (System.currentTimeMillis() <= deadline);
- fail("Didn't receive onBlockedStatusChanged()");
- }
-
- public void expectCapabilitiesCallbackEventually(Network expectedNetwork, boolean hasCap,
- int cap) {
- final long deadline = System.currentTimeMillis() + TEST_CALLBACK_TIMEOUT_MS;
- do {
- final CallbackInfo cb = nextCallback((int) (deadline - System.currentTimeMillis()));
- if (cb.state != CallbackState.CAPABILITIES
- || !expectedNetwork.equals(cb.network)
- || (hasCap != ((NetworkCapabilities) cb.arg).hasCapability(cap))) {
- Log.i("NetworkCallbackTest#expectCapabilitiesCallback",
- "Ignoring non-matching callback : " + cb);
- continue;
- }
- // Found a match, return
- return;
- } while (System.currentTimeMillis() <= deadline);
- fail("Didn't receive the expected callback to onCapabilitiesChanged(). Check the "
- + "log for a list of received callbacks, if any.");
- }
- }
-
- @Before
- public void setUp() throws Exception {
- super.setUp();
-
- assumeTrue(canChangeActiveNetworkMeteredness());
-
- registerBroadcastReceiver();
-
- removeRestrictBackgroundWhitelist(mUid);
- removeRestrictBackgroundBlacklist(mUid);
- assertRestrictBackgroundChangedReceived(0);
-
- // Initial state
- setBatterySaverMode(false);
- setRestrictBackground(false);
- setAppIdle(false);
-
- // Get transports of the active network, this has to be done before changing meteredness,
- // since wifi will be disconnected when changing from non-metered to metered.
- final NetworkCapabilities networkCapabilities = getActiveNetworkCapabilities();
-
- // Mark network as metered.
- mMeterednessConfiguration.configureNetworkMeteredness(true);
-
- // Register callback, copy the capabilities from the active network to expect the "original"
- // network before disconnecting, but null out some fields to prevent over-specified.
- registerNetworkCallback(new NetworkRequest.Builder()
- .setCapabilities(networkCapabilities.setTransportInfo(null))
- .removeCapability(NET_CAPABILITY_NOT_METERED)
- .setSignalStrength(SIGNAL_STRENGTH_UNSPECIFIED).build(), mTestNetworkCallback);
- // Wait for onAvailable() callback to ensure network is available before the test
- // and store the default network.
- mNetwork = mTestNetworkCallback.expectAvailableCallbackAndGetNetwork();
- // Check that the network is metered.
- mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
- false /* hasCapability */, NET_CAPABILITY_NOT_METERED);
- mTestNetworkCallback.drainAndWaitForIdle();
-
- // Before Android T, DNS queries over private DNS should be but are not restricted by Power
- // Saver or Data Saver. The issue is fixed in mainline update and apps can no longer request
- // DNS queries when its network is restricted by Power Saver. The fix takes effect backwards
- // starting from Android T. But for Data Saver, the fix is not backward compatible since
- // there are some platform changes involved. It is only available on devices that a specific
- // trunk flag is enabled.
- //
- // This test can not only verify that the network traffic from apps is blocked at the right
- // time, but also verify whether it is correctly blocked at the DNS stage, or at a later
- // socket connection stage.
- if (SdkLevel.isAtLeastT()) {
- // Enable private DNS
- mCtsNetUtils = new CtsNetUtils(mContext);
- mCtsNetUtils.storePrivateDnsSetting();
- mCtsNetUtils.setPrivateDnsStrictMode(GOOGLE_PRIVATE_DNS_SERVER);
- mCtsNetUtils.awaitPrivateDnsSetting(
- "NetworkCallbackTest wait private DNS setting timeout", mNetwork,
- GOOGLE_PRIVATE_DNS_SERVER, true);
- }
- }
-
- @After
- public void tearDown() throws Exception {
- super.tearDown();
-
- setRestrictBackground(false);
- setBatterySaverMode(false);
- unregisterNetworkCallback();
- stopApp();
-
- if (SdkLevel.isAtLeastT() && (mCtsNetUtils != null)) {
- mCtsNetUtils.restorePrivateDnsSetting();
- }
- }
-
- @RequiredProperties({DATA_SAVER_MODE})
- @Test
- public void testOnBlockedStatusChanged_dataSaver() throws Exception {
- try {
- // Enable restrict background
- setRestrictBackground(true);
- // TODO: Verify expectedUnavailableError when aconfig support mainline.
- // (see go/aconfig-in-mainline-problems)
- assertBackgroundNetworkAccess(false);
- assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
-
- // Add to whitelist
- addRestrictBackgroundWhitelist(mUid);
- assertBackgroundNetworkAccess(true);
- assertNetworkAccessBlockedByBpf(false, mUid, true /* metered */);
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
-
- // Remove from whitelist
- removeRestrictBackgroundWhitelist(mUid);
- // TODO: Verify expectedUnavailableError when aconfig support mainline.
- assertBackgroundNetworkAccess(false);
- assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
- } finally {
- mMeterednessConfiguration.resetNetworkMeteredness();
- }
-
- // Set to non-metered network
- mMeterednessConfiguration.configureNetworkMeteredness(false);
- mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
- true /* hasCapability */, NET_CAPABILITY_NOT_METERED);
- try {
- assertBackgroundNetworkAccess(true);
- assertNetworkAccessBlockedByBpf(false, mUid, false /* metered */);
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
-
- // Disable restrict background, should not trigger callback
- setRestrictBackground(false);
- assertBackgroundNetworkAccess(true);
- assertNetworkAccessBlockedByBpf(false, mUid, false /* metered */);
- } finally {
- mMeterednessConfiguration.resetNetworkMeteredness();
- }
- }
-
- @RequiredProperties({BATTERY_SAVER_MODE})
- @Test
- public void testOnBlockedStatusChanged_powerSaver() throws Exception {
- try {
- // Enable Power Saver
- setBatterySaverMode(true);
- if (SdkLevel.isAtLeastT()) {
- assertProcessStateBelow(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
- assertNetworkAccess(false, "java.net.UnknownHostException");
- } else {
- assertBackgroundNetworkAccess(false);
- }
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
- assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
-
- // Disable Power Saver
- setBatterySaverMode(false);
- assertBackgroundNetworkAccess(true);
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
- assertNetworkAccessBlockedByBpf(false, mUid, true /* metered */);
- } finally {
- mMeterednessConfiguration.resetNetworkMeteredness();
- }
-
- // Set to non-metered network
- mMeterednessConfiguration.configureNetworkMeteredness(false);
- mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
- true /* hasCapability */, NET_CAPABILITY_NOT_METERED);
- try {
- // Enable Power Saver
- setBatterySaverMode(true);
- if (SdkLevel.isAtLeastT()) {
- assertProcessStateBelow(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
- assertNetworkAccess(false, "java.net.UnknownHostException");
- } else {
- assertBackgroundNetworkAccess(false);
- }
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
- assertNetworkAccessBlockedByBpf(true, mUid, false /* metered */);
-
- // Disable Power Saver
- setBatterySaverMode(false);
- assertBackgroundNetworkAccess(true);
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
- assertNetworkAccessBlockedByBpf(false, mUid, false /* metered */);
- } finally {
- mMeterednessConfiguration.resetNetworkMeteredness();
- }
- }
-
- @Test
- public void testOnBlockedStatusChanged_default() throws Exception {
- assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
-
- try {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- assertNetworkAccess(false, null);
- assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
-
- launchActivity();
- assertTopState();
- assertNetworkAccess(true, null);
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
- assertNetworkAccessBlockedByBpf(false, mUid, true /* metered */);
-
- finishActivity();
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(mProcessStateTransitionLongDelayMs);
- assertNetworkAccess(false, null);
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
- assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
-
- } finally {
- mMeterednessConfiguration.resetNetworkMeteredness();
- }
-
- // Set to non-metered network
- mMeterednessConfiguration.configureNetworkMeteredness(false);
- mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
- true /* hasCapability */, NET_CAPABILITY_NOT_METERED);
- try {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- assertNetworkAccess(false, null);
- assertNetworkAccessBlockedByBpf(true, mUid, false /* metered */);
-
- launchActivity();
- assertTopState();
- assertNetworkAccess(true, null);
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
- assertNetworkAccessBlockedByBpf(false, mUid, false /* metered */);
-
- finishActivity();
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(mProcessStateTransitionLongDelayMs);
- assertNetworkAccess(false, null);
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
- assertNetworkAccessBlockedByBpf(true, mUid, false /* metered */);
- } finally {
- mMeterednessConfiguration.resetNetworkMeteredness();
- }
- }
-
- // TODO: 1. test against VPN lockdown.
- // 2. test against multiple networks.
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyManagerTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyManagerTest.java
deleted file mode 100644
index 6c5f2ff..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyManagerTest.java
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.netpolicy.hostside;
-
-import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
-import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
-import static android.os.Process.SYSTEM_UID;
-
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.assertIsUidRestrictedOnMeteredNetworks;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.assertNetworkingBlockedStatusForUid;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isUidNetworkingBlocked;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import android.os.SystemClock;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class NetworkPolicyManagerTest extends AbstractRestrictBackgroundNetworkTestCase {
- private static final boolean METERED = true;
- private static final boolean NON_METERED = false;
-
- @Before
- public void setUp() throws Exception {
- super.setUp();
-
- registerBroadcastReceiver();
-
- removeRestrictBackgroundWhitelist(mUid);
- removeRestrictBackgroundBlacklist(mUid);
- assertRestrictBackgroundChangedReceived(0);
-
- // Initial state
- setBatterySaverMode(false);
- setRestrictBackground(false);
- setRestrictedNetworkingMode(false);
- }
-
- @After
- public void tearDown() throws Exception {
- super.tearDown();
-
- setBatterySaverMode(false);
- setRestrictBackground(false);
- setRestrictedNetworkingMode(false);
- unregisterNetworkCallback();
- stopApp();
- }
-
- @Test
- public void testIsUidNetworkingBlocked_withUidNotBlocked() throws Exception {
- // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
- // test the cases of non-metered network and uid not matched by any rule.
- // If mUid is not blocked by data saver mode or power saver mode, no matter the network is
- // metered or non-metered, mUid shouldn't be blocked.
- assertFalse(isUidNetworkingBlocked(mUid, METERED)); // Match NTWK_ALLOWED_DEFAULT
- assertFalse(isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
- }
-
- @RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE})
- @Test
- public void testIsUidNetworkingBlocked_withSystemUid() throws Exception {
- // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
- // test the case of uid is system uid.
- // SYSTEM_UID will never be blocked.
- assertFalse(isUidNetworkingBlocked(SYSTEM_UID, METERED)); // Match NTWK_ALLOWED_SYSTEM
- assertFalse(isUidNetworkingBlocked(SYSTEM_UID, NON_METERED)); // Match NTWK_ALLOWED_SYSTEM
- try {
- setRestrictBackground(true);
- setBatterySaverMode(true);
- setRestrictedNetworkingMode(true);
- assertNetworkingBlockedStatusForUid(SYSTEM_UID, METERED,
- false /* expectedResult */); // Match NTWK_ALLOWED_SYSTEM
- assertFalse(
- isUidNetworkingBlocked(SYSTEM_UID, NON_METERED)); // Match NTWK_ALLOWED_SYSTEM
- } finally {
- setRestrictBackground(false);
- setBatterySaverMode(false);
- setRestrictedNetworkingMode(false);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- false /* expectedResult */); // Match NTWK_ALLOWED_DEFAULT
- }
- }
-
- @RequiredProperties({DATA_SAVER_MODE})
- @Test
- public void testIsUidNetworkingBlocked_withDataSaverMode() throws Exception {
- // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
- // test the cases of non-metered network, uid is matched by restrict background blacklist,
- // uid is matched by restrict background whitelist, app is in the foreground with restrict
- // background enabled and the app is in the background with restrict background enabled.
- try {
- // Enable restrict background and mUid will be blocked because it's not in the
- // foreground.
- setRestrictBackground(true);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- true /* expectedResult */); // Match NTWK_BLOCKED_BG_RESTRICT
-
- // Although restrict background is enabled and mUid is in the background, but mUid will
- // not be blocked if network is non-metered.
- assertFalse(
- isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
-
- // Add mUid into the restrict background blacklist.
- addRestrictBackgroundBlacklist(mUid);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- true /* expectedResult */); // Match NTWK_BLOCKED_DENYLIST
-
- // Although mUid is in the restrict background blacklist, but mUid won't be blocked if
- // the network is non-metered.
- assertFalse(
- isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
- removeRestrictBackgroundBlacklist(mUid);
-
- // Add mUid into the restrict background whitelist.
- addRestrictBackgroundWhitelist(mUid);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- false /* expectedResult */); // Match NTWK_ALLOWED_ALLOWLIST
- assertFalse(
- isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
- removeRestrictBackgroundWhitelist(mUid);
-
- // Make TEST_APP2_PKG go to foreground and mUid will be allowed temporarily.
- launchActivity();
- assertTopState();
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- false /* expectedResult */); // Match NTWK_ALLOWED_TMP_ALLOWLIST
-
- // Back to background.
- finishActivity();
- assertProcessStateBelow(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- true /* expectedResult */); // Match NTWK_BLOCKED_BG_RESTRICT
- } finally {
- setRestrictBackground(false);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- false /* expectedResult */); // Match NTWK_ALLOWED_DEFAULT
- }
- }
-
- @Test
- public void testIsUidNetworkingBlocked_withRestrictedNetworkingMode() throws Exception {
- // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
- // test the cases of restricted networking mode enabled.
- try {
- // All apps should be blocked if restricted networking mode is enabled except for those
- // apps who have CONNECTIVITY_USE_RESTRICTED_NETWORKS permission.
- // This test won't test if an app who has CONNECTIVITY_USE_RESTRICTED_NETWORKS will not
- // be blocked because CONNECTIVITY_USE_RESTRICTED_NETWORKS is a signature/privileged
- // permission that CTS cannot acquire. Also it's not good for this test to use those
- // privileged apps which have CONNECTIVITY_USE_RESTRICTED_NETWORKS to test because there
- // is no guarantee that those apps won't remove this permission someday, and if it
- // happens, then this test will fail.
- setRestrictedNetworkingMode(true);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- true /* expectedResult */); // Match NTWK_BLOCKED_RESTRICTED_MODE
- assertTrue(isUidNetworkingBlocked(mUid,
- NON_METERED)); // Match NTWK_BLOCKED_RESTRICTED_MODE
- } finally {
- setRestrictedNetworkingMode(false);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- false /* expectedResult */); // Match NTWK_ALLOWED_DEFAULT
- }
- }
-
- @RequiredProperties({BATTERY_SAVER_MODE})
- @Test
- public void testIsUidNetworkingBlocked_withPowerSaverMode() throws Exception {
- // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
- // test the cases of power saver mode enabled, uid in the power saver mode whitelist and
- // uid in the power saver mode whitelist with non-metered network.
- try {
- // mUid should be blocked if power saver mode is enabled.
- setBatterySaverMode(true);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- true /* expectedResult */); // Match NTWK_BLOCKED_POWER
- assertTrue(isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_BLOCKED_POWER
-
- // Add TEST_APP2_PKG into power saver mode whitelist, its uid rule is RULE_ALLOW_ALL and
- // it shouldn't be blocked.
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- false /* expectedResult */); // Match NTWK_ALLOWED_DEFAULT
- assertFalse(
- isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- } finally {
- setBatterySaverMode(false);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- false /* expectedResult */); // Match NTWK_ALLOWED_DEFAULT
- }
- }
-
- @RequiredProperties({DATA_SAVER_MODE})
- @Test
- public void testIsUidRestrictedOnMeteredNetworks() throws Exception {
- try {
- // isUidRestrictedOnMeteredNetworks() will only return true when restrict background is
- // enabled and mUid is not in the restrict background whitelist and TEST_APP2_PKG is not
- // in the foreground. For other cases, it will return false.
- setRestrictBackground(true);
- assertIsUidRestrictedOnMeteredNetworks(mUid, true /* expectedResult */);
-
- // Make TEST_APP2_PKG go to foreground and isUidRestrictedOnMeteredNetworks() will
- // return false.
- launchActivity();
- assertTopState();
- assertIsUidRestrictedOnMeteredNetworks(mUid, false /* expectedResult */);
- // Back to background.
- finishActivity();
- assertProcessStateBelow(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
-
- // Add mUid into restrict background whitelist and isUidRestrictedOnMeteredNetworks()
- // will return false.
- addRestrictBackgroundWhitelist(mUid);
- assertIsUidRestrictedOnMeteredNetworks(mUid, false /* expectedResult */);
- removeRestrictBackgroundWhitelist(mUid);
- } finally {
- // Restrict background is disabled and isUidRestrictedOnMeteredNetworks() will return
- // false.
- setRestrictBackground(false);
- assertIsUidRestrictedOnMeteredNetworks(mUid, false /* expectedResult */);
- }
- }
-
- @Test
- public void testIsUidNetworkingBlocked_whenInBackground() throws Exception {
- assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
-
- try {
- assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
- SystemClock.sleep(mProcessStateTransitionShortDelayMs);
- assertNetworkingBlockedStatusForUid(mUid, METERED, true /* expectedResult */);
- assertTrue(isUidNetworkingBlocked(mUid, NON_METERED));
-
- launchActivity();
- assertTopState();
- assertNetworkingBlockedStatusForUid(mUid, METERED, false /* expectedResult */);
- assertFalse(isUidNetworkingBlocked(mUid, NON_METERED));
-
- finishActivity();
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(mProcessStateTransitionLongDelayMs);
- assertNetworkingBlockedStatusForUid(mUid, METERED, true /* expectedResult */);
- assertTrue(isUidNetworkingBlocked(mUid, NON_METERED));
-
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertNetworkingBlockedStatusForUid(mUid, METERED, false /* expectedResult */);
- assertFalse(isUidNetworkingBlocked(mUid, NON_METERED));
- } finally {
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- }
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestRunner.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestRunner.java
deleted file mode 100644
index 0207b00..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestRunner.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.netpolicy.hostside;
-
-import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
-
-import org.junit.rules.RunRules;
-import org.junit.rules.TestRule;
-import org.junit.runners.model.FrameworkMethod;
-import org.junit.runners.model.InitializationError;
-import org.junit.runners.model.Statement;
-
-import java.util.List;
-
-/**
- * Custom runner to allow dumping logs after a test failure before the @After methods get to run.
- */
-public class NetworkPolicyTestRunner extends AndroidJUnit4ClassRunner {
- private TestRule mDumpOnFailureRule = new DumpOnFailureRule();
-
- public NetworkPolicyTestRunner(Class<?> klass) throws InitializationError {
- super(klass);
- }
-
- @Override
- public Statement methodInvoker(FrameworkMethod method, Object test) {
- return new RunRules(super.methodInvoker(method, test), List.of(mDumpOnFailureRule),
- describeChild(method));
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestUtils.java
deleted file mode 100644
index 26a88f2..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestUtils.java
+++ /dev/null
@@ -1,486 +0,0 @@
-/*
- * Copyright (C) 2019 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.cts.netpolicy.hostside;
-
-import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
-import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
-import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_METERED;
-import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_NONE;
-
-import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
-import static com.android.cts.netpolicy.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.ActivityManager;
-import android.app.Instrumentation;
-import android.app.UiAutomation;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.location.LocationManager;
-import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkPolicyManager;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiManager.ActionListener;
-import android.os.PersistableBundle;
-import android.os.Process;
-import android.os.UserHandle;
-import android.telephony.CarrierConfigManager;
-import android.telephony.SubscriptionManager;
-import android.telephony.data.ApnSetting;
-import android.util.Log;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.uiautomator.UiDevice;
-
-import com.android.compatibility.common.util.AppStandbyUtils;
-import com.android.compatibility.common.util.BatteryUtils;
-import com.android.compatibility.common.util.PollingCheck;
-import com.android.compatibility.common.util.ShellIdentityUtils;
-import com.android.compatibility.common.util.ThrowingRunnable;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-public class NetworkPolicyTestUtils {
-
- // android.telephony.CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS
- // TODO: Expose it as a @TestApi instead of copying the constant
- private static final String KEY_CARRIER_METERED_APN_TYPES_STRINGS =
- "carrier_metered_apn_types_strings";
-
- private static final int TIMEOUT_CHANGE_METEREDNESS_MS = 10_000;
-
- private static ConnectivityManager mCm;
- private static WifiManager mWm;
- private static CarrierConfigManager mCarrierConfigManager;
- private static NetworkPolicyManager sNpm;
-
- private static Boolean mBatterySaverSupported;
- private static Boolean mDataSaverSupported;
- private static Boolean mDozeModeSupported;
- private static Boolean mAppStandbySupported;
-
- private NetworkPolicyTestUtils() {}
-
- public static boolean isBatterySaverSupported() {
- if (mBatterySaverSupported == null) {
- mBatterySaverSupported = BatteryUtils.isBatterySaverSupported();
- }
- return mBatterySaverSupported;
- }
-
- private static boolean isWear() {
- return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
- }
-
- /**
- * As per CDD requirements, if the device doesn't support data saver mode then
- * ConnectivityManager.getRestrictBackgroundStatus() will always return
- * RESTRICT_BACKGROUND_STATUS_DISABLED. So, enable the data saver mode and check if
- * ConnectivityManager.getRestrictBackgroundStatus() for an app in background returns
- * RESTRICT_BACKGROUND_STATUS_DISABLED or not.
- */
- public static boolean isDataSaverSupported() {
- if (isWear()) {
- return false;
- }
- if (mDataSaverSupported == null) {
- setRestrictBackgroundInternal(false);
- assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
- try {
- setRestrictBackgroundInternal(true);
- mDataSaverSupported = !isMyRestrictBackgroundStatus(
- RESTRICT_BACKGROUND_STATUS_DISABLED);
- } finally {
- setRestrictBackgroundInternal(false);
- }
- }
- return mDataSaverSupported;
- }
-
- public static boolean isDozeModeSupported() {
- if (mDozeModeSupported == null) {
- final String result = executeShellCommand("cmd deviceidle enabled deep");
- mDozeModeSupported = result.equals("1");
- }
- return mDozeModeSupported;
- }
-
- public static boolean isAppStandbySupported() {
- if (mAppStandbySupported == null) {
- mAppStandbySupported = AppStandbyUtils.isAppStandbyEnabled();
- }
- return mAppStandbySupported;
- }
-
- public static boolean isLowRamDevice() {
- final ActivityManager am = (ActivityManager) getContext().getSystemService(
- Context.ACTIVITY_SERVICE);
- return am.isLowRamDevice();
- }
-
- /** Forces JobScheduler to run the job if constraints are met. */
- public static void forceRunJob(String pkg, int jobId) {
- executeShellCommand("cmd jobscheduler run -f -u " + UserHandle.myUserId()
- + " " + pkg + " " + jobId);
- }
-
- public static boolean isLocationEnabled() {
- final LocationManager lm = (LocationManager) getContext().getSystemService(
- Context.LOCATION_SERVICE);
- return lm.isLocationEnabled();
- }
-
- public static void setLocationEnabled(boolean enabled) {
- final LocationManager lm = (LocationManager) getContext().getSystemService(
- Context.LOCATION_SERVICE);
- lm.setLocationEnabledForUser(enabled, Process.myUserHandle());
- assertEquals("Couldn't change location enabled state", lm.isLocationEnabled(), enabled);
- Log.d(TAG, "Changed location enabled state to " + enabled);
- }
-
- public static boolean isActiveNetworkMetered(boolean metered) {
- return getConnectivityManager().isActiveNetworkMetered() == metered;
- }
-
- public static boolean canChangeActiveNetworkMeteredness() {
- final NetworkCapabilities networkCapabilities = getActiveNetworkCapabilities();
- return networkCapabilities.hasTransport(TRANSPORT_WIFI)
- || networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
- }
-
- /**
- * Updates the meteredness of the active network. Right now we can only change meteredness
- * of either Wifi or cellular network, so if the active network is not either of these, this
- * will throw an exception.
- *
- * @return a {@link ThrowingRunnable} object that can used to reset the meteredness change
- * made by this method.
- */
- public static ThrowingRunnable setupActiveNetworkMeteredness(boolean metered) throws Exception {
- if (isActiveNetworkMetered(metered)) {
- return null;
- }
- final NetworkCapabilities networkCapabilities = getActiveNetworkCapabilities();
- if (networkCapabilities.hasTransport(TRANSPORT_WIFI)) {
- final String ssid = getWifiSsid();
- setWifiMeteredStatus(ssid, metered);
- return () -> setWifiMeteredStatus(ssid, !metered);
- } else if (networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
- final int subId = SubscriptionManager.getActiveDataSubscriptionId();
- setCellularMeteredStatus(subId, metered);
- return () -> setCellularMeteredStatus(subId, !metered);
- } else {
- // Right now, we don't have a way to change meteredness of networks other
- // than Wi-Fi or Cellular, so just throw an exception.
- throw new IllegalStateException("Can't change meteredness of current active network");
- }
- }
-
- private static String getWifiSsid() {
- final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
- try {
- uiAutomation.adoptShellPermissionIdentity();
- final String ssid = getWifiManager().getConnectionInfo().getSSID();
- assertNotEquals(WifiManager.UNKNOWN_SSID, ssid);
- return ssid;
- } finally {
- uiAutomation.dropShellPermissionIdentity();
- }
- }
-
- static NetworkCapabilities getActiveNetworkCapabilities() {
- final Network activeNetwork = getConnectivityManager().getActiveNetwork();
- assertNotNull("No active network available", activeNetwork);
- return getConnectivityManager().getNetworkCapabilities(activeNetwork);
- }
-
- private static void setWifiMeteredStatus(String ssid, boolean metered) throws Exception {
- final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
- try {
- uiAutomation.adoptShellPermissionIdentity();
- final WifiConfiguration currentConfig = getWifiConfiguration(ssid);
- currentConfig.meteredOverride = metered
- ? METERED_OVERRIDE_METERED : METERED_OVERRIDE_NONE;
- BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();
- getWifiManager().save(currentConfig, createActionListener(
- blockingQueue, Integer.MAX_VALUE));
- Integer resultCode = blockingQueue.poll(TIMEOUT_CHANGE_METEREDNESS_MS,
- TimeUnit.MILLISECONDS);
- if (resultCode == null) {
- fail("Timed out waiting for meteredness to change; ssid=" + ssid
- + ", metered=" + metered);
- } else if (resultCode != Integer.MAX_VALUE) {
- fail("Error overriding the meteredness; ssid=" + ssid
- + ", metered=" + metered + ", error=" + resultCode);
- }
- final boolean success = assertActiveNetworkMetered(metered, false /* throwOnFailure */);
- if (!success) {
- Log.i(TAG, "Retry connecting to wifi; ssid=" + ssid);
- blockingQueue = new LinkedBlockingQueue<>();
- getWifiManager().connect(currentConfig, createActionListener(
- blockingQueue, Integer.MAX_VALUE));
- resultCode = blockingQueue.poll(TIMEOUT_CHANGE_METEREDNESS_MS,
- TimeUnit.MILLISECONDS);
- if (resultCode == null) {
- fail("Timed out waiting for wifi to connect; ssid=" + ssid);
- } else if (resultCode != Integer.MAX_VALUE) {
- fail("Error connecting to wifi; ssid=" + ssid
- + ", error=" + resultCode);
- }
- assertActiveNetworkMetered(metered, true /* throwOnFailure */);
- }
- } finally {
- uiAutomation.dropShellPermissionIdentity();
- }
- }
-
- private static WifiConfiguration getWifiConfiguration(String ssid) {
- final List<String> ssids = new ArrayList<>();
- for (WifiConfiguration config : getWifiManager().getConfiguredNetworks()) {
- if (config.SSID.equals(ssid)) {
- return config;
- }
- ssids.add(config.SSID);
- }
- fail("Couldn't find the wifi config; ssid=" + ssid
- + ", all=" + Arrays.toString(ssids.toArray()));
- return null;
- }
-
- private static ActionListener createActionListener(BlockingQueue<Integer> blockingQueue,
- int successCode) {
- return new ActionListener() {
- @Override
- public void onSuccess() {
- blockingQueue.offer(successCode);
- }
-
- @Override
- public void onFailure(int reason) {
- blockingQueue.offer(reason);
- }
- };
- }
-
- private static void setCellularMeteredStatus(int subId, boolean metered) throws Exception {
- final PersistableBundle bundle = new PersistableBundle();
- bundle.putStringArray(KEY_CARRIER_METERED_APN_TYPES_STRINGS,
- new String[] {ApnSetting.TYPE_MMS_STRING});
- ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(getCarrierConfigManager(),
- (cm) -> cm.overrideConfig(subId, metered ? null : bundle));
- assertActiveNetworkMetered(metered, true /* throwOnFailure */);
- }
-
- private static boolean assertActiveNetworkMetered(boolean expectedMeteredStatus,
- boolean throwOnFailure) throws Exception {
- final CountDownLatch latch = new CountDownLatch(1);
- final NetworkCallback networkCallback = new NetworkCallback() {
- @Override
- public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
- final boolean metered = !nc.hasCapability(NET_CAPABILITY_NOT_METERED);
- if (metered == expectedMeteredStatus) {
- latch.countDown();
- }
- }
- };
- // Registering a callback here guarantees onCapabilitiesChanged is called immediately
- // with the current setting. Therefore, if the setting has already been changed,
- // this method will return right away, and if not it will wait for the setting to change.
- getConnectivityManager().registerDefaultNetworkCallback(networkCallback);
- try {
- if (!latch.await(TIMEOUT_CHANGE_METEREDNESS_MS, TimeUnit.MILLISECONDS)) {
- final String errorMsg = "Timed out waiting for active network metered status "
- + "to change to " + expectedMeteredStatus + "; network = "
- + getConnectivityManager().getActiveNetwork();
- if (throwOnFailure) {
- fail(errorMsg);
- }
- Log.w(TAG, errorMsg);
- return false;
- }
- return true;
- } finally {
- getConnectivityManager().unregisterNetworkCallback(networkCallback);
- }
- }
-
- public static void setRestrictBackground(boolean enabled) {
- if (!isDataSaverSupported()) {
- return;
- }
- setRestrictBackgroundInternal(enabled);
- }
-
- static void setRestrictBackgroundInternal(boolean enabled) {
- executeShellCommand("cmd netpolicy set restrict-background " + enabled);
- final String output = executeShellCommand("cmd netpolicy get restrict-background");
- final String expectedSuffix = enabled ? "enabled" : "disabled";
- assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'",
- output.endsWith(expectedSuffix));
- }
-
- public static boolean isMyRestrictBackgroundStatus(int expectedStatus) {
- final int actualStatus = getConnectivityManager().getRestrictBackgroundStatus();
- if (expectedStatus != actualStatus) {
- Log.d(TAG, "MyRestrictBackgroundStatus: "
- + "Expected: " + restrictBackgroundValueToString(expectedStatus)
- + "; Actual: " + restrictBackgroundValueToString(actualStatus));
- return false;
- }
- return true;
- }
-
- // Copied from cts/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
- private static String unquoteSSID(String ssid) {
- // SSID is returned surrounded by quotes if it can be decoded as UTF-8.
- // Otherwise it's guaranteed not to start with a quote.
- if (ssid.charAt(0) == '"') {
- return ssid.substring(1, ssid.length() - 1);
- } else {
- return ssid;
- }
- }
-
- public static String restrictBackgroundValueToString(int status) {
- switch (status) {
- case RESTRICT_BACKGROUND_STATUS_DISABLED:
- return "DISABLED";
- case RESTRICT_BACKGROUND_STATUS_WHITELISTED:
- return "WHITELISTED";
- case RESTRICT_BACKGROUND_STATUS_ENABLED:
- return "ENABLED";
- default:
- return "UNKNOWN_STATUS_" + status;
- }
- }
-
- public static void clearSnoozeTimestamps() {
- executeShellCommand("dumpsys netpolicy --unsnooze");
- }
-
- public static String executeShellCommand(String command) {
- final String result = runShellCommandOrThrow(command).trim();
- Log.d(TAG, "Output of '" + command + "': '" + result + "'");
- return result;
- }
-
- public static void assertMyRestrictBackgroundStatus(int expectedStatus) {
- final int actualStatus = getConnectivityManager().getRestrictBackgroundStatus();
- assertEquals(restrictBackgroundValueToString(expectedStatus),
- restrictBackgroundValueToString(actualStatus));
- }
-
- public static ConnectivityManager getConnectivityManager() {
- if (mCm == null) {
- mCm = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
- }
- return mCm;
- }
-
- public static WifiManager getWifiManager() {
- if (mWm == null) {
- mWm = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
- }
- return mWm;
- }
-
- public static CarrierConfigManager getCarrierConfigManager() {
- if (mCarrierConfigManager == null) {
- mCarrierConfigManager = (CarrierConfigManager) getContext().getSystemService(
- Context.CARRIER_CONFIG_SERVICE);
- }
- return mCarrierConfigManager;
- }
-
- public static NetworkPolicyManager getNetworkPolicyManager() {
- if (sNpm == null) {
- sNpm = getContext().getSystemService(NetworkPolicyManager.class);
- }
- return sNpm;
- }
-
- public static Context getContext() {
- return getInstrumentation().getContext();
- }
-
- public static Instrumentation getInstrumentation() {
- return InstrumentationRegistry.getInstrumentation();
- }
-
- public static UiDevice getUiDevice() {
- return UiDevice.getInstance(getInstrumentation());
- }
-
- // When power saver mode or restrict background enabled or adding any white/black list into
- // those modes, NetworkPolicy may need to take some time to update the rules of uids. So having
- // this function and using PollingCheck to try to make sure the uid has updated and reduce the
- // flaky rate.
- public static void assertNetworkingBlockedStatusForUid(int uid, boolean metered,
- boolean expectedResult) {
- final String errMsg = String.format("Unexpected result from isUidNetworkingBlocked; "
- + "uid= " + uid + ", metered=" + metered + ", expected=" + expectedResult);
- PollingCheck.waitFor(() -> (expectedResult == isUidNetworkingBlocked(uid, metered)),
- errMsg);
- }
-
- public static void assertIsUidRestrictedOnMeteredNetworks(int uid, boolean expectedResult) {
- final String errMsg = String.format(
- "Unexpected result from isUidRestrictedOnMeteredNetworks; "
- + "uid= " + uid + ", expected=" + expectedResult);
- PollingCheck.waitFor(() -> (expectedResult == isUidRestrictedOnMeteredNetworks(uid)),
- errMsg);
- }
-
- public static boolean isUidNetworkingBlocked(int uid, boolean meteredNetwork) {
- final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
- try {
- uiAutomation.adoptShellPermissionIdentity();
- return getNetworkPolicyManager().isUidNetworkingBlocked(uid, meteredNetwork);
- } finally {
- uiAutomation.dropShellPermissionIdentity();
- }
- }
-
- public static boolean isUidRestrictedOnMeteredNetworks(int uid) {
- final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
- try {
- uiAutomation.adoptShellPermissionIdentity();
- return getNetworkPolicyManager().isUidRestrictedOnMeteredNetworks(uid);
- } finally {
- uiAutomation.dropShellPermissionIdentity();
- }
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/Property.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/Property.java
deleted file mode 100644
index a03833f..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/Property.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2019 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isActiveNetworkMetered;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isAppStandbySupported;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isDataSaverSupported;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isLowRamDevice;
-
-public enum Property {
- BATTERY_SAVER_MODE(1 << 0) {
- public boolean isSupported() { return isBatterySaverSupported(); }
- },
-
- DATA_SAVER_MODE(1 << 1) {
- public boolean isSupported() { return isDataSaverSupported(); }
- },
-
- NO_DATA_SAVER_MODE(~DATA_SAVER_MODE.getValue()) {
- public boolean isSupported() { return !isDataSaverSupported(); }
- },
-
- DOZE_MODE(1 << 2) {
- public boolean isSupported() { return isDozeModeSupported(); }
- },
-
- APP_STANDBY_MODE(1 << 3) {
- public boolean isSupported() { return isAppStandbySupported(); }
- },
-
- NOT_LOW_RAM_DEVICE(1 << 4) {
- public boolean isSupported() { return !isLowRamDevice(); }
- },
-
- METERED_NETWORK(1 << 5) {
- public boolean isSupported() {
- return isActiveNetworkMetered(true) || canChangeActiveNetworkMeteredness();
- }
- },
-
- NON_METERED_NETWORK(~METERED_NETWORK.getValue()) {
- public boolean isSupported() {
- return isActiveNetworkMetered(false) || canChangeActiveNetworkMeteredness();
- }
- };
-
- private int mValue;
-
- Property(int value) { mValue = value; }
-
- public int getValue() { return mValue; }
-
- abstract boolean isSupported();
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredProperties.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredProperties.java
deleted file mode 100644
index 799a513..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredProperties.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2019 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.cts.netpolicy.hostside;
-
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.TYPE;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-@Retention(RUNTIME)
-@Target({METHOD, TYPE})
-@Inherited
-public @interface RequiredProperties {
- Property[] value();
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredPropertiesRule.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredPropertiesRule.java
deleted file mode 100644
index 5dea67c..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredPropertiesRule.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2019 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
-
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Log;
-
-import com.android.compatibility.common.util.BeforeAfterRule;
-
-import org.junit.Assume;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.util.ArrayList;
-import java.util.Collections;
-
-public class RequiredPropertiesRule extends BeforeAfterRule {
-
- private static ArraySet<Property> mRequiredProperties;
-
- @Override
- public void onBefore(Statement base, Description description) {
- mRequiredProperties = getAllRequiredProperties(description);
-
- final String testName = description.getClassName() + "#" + description.getMethodName();
- assertTestIsValid(testName, mRequiredProperties);
- Log.i(TAG, "Running test " + testName + " with required properties: "
- + propertiesToString(mRequiredProperties));
- }
-
- private ArraySet<Property> getAllRequiredProperties(Description description) {
- final ArraySet<Property> allRequiredProperties = new ArraySet<>();
- RequiredProperties requiredProperties = description.getAnnotation(RequiredProperties.class);
- if (requiredProperties != null) {
- Collections.addAll(allRequiredProperties, requiredProperties.value());
- }
-
- for (Class<?> clazz = description.getTestClass();
- clazz != null; clazz = clazz.getSuperclass()) {
- requiredProperties = clazz.getDeclaredAnnotation(RequiredProperties.class);
- if (requiredProperties == null) {
- continue;
- }
- for (Property requiredProperty : requiredProperties.value()) {
- for (Property p : Property.values()) {
- if (p.getValue() == ~requiredProperty.getValue()
- && allRequiredProperties.contains(p)) {
- continue;
- }
- }
- allRequiredProperties.add(requiredProperty);
- }
- }
- return allRequiredProperties;
- }
-
- private void assertTestIsValid(String testName, ArraySet<Property> requiredProperies) {
- if (requiredProperies == null) {
- return;
- }
- final ArrayList<Property> unsupportedProperties = new ArrayList<>();
- for (Property property : requiredProperies) {
- if (!property.isSupported()) {
- unsupportedProperties.add(property);
- }
- }
- Assume.assumeTrue("Unsupported properties: "
- + propertiesToString(unsupportedProperties), unsupportedProperties.isEmpty());
- }
-
- public static ArraySet<Property> getRequiredProperties() {
- return mRequiredProperties;
- }
-
- private static String propertiesToString(Iterable<Property> properties) {
- return "[" + TextUtils.join(",", properties) + "]";
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RestrictedModeTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RestrictedModeTest.java
deleted file mode 100644
index f183f4e..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RestrictedModeTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.netpolicy.hostside;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public final class RestrictedModeTest extends AbstractRestrictBackgroundNetworkTestCase {
- @Before
- public void setUp() throws Exception {
- super.setUp();
- setRestrictedNetworkingMode(false);
- }
-
- @After
- public void tearDown() throws Exception {
- setRestrictedNetworkingMode(false);
- super.tearDown();
- }
-
- @Test
- public void testNetworkAccess() throws Exception {
- // go to foreground state and enable restricted mode
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
- setRestrictedNetworkingMode(true);
- assertTopNetworkAccess(false);
-
- // go to background state
- finishActivity();
- assertBackgroundNetworkAccess(false);
-
- // disable restricted mode and assert network access in foreground and background states
- setRestrictedNetworkingMode(false);
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
- assertTopNetworkAccess(true);
-
- // go to background state
- finishActivity();
- assertBackgroundNetworkAccess(true);
- }
-
- @Test
- public void testNetworkAccess_withBatterySaver() throws Exception {
- setBatterySaverMode(true);
- try {
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
-
- setRestrictedNetworkingMode(true);
- // App would be denied network access since Restricted mode is on.
- assertBackgroundNetworkAccess(false);
- setRestrictedNetworkingMode(false);
- // Given that Restricted mode is turned off, app should be able to access network again.
- assertBackgroundNetworkAccess(true);
- } finally {
- setBatterySaverMode(false);
- }
- }
-}
diff --git a/tests/cts/hostside-network-policy/app2/Android.bp b/tests/cts/hostside-network-policy/app2/Android.bp
deleted file mode 100644
index 6ef0b06..0000000
--- a/tests/cts/hostside-network-policy/app2/Android.bp
+++ /dev/null
@@ -1,39 +0,0 @@
-//
-// Copyright (C) 2024 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 {
- default_team: "trendy_team_framework_backstage_power",
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
- name: "CtsHostsideNetworkPolicyTestsApp2",
- defaults: ["cts_support_defaults"],
- platform_apis: true,
- static_libs: [
- "androidx.annotation_annotation",
- "CtsHostsideNetworkPolicyTestsAidl",
- "modules-utils-build",
- ],
- srcs: ["src/**/*.java"],
- // Tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
- "sts",
- ],
- certificate: ":cts-netpolicy-app",
-}
diff --git a/tests/cts/hostside-network-policy/app2/AndroidManifest.xml b/tests/cts/hostside-network-policy/app2/AndroidManifest.xml
deleted file mode 100644
index 668f2da..0000000
--- a/tests/cts/hostside-network-policy/app2/AndroidManifest.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2024 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.cts.netpolicy.hostside.app2">
-
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
- <uses-permission android:name="android.permission.INTERNET"/>
- <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
- <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
-
- <!--
- This application is used to listen to RESTRICT_BACKGROUND_CHANGED intents and store
- them in a shared preferences which is then read by the test app. These broadcasts are
- handled by 2 listeners, one defined the manifest and another dynamically registered by
- a service.
-
- The manifest-defined listener also handles ordered broadcasts used to share data with the
- test app.
-
- This application also provides a service, RemoteSocketFactoryService, that the test app can
- use to open sockets to remote hosts as a different user ID.
- -->
- <application android:usesCleartextTraffic="true"
- android:testOnly="true"
- android:debuggable="true">
-
- <activity android:name=".MyActivity"
- android:exported="true"/>
- <service android:name=".MyService"
- android:exported="true"/>
- <service android:name=".MyForegroundService"
- android:foregroundServiceType="specialUse"
- android:exported="true">
- <property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
- android:value="Connectivity" />
- </service>
- <receiver android:name=".MyBroadcastReceiver"
- android:exported="true">
- <intent-filter>
- <action android:name="android.net.conn.RESTRICT_BACKGROUND_CHANGED"/>
- <action android:name="com.android.cts.netpolicy.hostside.app2.action.GET_COUNTERS"/>
- <action android:name="com.android.cts.netpolicy.hostside.app2.action.GET_RESTRICT_BACKGROUND_STATUS"/>
- <action android:name="com.android.cts.netpolicy.hostside.app2.action.CHECK_NETWORK"/>
- <action android:name="com.android.cts.netpolicy.hostside.app2.action.SEND_NOTIFICATION"/>
- <action android:name="com.android.cts.netpolicy.hostside.app2.action.SHOW_TOAST"/>
- </intent-filter>
- </receiver>
- <service android:name=".MyJobService"
- android:permission="android.permission.BIND_JOB_SERVICE" />
- </application>
-
- <!--
- Adding this to make sure that receiving the broadcast is not restricted by
- package visibility restrictions.
- -->
- <queries>
- <package android:name="android" />
- </queries>
-
-</manifest>
diff --git a/tests/cts/hostside-network-policy/app2/res/drawable/ic_notification.png b/tests/cts/hostside-network-policy/app2/res/drawable/ic_notification.png
deleted file mode 100644
index 6ae570b..0000000
--- a/tests/cts/hostside-network-policy/app2/res/drawable/ic_notification.png
+++ /dev/null
Binary files differ
diff --git a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/Common.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/Common.java
deleted file mode 100644
index 1719f9b..0000000
--- a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/Common.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside.app2;
-
-import static com.android.cts.netpolicy.hostside.INetworkStateObserver.RESULT_ERROR_OTHER;
-import static com.android.cts.netpolicy.hostside.INetworkStateObserver.RESULT_ERROR_UNEXPECTED_CAPABILITIES;
-import static com.android.cts.netpolicy.hostside.INetworkStateObserver.RESULT_ERROR_UNEXPECTED_PROC_STATE;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.cts.netpolicy.hostside.INetworkStateObserver;
-import com.android.cts.netpolicy.hostside.NetworkCheckResult;
-
-import java.net.HttpURLConnection;
-import java.net.InetAddress;
-import java.net.URL;
-import java.util.concurrent.TimeUnit;
-
-public final class Common {
-
- static final String TAG = "CtsNetApp2";
-
- // Constants below must match values defined on app's
- // AbstractRestrictBackgroundNetworkTestCase.java
- static final String MANIFEST_RECEIVER = "ManifestReceiver";
- static final String DYNAMIC_RECEIVER = "DynamicReceiver";
-
- static final String ACTION_RECEIVER_READY =
- "com.android.cts.netpolicy.hostside.app2.action.RECEIVER_READY";
- static final String ACTION_FINISH_ACTIVITY =
- "com.android.cts.netpolicy.hostside.app2.action.FINISH_ACTIVITY";
- static final String ACTION_FINISH_JOB =
- "com.android.cts.netpolicy.hostside.app2.action.FINISH_JOB";
- static final String ACTION_SHOW_TOAST =
- "com.android.cts.netpolicy.hostside.app2.action.SHOW_TOAST";
- // Copied from com.android.server.net.NetworkPolicyManagerService class
- static final String ACTION_SNOOZE_WARNING =
- "com.android.server.net.action.SNOOZE_WARNING";
-
- private static final String DEFAULT_TEST_URL =
- "https://connectivitycheck.android.com/generate_204";
-
- static final String NOTIFICATION_TYPE_CONTENT = "CONTENT";
- static final String NOTIFICATION_TYPE_DELETE = "DELETE";
- static final String NOTIFICATION_TYPE_FULL_SCREEN = "FULL_SCREEN";
- static final String NOTIFICATION_TYPE_BUNDLE = "BUNDLE";
- static final String NOTIFICATION_TYPE_ACTION = "ACTION";
- static final String NOTIFICATION_TYPE_ACTION_BUNDLE = "ACTION_BUNDLE";
- static final String NOTIFICATION_TYPE_ACTION_REMOTE_INPUT = "ACTION_REMOTE_INPUT";
-
- static final String TEST_PKG = "com.android.cts.netpolicy.hostside";
- static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
- static final String KEY_SKIP_VALIDATION_CHECKS = TEST_PKG + ".skip_validation_checks";
- static final String KEY_CUSTOM_URL = TEST_PKG + ".custom_url";
-
- static final int TYPE_COMPONENT_ACTIVTY = 0;
- static final int TYPE_COMPONENT_FOREGROUND_SERVICE = 1;
- static final int TYPE_COMPONENT_EXPEDITED_JOB = 2;
- private static final int NETWORK_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(10);
-
- static int getUid(Context context) {
- final String packageName = context.getPackageName();
- try {
- return context.getPackageManager().getPackageUid(packageName, 0);
- } catch (NameNotFoundException e) {
- throw new IllegalStateException("Could not get UID for " + packageName, e);
- }
- }
-
- private static NetworkCheckResult createNetworkCheckResult(boolean connected, String details,
- NetworkInfo networkInfo) {
- final NetworkCheckResult checkResult = new NetworkCheckResult();
- checkResult.connected = connected;
- checkResult.details = details;
- checkResult.networkInfo = networkInfo;
- return checkResult;
- }
-
- private static boolean validateComponentState(Context context, int componentType,
- INetworkStateObserver observer) throws RemoteException {
- final ActivityManager activityManager = context.getSystemService(ActivityManager.class);
- switch (componentType) {
- case TYPE_COMPONENT_ACTIVTY: {
- final int procState = activityManager.getUidProcessState(Process.myUid());
- if (procState != ActivityManager.PROCESS_STATE_TOP) {
- observer.onNetworkStateChecked(RESULT_ERROR_UNEXPECTED_PROC_STATE,
- createNetworkCheckResult(false, "Unexpected procstate: " + procState,
- null));
- return false;
- }
- return true;
- }
- case TYPE_COMPONENT_FOREGROUND_SERVICE: {
- final int procState = activityManager.getUidProcessState(Process.myUid());
- if (procState != ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
- observer.onNetworkStateChecked(RESULT_ERROR_UNEXPECTED_PROC_STATE,
- createNetworkCheckResult(false, "Unexpected procstate: " + procState,
- null));
- return false;
- }
- return true;
- }
- case TYPE_COMPONENT_EXPEDITED_JOB: {
- final int capabilities = activityManager.getUidProcessCapabilities(Process.myUid());
- if ((capabilities
- & ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) == 0) {
- observer.onNetworkStateChecked(RESULT_ERROR_UNEXPECTED_CAPABILITIES,
- createNetworkCheckResult(false,
- "Unexpected capabilities: " + capabilities, null));
- return false;
- }
- return true;
- }
- default: {
- observer.onNetworkStateChecked(RESULT_ERROR_OTHER,
- createNetworkCheckResult(false, "Unknown component type: " + componentType,
- null));
- return false;
- }
- }
- }
-
- static void notifyNetworkStateObserver(Context context, Intent intent, int componentType) {
- if (intent == null) {
- return;
- }
- final Bundle extras = intent.getExtras();
- notifyNetworkStateObserver(context, extras, componentType);
- }
-
- static void notifyNetworkStateObserver(Context context, Bundle extras, int componentType) {
- if (extras == null) {
- return;
- }
- final INetworkStateObserver observer = INetworkStateObserver.Stub.asInterface(
- extras.getBinder(KEY_NETWORK_STATE_OBSERVER));
- if (observer != null) {
- final String customUrl = extras.getString(KEY_CUSTOM_URL);
- try {
- final boolean skipValidation = extras.getBoolean(KEY_SKIP_VALIDATION_CHECKS);
- if (!skipValidation && !validateComponentState(context, componentType, observer)) {
- return;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Error occurred while informing the validation result: " + e);
- }
- AsyncTask.execute(() -> {
- try {
- observer.onNetworkStateChecked(
- INetworkStateObserver.RESULT_SUCCESS_NETWORK_STATE_CHECKED,
- checkNetworkStatus(context, customUrl));
- } catch (RemoteException e) {
- Log.e(TAG, "Error occurred while notifying the observer: " + e);
- }
- });
- }
- }
-
- /**
- * Checks whether the network is available by attempting a connection to the given address
- * and returns a {@link NetworkCheckResult} object containing all the relevant details for
- * debugging. Uses a default address if the given address is {@code null}.
- *
- * <p>
- * The returned object has the following fields:
- *
- * <ul>
- * <li>{@code connected}: whether or not the connection was successful.
- * <li>{@code networkInfo}: the {@link NetworkInfo} describing the current active network as
- * visible to this app.
- * <li>{@code details}: A human readable string giving useful information about the success or
- * failure.
- * </ul>
- */
- static NetworkCheckResult checkNetworkStatus(Context context, String customUrl) {
- final String address = (customUrl == null) ? DEFAULT_TEST_URL : customUrl;
-
- // The current Android DNS resolver returns an UnknownHostException whenever network access
- // is blocked. This can get cached in the current process-local InetAddress cache. Clearing
- // the cache before attempting a connection ensures we never report a failure due to a
- // negative cache entry.
- InetAddress.clearDnsCache();
-
- final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
-
- final NetworkInfo networkInfo = cm.getActiveNetworkInfo();
- Log.d(TAG, "Running checkNetworkStatus() on thread "
- + Thread.currentThread().getName() + " for UID " + getUid(context)
- + "\n\tactiveNetworkInfo: " + networkInfo + "\n\tURL: " + address);
- boolean checkStatus = false;
- String checkDetails = "N/A";
- try {
- final URL url = new URL(address);
- final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn.setReadTimeout(NETWORK_TIMEOUT_MS);
- conn.setConnectTimeout(NETWORK_TIMEOUT_MS / 2);
- conn.setRequestMethod("GET");
- conn.connect();
- final int response = conn.getResponseCode();
- checkStatus = true;
- checkDetails = "HTTP response for " + address + ": " + response;
- } catch (Exception e) {
- checkStatus = false;
- checkDetails = "Exception getting " + address + ": " + e;
- }
- final NetworkCheckResult result = createNetworkCheckResult(checkStatus, checkDetails,
- networkInfo);
- Log.d(TAG, "Offering: " + result);
- return result;
- }
-}
diff --git a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyActivity.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyActivity.java
deleted file mode 100644
index d274c50..0000000
--- a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyActivity.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside.app2;
-
-import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_FINISH_ACTIVITY;
-import static com.android.cts.netpolicy.hostside.app2.Common.TAG;
-import static com.android.cts.netpolicy.hostside.app2.Common.TYPE_COMPONENT_ACTIVTY;
-
-import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.os.RemoteCallback;
-import android.util.Log;
-import android.view.WindowManager;
-
-import androidx.annotation.GuardedBy;
-
-/**
- * Activity used to bring process to foreground.
- */
-public class MyActivity extends Activity {
-
- @GuardedBy("this")
- private BroadcastReceiver finishCommandReceiver = null;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Log.d(TAG, "MyActivity.onCreate()");
-
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- }
-
- @Override
- public void finish() {
- synchronized (this) {
- if (finishCommandReceiver != null) {
- unregisterReceiver(finishCommandReceiver);
- finishCommandReceiver = null;
- }
- }
- super.finish();
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- Log.d(TAG, "MyActivity.onStart()");
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- Log.d(TAG, "MyActivity.onNewIntent()");
- setIntent(intent);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- Log.d(TAG, "MyActivity.onResume(): " + getIntent());
- Common.notifyNetworkStateObserver(this, getIntent(), TYPE_COMPONENT_ACTIVTY);
- synchronized (this) {
- finishCommandReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.d(TAG, "Finishing MyActivity");
- MyActivity.this.finish();
- }
- };
- registerReceiver(finishCommandReceiver, new IntentFilter(ACTION_FINISH_ACTIVITY),
- Context.RECEIVER_EXPORTED);
- }
- final RemoteCallback callback = getIntent().getParcelableExtra(
- Intent.EXTRA_REMOTE_CALLBACK);
- if (callback != null) {
- callback.sendResult(null);
- }
- }
-
- @Override
- protected void onDestroy() {
- Log.d(TAG, "MyActivity.onDestroy()");
- super.onDestroy();
- }
-}
diff --git a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyBroadcastReceiver.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyBroadcastReceiver.java
deleted file mode 100644
index 27aec8c..0000000
--- a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyBroadcastReceiver.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside.app2;
-
-import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
-
-import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_RECEIVER_READY;
-import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_SHOW_TOAST;
-import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_SNOOZE_WARNING;
-import static com.android.cts.netpolicy.hostside.app2.Common.MANIFEST_RECEIVER;
-import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_ACTION;
-import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_BUNDLE;
-import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_REMOTE_INPUT;
-import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_BUNDLE;
-import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_CONTENT;
-import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_DELETE;
-import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_FULL_SCREEN;
-import static com.android.cts.netpolicy.hostside.app2.Common.TAG;
-
-import android.app.Notification;
-import android.app.Notification.Action;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.RemoteInput;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.net.ConnectivityManager;
-import android.os.Bundle;
-import android.util.Log;
-import android.widget.Toast;
-
-/**
- * Receiver used to:
- * <ol>
- * <li>Count number of {@code RESTRICT_BACKGROUND_CHANGED} broadcasts received.
- * <li>Show a toast.
- * </ol>
- */
-public class MyBroadcastReceiver extends BroadcastReceiver {
-
- private final String mName;
-
- public MyBroadcastReceiver() {
- this(MANIFEST_RECEIVER);
- }
-
- MyBroadcastReceiver(String name) {
- Log.d(TAG, "Constructing MyBroadcastReceiver named " + name);
- mName = name;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.d(TAG, "onReceive() for " + mName + ": " + intent);
- final String action = intent.getAction();
- switch (action) {
- case ACTION_SNOOZE_WARNING:
- increaseCounter(context, action);
- break;
- case ACTION_RESTRICT_BACKGROUND_CHANGED:
- increaseCounter(context, action);
- break;
- case ACTION_RECEIVER_READY:
- final String message = mName + " is ready to rumble";
- Log.d(TAG, message);
- setResultData(message);
- break;
- case ACTION_SHOW_TOAST:
- showToast(context);
- break;
- default:
- Log.e(TAG, "received unexpected action: " + action);
- }
- }
-
- @Override
- public String toString() {
- return "[MyBroadcastReceiver: mName=" + mName + "]";
- }
-
- private void increaseCounter(Context context, String action) {
- final SharedPreferences prefs = context.getApplicationContext()
- .getSharedPreferences(mName, Context.MODE_PRIVATE);
- final int value = prefs.getInt(action, 0) + 1;
- Log.d(TAG, "increaseCounter('" + action + "'): setting '" + mName + "' to " + value);
- prefs.edit().putInt(action, value).apply();
- }
-
- static int getCounter(Context context, String action, String receiverName) {
- final SharedPreferences prefs = context.getSharedPreferences(receiverName,
- Context.MODE_PRIVATE);
- final int value = prefs.getInt(action, 0);
- Log.d(TAG, "getCounter('" + action + "', '" + receiverName + "'): " + value);
- return value;
- }
-
- static String getRestrictBackgroundStatus(Context context) {
- final ConnectivityManager cm = (ConnectivityManager) context
- .getSystemService(Context.CONNECTIVITY_SERVICE);
- final int apiStatus = cm.getRestrictBackgroundStatus();
- Log.d(TAG, "getRestrictBackgroundStatus: returning " + apiStatus);
- return String.valueOf(apiStatus);
- }
-
- /**
- * Sends a system notification containing actions with pending intents to launch the app's
- * main activitiy or service.
- */
- static void sendNotification(Context context, String channelId, int notificationId,
- String notificationType ) {
- Log.d(TAG, "sendNotification: id=" + notificationId + ", type=" + notificationType);
- final Intent serviceIntent = new Intent(context, MyService.class);
- final PendingIntent pendingIntent = PendingIntent.getService(context, 0, serviceIntent,
- PendingIntent.FLAG_MUTABLE);
- final Bundle bundle = new Bundle();
- bundle.putCharSequence("parcelable", "I am not");
-
- final Notification.Builder builder = new Notification.Builder(context, channelId)
- .setSmallIcon(R.drawable.ic_notification);
-
- Action action = null;
- switch (notificationType) {
- case NOTIFICATION_TYPE_CONTENT:
- builder
- .setContentTitle("Light, Cameras...")
- .setContentIntent(pendingIntent);
- break;
- case NOTIFICATION_TYPE_DELETE:
- builder.setDeleteIntent(pendingIntent);
- break;
- case NOTIFICATION_TYPE_FULL_SCREEN:
- builder.setFullScreenIntent(pendingIntent, true);
- break;
- case NOTIFICATION_TYPE_BUNDLE:
- bundle.putParcelable("Magnum P.I. (Pending Intent)", pendingIntent);
- builder.setExtras(bundle);
- break;
- case NOTIFICATION_TYPE_ACTION:
- action = new Action.Builder(
- R.drawable.ic_notification, "ACTION", pendingIntent)
- .build();
- builder.addAction(action);
- break;
- case NOTIFICATION_TYPE_ACTION_BUNDLE:
- bundle.putParcelable("Magnum A.P.I. (Action Pending Intent)", pendingIntent);
- action = new Action.Builder(
- R.drawable.ic_notification, "ACTION WITH BUNDLE", null)
- .addExtras(bundle)
- .build();
- builder.addAction(action);
- break;
- case NOTIFICATION_TYPE_ACTION_REMOTE_INPUT:
- bundle.putParcelable("Magnum R.I. (Remote Input)", null);
- final RemoteInput remoteInput = new RemoteInput.Builder("RI")
- .addExtras(bundle)
- .build();
- action = new Action.Builder(
- R.drawable.ic_notification, "ACTION WITH REMOTE INPUT", pendingIntent)
- .addRemoteInput(remoteInput)
- .build();
- builder.addAction(action);
- break;
- default:
- Log.e(TAG, "Unknown notification type: " + notificationType);
- return;
- }
-
- final Notification notification = builder.build();
- ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
- .notify(notificationId, notification);
- }
-
- private void showToast(Context context) {
- Toast.makeText(context, "Toast from CTS test", Toast.LENGTH_SHORT).show();
- setResultData("Shown");
- }
-}
diff --git a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyForegroundService.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyForegroundService.java
deleted file mode 100644
index 54cee3c..0000000
--- a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyForegroundService.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside.app2;
-
-import static com.android.cts.netpolicy.hostside.app2.Common.TAG;
-import static com.android.cts.netpolicy.hostside.app2.Common.TEST_PKG;
-import static com.android.cts.netpolicy.hostside.app2.Common.TYPE_COMPONENT_FOREGROUND_SERVICE;
-
-import android.R;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.Service;
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.cts.netpolicy.hostside.INetworkStateObserver;
-
-/**
- * Service used to change app state to FOREGROUND_SERVICE.
- */
-public class MyForegroundService extends Service {
- private static final String NOTIFICATION_CHANNEL_ID = "cts/MyForegroundService";
- private static final int FLAG_START_FOREGROUND = 1;
- private static final int FLAG_STOP_FOREGROUND = 2;
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Log.v(TAG, "MyForegroundService.onStartCommand(): " + intent);
- NotificationManager notificationManager = getSystemService(NotificationManager.class);
- notificationManager.createNotificationChannel(new NotificationChannel(
- NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
- NotificationManager.IMPORTANCE_DEFAULT));
- switch (intent.getFlags()) {
- case FLAG_START_FOREGROUND:
- Log.d(TAG, "Starting foreground");
- startForeground(42, new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
- .setSmallIcon(R.drawable.ic_dialog_alert) // any icon is fine
- .build());
- Common.notifyNetworkStateObserver(this, intent, TYPE_COMPONENT_FOREGROUND_SERVICE);
- break;
- case FLAG_STOP_FOREGROUND:
- Log.d(TAG, "Stopping foreground");
- stopForeground(true);
- break;
- default:
- Log.wtf(TAG, "Invalid flag on intent " + intent);
- }
- return START_STICKY;
- }
-}
diff --git a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyJobService.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyJobService.java
deleted file mode 100644
index eba55ed..0000000
--- a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyJobService.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.netpolicy.hostside.app2;
-
-import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_FINISH_JOB;
-import static com.android.cts.netpolicy.hostside.app2.Common.TAG;
-import static com.android.cts.netpolicy.hostside.app2.Common.TYPE_COMPONENT_EXPEDITED_JOB;
-
-import android.app.job.JobParameters;
-import android.app.job.JobService;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.util.Log;
-
-public class MyJobService extends JobService {
-
- private BroadcastReceiver mFinishCommandReceiver = null;
-
- @Override
- public void onCreate() {
- super.onCreate();
- Log.v(TAG, "MyJobService.onCreate()");
- }
-
- @Override
- public boolean onStartJob(JobParameters params) {
- Log.v(TAG, "MyJobService.onStartJob()");
- Common.notifyNetworkStateObserver(this, params.getTransientExtras(),
- TYPE_COMPONENT_EXPEDITED_JOB);
- mFinishCommandReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.v(TAG, "Finishing MyJobService");
- try {
- jobFinished(params, /*wantsReschedule=*/ false);
- } finally {
- if (mFinishCommandReceiver != null) {
- unregisterReceiver(mFinishCommandReceiver);
- mFinishCommandReceiver = null;
- }
- }
- }
- };
- registerReceiver(mFinishCommandReceiver, new IntentFilter(ACTION_FINISH_JOB),
- Context.RECEIVER_EXPORTED);
- return true;
- }
-
- @Override
- public boolean onStopJob(JobParameters params) {
- // If this job is stopped before it had a chance to send network status via
- // INetworkStateObserver, the test will fail. It could happen either due to test timing out
- // or this app moving to a lower proc_state and losing network access.
- Log.v(TAG, "MyJobService.onStopJob()");
- if (mFinishCommandReceiver != null) {
- unregisterReceiver(mFinishCommandReceiver);
- mFinishCommandReceiver = null;
- }
- return false;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- Log.v(TAG, "MyJobService.onDestroy()");
- }
-}
diff --git a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyService.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyService.java
deleted file mode 100644
index 71bcead..0000000
--- a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyService.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside.app2;
-
-import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
-
-import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_RECEIVER_READY;
-import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_SNOOZE_WARNING;
-import static com.android.cts.netpolicy.hostside.app2.Common.DYNAMIC_RECEIVER;
-import static com.android.cts.netpolicy.hostside.app2.Common.TAG;
-
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.Service;
-import android.app.job.JobInfo;
-import android.app.job.JobScheduler;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.cts.netpolicy.hostside.IMyService;
-import com.android.cts.netpolicy.hostside.INetworkCallback;
-import com.android.cts.netpolicy.hostside.NetworkCheckResult;
-import com.android.modules.utils.build.SdkLevel;
-
-/**
- * Service used to dynamically register a broadcast receiver.
- */
-public class MyService extends Service {
- private static final String NOTIFICATION_CHANNEL_ID = "MyService";
-
- ConnectivityManager mCm;
-
- private MyBroadcastReceiver mReceiver;
- private ConnectivityManager.NetworkCallback mNetworkCallback;
-
- // TODO: move MyBroadcast static functions here - they were kept there to make git diff easier.
-
- private IMyService.Stub mBinder = new IMyService.Stub() {
- @Override
- public void registerBroadcastReceiver() {
- if (mReceiver != null) {
- Log.d(TAG, "receiver already registered: " + mReceiver);
- return;
- }
- final Context context = getApplicationContext();
- final int flags = SdkLevel.isAtLeastT() ? RECEIVER_EXPORTED : 0;
- mReceiver = new MyBroadcastReceiver(DYNAMIC_RECEIVER);
- context.registerReceiver(mReceiver,
- new IntentFilter(ACTION_RECEIVER_READY), flags);
- context.registerReceiver(mReceiver,
- new IntentFilter(ACTION_RESTRICT_BACKGROUND_CHANGED), flags);
- context.registerReceiver(mReceiver,
- new IntentFilter(ACTION_SNOOZE_WARNING), flags);
- Log.d(TAG, "receiver registered");
- }
-
- @Override
- public int getCounters(String receiverName, String action) {
- return MyBroadcastReceiver.getCounter(getApplicationContext(), action, receiverName);
- }
-
- @Override
- public NetworkCheckResult checkNetworkStatus(String customUrl) {
- return Common.checkNetworkStatus(getApplicationContext(), customUrl);
- }
-
- @Override
- public String getRestrictBackgroundStatus() {
- return MyBroadcastReceiver.getRestrictBackgroundStatus(getApplicationContext());
- }
-
- @Override
- public void sendNotification(int notificationId, String notificationType) {
- MyBroadcastReceiver.sendNotification(getApplicationContext(), NOTIFICATION_CHANNEL_ID,
- notificationId, notificationType);
- }
-
- @Override
- public void registerNetworkCallback(final NetworkRequest request, INetworkCallback cb) {
- if (mNetworkCallback != null) {
- Log.d(TAG, "unregister previous network callback: " + mNetworkCallback);
- unregisterNetworkCallback();
- }
- Log.d(TAG, "registering network callback for " + request);
-
- mNetworkCallback = new ConnectivityManager.NetworkCallback() {
- @Override
- public void onBlockedStatusChanged(Network network, boolean blocked) {
- try {
- cb.onBlockedStatusChanged(network, blocked);
- } catch (RemoteException e) {
- Log.d(TAG, "Cannot send onBlockedStatusChanged: " + e);
- unregisterNetworkCallback();
- }
- }
-
- @Override
- public void onAvailable(Network network) {
- try {
- cb.onAvailable(network);
- } catch (RemoteException e) {
- Log.d(TAG, "Cannot send onAvailable: " + e);
- unregisterNetworkCallback();
- }
- }
-
- @Override
- public void onLost(Network network) {
- try {
- cb.onLost(network);
- } catch (RemoteException e) {
- Log.d(TAG, "Cannot send onLost: " + e);
- unregisterNetworkCallback();
- }
- }
-
- @Override
- public void onCapabilitiesChanged(Network network, NetworkCapabilities cap) {
- try {
- cb.onCapabilitiesChanged(network, cap);
- } catch (RemoteException e) {
- Log.d(TAG, "Cannot send onCapabilitiesChanged: " + e);
- unregisterNetworkCallback();
- }
- }
- };
- mCm.registerNetworkCallback(request, mNetworkCallback);
- try {
- cb.asBinder().linkToDeath(() -> unregisterNetworkCallback(), 0);
- } catch (RemoteException e) {
- unregisterNetworkCallback();
- }
- }
-
- @Override
- public void unregisterNetworkCallback() {
- Log.d(TAG, "unregistering network callback");
- if (mNetworkCallback != null) {
- mCm.unregisterNetworkCallback(mNetworkCallback);
- mNetworkCallback = null;
- }
- }
-
- @Override
- public int scheduleJob(JobInfo jobInfo) {
- final JobScheduler jobScheduler = getApplicationContext()
- .getSystemService(JobScheduler.class);
- return jobScheduler.schedule(jobInfo);
- }
- };
-
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
-
- @Override
- public void onCreate() {
- final Context context = getApplicationContext();
- ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
- .createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID,
- NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT));
- mCm = (ConnectivityManager) getApplicationContext()
- .getSystemService(Context.CONNECTIVITY_SERVICE);
- }
-
- @Override
- public void onDestroy() {
- final Context context = getApplicationContext();
- ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
- .deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
- if (mReceiver != null) {
- Log.d(TAG, "onDestroy(): unregistering " + mReceiver);
- getApplicationContext().unregisterReceiver(mReceiver);
- }
-
- super.onDestroy();
- }
-}
diff --git a/tests/cts/hostside-network-policy/certs/Android.bp b/tests/cts/hostside-network-policy/certs/Android.bp
deleted file mode 100644
index bfbc341..0000000
--- a/tests/cts/hostside-network-policy/certs/Android.bp
+++ /dev/null
@@ -1,9 +0,0 @@
-package {
- default_team: "trendy_team_framework_backstage_power",
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_app_certificate {
- name: "cts-netpolicy-app",
- certificate: "cts-net-app",
-}
diff --git a/tests/cts/hostside-network-policy/certs/README b/tests/cts/hostside-network-policy/certs/README
deleted file mode 100644
index b660a82..0000000
--- a/tests/cts/hostside-network-policy/certs/README
+++ /dev/null
@@ -1,2 +0,0 @@
-# Generated with:
-development/tools/make_key cts-net-app '/CN=cts-net-app'
diff --git a/tests/cts/hostside-network-policy/certs/cts-net-app.pk8 b/tests/cts/hostside-network-policy/certs/cts-net-app.pk8
deleted file mode 100644
index 1703e4e..0000000
--- a/tests/cts/hostside-network-policy/certs/cts-net-app.pk8
+++ /dev/null
Binary files differ
diff --git a/tests/cts/hostside-network-policy/certs/cts-net-app.x509.pem b/tests/cts/hostside-network-policy/certs/cts-net-app.x509.pem
deleted file mode 100644
index a15ff48..0000000
--- a/tests/cts/hostside-network-policy/certs/cts-net-app.x509.pem
+++ /dev/null
@@ -1,19 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDAjCCAeqgAwIBAgIJAMhWwIIqr1r6MA0GCSqGSIb3DQEBCwUAMBYxFDASBgNV
-BAMMC2N0cy1uZXQtYXBwMB4XDTE4MDYyMDAyMjAwN1oXDTQ1MTEwNTAyMjAwN1ow
-FjEUMBIGA1UEAwwLY3RzLW5ldC1hcHAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
-ggEKAoIBAQDefOayWQss1E+FQIONK6IhlXhe0BEyHshIrnPOOmuCPa/Svfbnmziy
-hr1KTjaQ3ET/mGShwlt6AUti7nKx9aB71IJp5mSBuwW62A8jvN3yNOo45YV8+n1o
-TrEoMWMf7hQmoOSqaSJ+VFuVms/kPSEh99okDgHCej6rsEkEcDoh6pJajQyUYDwR
-SNAF8SrqCDhqFbZW/LWedvuikCUlNtzuv7/GrcLcsiWEfHv7UOBKpMjLo9BhD1XF
-IefnxImcBQrQGMnE9TLixBiEeX5yauLgbZuxBqD/zsI2TH1FjxTeuJan83kLbqqH
-FgyvPaUjwckAdQPyom7ZUYFnBc0LQ9xzAgMBAAGjUzBRMB0GA1UdDgQWBBRZrBEw
-tAB2WNXj8dQ7ZOuJ34kY5DAfBgNVHSMEGDAWgBRZrBEwtAB2WNXj8dQ7ZOuJ34kY
-5DAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQDeI9AnLW6l/39y
-z96w/ldxZVFPzBRiFIsJsPHVyXlD5vUHZv/ju2jFn8TZSZR5TK0bzCEoVLp34Sho
-bbS0magP82yIvCRibyoyD+TDNnZkNJwjYnikE+/oyshTSQtpkn/rDA+0Y09BUC1E
-N2I6bV9pTXLFg7oah2FmqPRPzhgeYUKENgOQkrrjUCn6y0i/k374n7aftzdniSIz
-2kCRVEeN9gws6CnoMPx0vr32v/JVuPV6zfdJYadgj/eFRyTNE4msd9kE82Wc46eU
-YiI+LuXZ3ZMUNWGY7MK2pOUUS52JsBQ3K235dA5WaU4x8OBlY/WkNYX/eLbNs5jj
-FzLmhZZ1
------END CERTIFICATE-----
diff --git a/tests/cts/hostside-network-policy/instrumentation_arguments/Android.bp b/tests/cts/hostside-network-policy/instrumentation_arguments/Android.bp
deleted file mode 100644
index cdede36..0000000
--- a/tests/cts/hostside-network-policy/instrumentation_arguments/Android.bp
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (C) 2024 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 {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-filegroup {
- name: "ArgumentConstants",
- srcs: ["src/**/*.java"],
-}
diff --git a/tests/cts/hostside-network-policy/instrumentation_arguments/src/com/android/cts/netpolicy/arguments/InstrumentationArguments.java b/tests/cts/hostside-network-policy/instrumentation_arguments/src/com/android/cts/netpolicy/arguments/InstrumentationArguments.java
deleted file mode 100644
index 0fe98e9..0000000
--- a/tests/cts/hostside-network-policy/instrumentation_arguments/src/com/android/cts/netpolicy/arguments/InstrumentationArguments.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2024 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.cts.netpolicy.arguments;
-
-public interface InstrumentationArguments {
- String ARG_WAIVE_BIND_PRIORITY = "waive_bind_priority";
- String ARG_CONNECTION_CHECK_CUSTOM_URL = "connection_check_custom_url";
-}
diff --git a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideConnOnActivityStartTest.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideConnOnActivityStartTest.java
deleted file mode 100644
index 422231d..0000000
--- a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideConnOnActivityStartTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.cts.netpolicy;
-
-import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
-
-import android.platform.test.annotations.FlakyTest;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.invoker.TestInformation;
-import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
-import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
-
-import org.junit.Test;
-
-import java.util.Map;
-
-@FlakyTest(bugId = 288324467)
-public class HostsideConnOnActivityStartTest extends HostsideNetworkPolicyTestCase {
- private static final String TEST_CLASS = TEST_PKG + ".ConnOnActivityStartTest";
-
- @BeforeClassWithInfo
- public static void setUpOnce(TestInformation testInfo) throws Exception {
- uninstallPackage(testInfo, TEST_APP2_PKG, false);
- installPackage(testInfo, TEST_APP2_APK);
- }
-
- @AfterClassWithInfo
- public static void tearDownOnce(TestInformation testInfo) throws DeviceNotAvailableException {
- uninstallPackage(testInfo, TEST_APP2_PKG, true);
- }
-
- @Test
- public void testStartActivity_batterySaver() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_CLASS, "testStartActivity_batterySaver");
- }
-
- @Test
- public void testStartActivity_dataSaver() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_CLASS, "testStartActivity_dataSaver");
- }
-
- @FlakyTest(bugId = 231440256)
- @Test
- public void testStartActivity_doze() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_CLASS, "testStartActivity_doze");
- }
-
- @Test
- public void testStartActivity_appStandby() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_CLASS, "testStartActivity_appStandby");
- }
-
- // TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side test.
- @Test
- public void testStartActivity_default() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_CLASS, "testStartActivity_default",
- Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
- }
-}
diff --git a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideDefaultNetworkRestrictionsTests.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideDefaultNetworkRestrictionsTests.java
deleted file mode 100644
index 62952bb..0000000
--- a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideDefaultNetworkRestrictionsTests.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2024 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.cts.netpolicy;
-
-import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
-
-import android.platform.test.annotations.FlakyTest;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Map;
-
-// TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side tests.
-@FlakyTest(bugId = 288324467)
-public class HostsideDefaultNetworkRestrictionsTests extends HostsideNetworkPolicyTestCase {
- private static final String METERED_TEST_CLASS = TEST_PKG + ".DefaultRestrictionsMeteredTest";
- private static final String NON_METERED_TEST_CLASS =
- TEST_PKG + ".DefaultRestrictionsNonMeteredTest";
-
- @Before
- public void setUp() throws Exception {
- uninstallPackage(TEST_APP2_PKG, false);
- installPackage(TEST_APP2_APK);
- }
-
- @After
- public void tearDown() throws Exception {
- uninstallPackage(TEST_APP2_PKG, true);
- }
-
- private void runMeteredTest(String methodName) throws DeviceNotAvailableException {
- runDeviceTestsWithCustomOptions(TEST_PKG, METERED_TEST_CLASS, methodName,
- Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
- }
-
- private void runNonMeteredTest(String methodName) throws DeviceNotAvailableException {
- runDeviceTestsWithCustomOptions(TEST_PKG, NON_METERED_TEST_CLASS, methodName,
- Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
- }
-
- @Test
- public void testMeteredNetworkAccess_defaultRestrictions_testActivityNetworkAccess()
- throws Exception {
- runMeteredTest("testActivityNetworkAccess");
- }
-
- @Test
- public void testMeteredNetworkAccess_defaultRestrictions_testFgsNetworkAccess()
- throws Exception {
- runMeteredTest("testFgsNetworkAccess");
- }
-
- @Test
- public void testMeteredNetworkAccess_defaultRestrictions_inFullAllowlist() throws Exception {
- runMeteredTest("testBackgroundNetworkAccess_inFullAllowlist");
- }
-
- @Test
- public void testMeteredNetworkAccess_defaultRestrictions_inExceptIdleAllowlist()
- throws Exception {
- runMeteredTest("testBackgroundNetworkAccess_inExceptIdleAllowlist");
- }
-
- @Test
- public void testNonMeteredNetworkAccess_defaultRestrictions_testActivityNetworkAccess()
- throws Exception {
- runNonMeteredTest("testActivityNetworkAccess");
- }
-
- @Test
- public void testNonMeteredNetworkAccess_defaultRestrictions_testFgsNetworkAccess()
- throws Exception {
- runNonMeteredTest("testFgsNetworkAccess");
- }
-
- @Test
- public void testNonMeteredNetworkAccess_defaultRestrictions_inFullAllowlist() throws Exception {
- runNonMeteredTest("testBackgroundNetworkAccess_inFullAllowlist");
- }
-
- @Test
- public void testNonMeteredNetworkAccess_defaultRestrictions_inExceptIdleAllowlist()
- throws Exception {
- runNonMeteredTest("testBackgroundNetworkAccess_inExceptIdleAllowlist");
- }
-}
diff --git a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkCallbackTests.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkCallbackTests.java
deleted file mode 100644
index 2c2b118..0000000
--- a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkCallbackTests.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2019 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.cts.netpolicy;
-
-import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
-
-import android.platform.test.annotations.FlakyTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Map;
-
-@FlakyTest(bugId = 288324467)
-public class HostsideNetworkCallbackTests extends HostsideNetworkPolicyTestCase {
-
- @Before
- public void setUp() throws Exception {
- uninstallPackage(TEST_APP2_PKG, false);
- installPackage(TEST_APP2_APK);
- }
-
- @After
- public void tearDown() throws Exception {
- uninstallPackage(TEST_APP2_PKG, true);
- }
-
- @Test
- public void testOnBlockedStatusChanged_dataSaver() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG,
- TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_dataSaver");
- }
-
- @Test
- public void testOnBlockedStatusChanged_powerSaver() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG,
- TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_powerSaver");
- }
-
- // TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side test.
- @Test
- public void testOnBlockedStatusChanged_default() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".NetworkCallbackTest",
- "testOnBlockedStatusChanged_default", Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
- }
-}
-
diff --git a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyManagerTests.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyManagerTests.java
deleted file mode 100644
index 8ffe360..0000000
--- a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyManagerTests.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.netpolicy;
-
-import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Map;
-
-public class HostsideNetworkPolicyManagerTests extends HostsideNetworkPolicyTestCase {
- @Before
- public void setUp() throws Exception {
- uninstallPackage(TEST_APP2_PKG, false);
- installPackage(TEST_APP2_APK);
- }
-
- @After
- public void tearDown() throws Exception {
- uninstallPackage(TEST_APP2_PKG, true);
- }
-
- @Test
- public void testIsUidNetworkingBlocked_withUidNotBlocked() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG,
- TEST_PKG + ".NetworkPolicyManagerTest",
- "testIsUidNetworkingBlocked_withUidNotBlocked");
- }
-
- @Test
- public void testIsUidNetworkingBlocked_withSystemUid() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG,
- TEST_PKG + ".NetworkPolicyManagerTest", "testIsUidNetworkingBlocked_withSystemUid");
- }
-
- @Test
- public void testIsUidNetworkingBlocked_withDataSaverMode() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG,
- TEST_PKG + ".NetworkPolicyManagerTest",
- "testIsUidNetworkingBlocked_withDataSaverMode");
- }
-
- @Test
- public void testIsUidNetworkingBlocked_withRestrictedNetworkingMode() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG,
- TEST_PKG + ".NetworkPolicyManagerTest",
- "testIsUidNetworkingBlocked_withRestrictedNetworkingMode");
- }
-
- @Test
- public void testIsUidNetworkingBlocked_withPowerSaverMode() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG,
- TEST_PKG + ".NetworkPolicyManagerTest",
- "testIsUidNetworkingBlocked_withPowerSaverMode");
- }
-
- @Test
- public void testIsUidRestrictedOnMeteredNetworks() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG,
- TEST_PKG + ".NetworkPolicyManagerTest", "testIsUidRestrictedOnMeteredNetworks");
- }
-
- // TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side test.
- @Test
- public void testIsUidNetworkingBlocked_whenInBackground() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".NetworkPolicyManagerTest",
- "testIsUidNetworkingBlocked_whenInBackground",
- Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
- }
-}
diff --git a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyTestCase.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyTestCase.java
deleted file mode 100644
index 6de6b17..0000000
--- a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyTestCase.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2024 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.cts.netpolicy;
-
-import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_CONNECTION_CHECK_CUSTOM_URL;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-
-import com.android.ddmlib.Log;
-import com.android.tradefed.config.Option;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.invoker.TestInformation;
-import com.android.tradefed.targetprep.BuildError;
-import com.android.tradefed.targetprep.TargetSetupError;
-import com.android.tradefed.targetprep.suite.SuiteApkInstaller;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
-import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
-import com.android.tradefed.util.RunUtil;
-
-import org.junit.runner.RunWith;
-
-import java.util.Map;
-
-@RunWith(DeviceJUnit4ClassRunner.class)
-abstract class HostsideNetworkPolicyTestCase extends BaseHostJUnit4Test {
- protected static final boolean DEBUG = false;
- protected static final String TAG = "HostsideNetworkPolicyTests";
- protected static final String TEST_PKG = "com.android.cts.netpolicy.hostside";
- protected static final String TEST_APK = "CtsHostsideNetworkPolicyTestsApp.apk";
- protected static final String TEST_APP2_PKG = "com.android.cts.netpolicy.hostside.app2";
- protected static final String TEST_APP2_APK = "CtsHostsideNetworkPolicyTestsApp2.apk";
-
- @Option(name = "custom-url", importance = Option.Importance.IF_UNSET,
- description = "A custom url to use for testing network connections")
- protected String mCustomUrl;
-
- @BeforeClassWithInfo
- public static void setUpOnceBase(TestInformation testInfo) throws Exception {
- uninstallPackage(testInfo, TEST_PKG, false);
- installPackage(testInfo, TEST_APK);
- }
-
- @AfterClassWithInfo
- public static void tearDownOnceBase(TestInformation testInfo)
- throws DeviceNotAvailableException {
- uninstallPackage(testInfo, TEST_PKG, true);
- }
-
- // Custom static method to install the specified package, this is used to bypass auto-cleanup
- // per test in BaseHostJUnit4.
- protected static void installPackage(TestInformation testInfo, String apk)
- throws DeviceNotAvailableException, TargetSetupError {
- assertNotNull(testInfo);
- final int userId = testInfo.getDevice().getCurrentUser();
- final SuiteApkInstaller installer = new SuiteApkInstaller();
- // Force the apk clean up
- installer.setCleanApk(true);
- installer.addTestFileName(apk);
- installer.setUserId(userId);
- installer.setShouldGrantPermission(true);
- installer.addInstallArg("-t");
- try {
- installer.setUp(testInfo);
- } catch (BuildError e) {
- throw new TargetSetupError(
- e.getMessage(), e, testInfo.getDevice().getDeviceDescriptor(), e.getErrorId());
- }
- }
-
- protected void installPackage(String apk) throws DeviceNotAvailableException, TargetSetupError {
- installPackage(getTestInformation(), apk);
- }
-
- protected static void uninstallPackage(TestInformation testInfo, String packageName,
- boolean shouldSucceed)
- throws DeviceNotAvailableException {
- assertNotNull(testInfo);
- final String result = testInfo.getDevice().uninstallPackage(packageName);
- if (shouldSucceed) {
- assertNull("uninstallPackage(" + packageName + ") failed: " + result, result);
- }
- }
-
- protected void uninstallPackage(String packageName,
- boolean shouldSucceed)
- throws DeviceNotAvailableException {
- uninstallPackage(getTestInformation(), packageName, shouldSucceed);
- }
-
- protected void assertPackageUninstalled(String packageName) throws DeviceNotAvailableException {
- final String command = "cmd package list packages " + packageName;
- final int max_tries = 5;
- for (int i = 1; i <= max_tries; i++) {
- final String result = runCommand(command);
- if (result.trim().isEmpty()) {
- return;
- }
- // 'list packages' filters by substring, so we need to iterate with the results
- // and check one by one, otherwise 'com.android.cts.netpolicy.hostside' could return
- // 'com.android.cts.netpolicy.hostside.app2'
- boolean found = false;
- for (String line : result.split("[\\r\\n]+")) {
- if (line.endsWith(packageName)) {
- found = true;
- break;
- }
- }
- if (!found) {
- return;
- }
- Log.v(TAG, "Package " + packageName + " not uninstalled yet (" + result
- + "); sleeping 1s before polling again");
- RunUtil.getDefault().sleep(1000);
- }
- fail("Package '" + packageName + "' not uinstalled after " + max_tries + " seconds");
- }
-
- protected int getUid(String packageName) throws DeviceNotAvailableException {
- final int currentUser = getDevice().getCurrentUser();
- final String uidLines = runCommand(
- "cmd package list packages -U --user " + currentUser + " " + packageName);
- for (String uidLine : uidLines.split("\n")) {
- if (uidLine.startsWith("package:" + packageName + " uid:")) {
- final String[] uidLineParts = uidLine.split(":");
- // 3rd entry is package uid
- return Integer.parseInt(uidLineParts[2].trim());
- }
- }
- throw new IllegalStateException("Failed to find the test app on the device; pkg="
- + packageName + ", u=" + currentUser);
- }
-
- protected boolean runDeviceTestsWithCustomOptions(String packageName, String className)
- throws DeviceNotAvailableException {
- return runDeviceTestsWithCustomOptions(packageName, className, null);
- }
-
- protected boolean runDeviceTestsWithCustomOptions(String packageName, String className,
- String methodName) throws DeviceNotAvailableException {
- return runDeviceTestsWithCustomOptions(packageName, className, methodName, null);
- }
-
- protected boolean runDeviceTestsWithCustomOptions(String packageName, String className,
- String methodName, Map<String, String> testArgs) throws DeviceNotAvailableException {
- final DeviceTestRunOptions deviceTestRunOptions = new DeviceTestRunOptions(packageName)
- .setTestClassName(className)
- .setTestMethodName(methodName);
-
- // Currently there is only one custom option that the test exposes.
- if (mCustomUrl != null) {
- deviceTestRunOptions.addInstrumentationArg(ARG_CONNECTION_CHECK_CUSTOM_URL, mCustomUrl);
- }
- // Pass over any test specific arguments.
- if (testArgs != null) {
- for (Map.Entry<String, String> arg : testArgs.entrySet()) {
- deviceTestRunOptions.addInstrumentationArg(arg.getKey(), arg.getValue());
- }
- }
- return runDeviceTests(deviceTestRunOptions);
- }
-
- protected String runCommand(String command) throws DeviceNotAvailableException {
- Log.d(TAG, "Command: '" + command + "'");
- final String output = getDevice().executeShellCommand(command);
- if (DEBUG) Log.v(TAG, "Output: " + output.trim());
- return output;
- }
-}
diff --git a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideRestrictBackgroundNetworkTests.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideRestrictBackgroundNetworkTests.java
deleted file mode 100644
index 0261c7d..0000000
--- a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideRestrictBackgroundNetworkTests.java
+++ /dev/null
@@ -1,466 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy;
-
-import static org.junit.Assert.fail;
-
-import android.platform.test.annotations.FlakyTest;
-import android.platform.test.annotations.SecurityTest;
-
-import com.android.ddmlib.Log;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.util.RunUtil;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-@FlakyTest(bugId = 288324467)
-public class HostsideRestrictBackgroundNetworkTests extends HostsideNetworkPolicyTestCase {
-
- @Before
- public void setUp() throws Exception {
- uninstallPackage(TEST_APP2_PKG, false);
- installPackage(TEST_APP2_APK);
- }
-
- @After
- public void tearDown() throws Exception {
- uninstallPackage(TEST_APP2_PKG, true);
- }
-
- @SecurityTest
- @Test
- public void testDataWarningReceiver() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataWarningReceiverTest",
- "testSnoozeWarningNotReceived");
- }
-
- /**************************
- * Data Saver Mode tests. *
- **************************/
-
- @Test
- public void testDataSaverMode_disabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
- "testGetRestrictBackgroundStatus_disabled");
- }
-
- @Test
- public void testDataSaverMode_whitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
- "testGetRestrictBackgroundStatus_whitelisted");
- }
-
- @Test
- public void testDataSaverMode_enabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
- "testGetRestrictBackgroundStatus_enabled");
- }
-
- @Test
- public void testDataSaverMode_blacklisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
- "testGetRestrictBackgroundStatus_blacklisted");
- }
-
- @Test
- public void testDataSaverMode_reinstall() throws Exception {
- final int oldUid = getUid(TEST_APP2_PKG);
-
- // Make sure whitelist is revoked when package is removed
- addRestrictBackgroundWhitelist(oldUid);
-
- uninstallPackage(TEST_APP2_PKG, true);
- assertPackageUninstalled(TEST_APP2_PKG);
- assertRestrictBackgroundWhitelist(oldUid, false);
-
- installPackage(TEST_APP2_APK);
- final int newUid = getUid(TEST_APP2_PKG);
- assertRestrictBackgroundWhitelist(oldUid, false);
- assertRestrictBackgroundWhitelist(newUid, false);
- }
-
- @Test
- public void testDataSaverMode_requiredWhitelistedPackages() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
- "testGetRestrictBackgroundStatus_requiredWhitelistedPackages");
- }
-
- @Test
- public void testDataSaverMode_broadcastNotSentOnUnsupportedDevices() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
- "testBroadcastNotSentOnUnsupportedDevices");
- }
-
- /*****************************
- * Battery Saver Mode tests. *
- *****************************/
-
- @Test
- public void testBatterySaverModeMetered_disabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
- "testBackgroundNetworkAccess_disabled");
- }
-
- @Test
- public void testBatterySaverModeMetered_whitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
- "testBackgroundNetworkAccess_whitelisted");
- }
-
- @Test
- public void testBatterySaverModeMetered_enabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
- "testBackgroundNetworkAccess_enabled");
- }
-
- @Test
- public void testBatterySaverMode_reinstall() throws Exception {
- if (!isDozeModeEnabled()) {
- Log.w(TAG, "testBatterySaverMode_reinstall() skipped because device does not support "
- + "Doze Mode");
- return;
- }
-
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
-
- uninstallPackage(TEST_APP2_PKG, true);
- assertPackageUninstalled(TEST_APP2_PKG);
- assertPowerSaveModeWhitelist(TEST_APP2_PKG, false);
-
- installPackage(TEST_APP2_APK);
- assertPowerSaveModeWhitelist(TEST_APP2_PKG, false);
- }
-
- @Test
- public void testBatterySaverModeNonMetered_disabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
- "testBackgroundNetworkAccess_disabled");
- }
-
- @Test
- public void testBatterySaverModeNonMetered_whitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
- "testBackgroundNetworkAccess_whitelisted");
- }
-
- @Test
- public void testBatterySaverModeNonMetered_enabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
- "testBackgroundNetworkAccess_enabled");
- }
-
- /*******************
- * App idle tests. *
- *******************/
-
- @Test
- public void testAppIdleMetered_disabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
- "testBackgroundNetworkAccess_disabled");
- }
-
- @Test
- public void testAppIdleMetered_whitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
- "testBackgroundNetworkAccess_whitelisted");
- }
-
- @Test
- public void testAppIdleMetered_tempWhitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
- "testBackgroundNetworkAccess_tempWhitelisted");
- }
-
- @Test
- public void testAppIdleMetered_enabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
- "testBackgroundNetworkAccess_enabled");
- }
-
- @Test
- public void testAppIdleMetered_idleWhitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
- "testAppIdleNetworkAccess_idleWhitelisted");
- }
-
- // TODO: currently power-save mode and idle uses the same whitelist, so this test would be
- // redundant (as it would be testing the same as testBatterySaverMode_reinstall())
- // public void testAppIdle_reinstall() throws Exception {
- // }
-
- @Test
- public void testAppIdleNonMetered_disabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
- "testBackgroundNetworkAccess_disabled");
- }
-
-
- @Test
- public void testAppIdleNonMetered_whitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
- "testBackgroundNetworkAccess_whitelisted");
- }
-
- @Test
- public void testAppIdleNonMetered_tempWhitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
- "testBackgroundNetworkAccess_tempWhitelisted");
- }
-
- @Test
- public void testAppIdleNonMetered_enabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
- "testBackgroundNetworkAccess_enabled");
- }
-
- @Test
- public void testAppIdleNonMetered_idleWhitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
- "testAppIdleNetworkAccess_idleWhitelisted");
- }
-
- @Test
- public void testAppIdleNonMetered_whenCharging() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
- "testAppIdleNetworkAccess_whenCharging");
- }
-
- @Test
- public void testAppIdleMetered_whenCharging() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
- "testAppIdleNetworkAccess_whenCharging");
- }
-
- @Test
- public void testAppIdle_toast() throws Exception {
- // Check that showing a toast doesn't bring an app out of standby
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
- "testAppIdle_toast");
- }
-
- /********************
- * Doze Mode tests. *
- ********************/
-
- @Test
- public void testDozeModeMetered_disabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
- "testBackgroundNetworkAccess_disabled");
- }
-
- @Test
- public void testDozeModeMetered_whitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
- "testBackgroundNetworkAccess_whitelisted");
- }
-
- @Test
- public void testDozeModeMetered_enabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
- "testBackgroundNetworkAccess_enabled");
- }
-
- @Test
- public void testDozeModeMetered_enabledButWhitelistedOnNotificationAction() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
- "testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction");
- }
-
- // TODO: currently power-save mode and idle uses the same whitelist, so this test would be
- // redundant (as it would be testing the same as testBatterySaverMode_reinstall())
- // public void testDozeMode_reinstall() throws Exception {
- // }
-
- @Test
- public void testDozeModeNonMetered_disabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
- "testBackgroundNetworkAccess_disabled");
- }
-
- @Test
- public void testDozeModeNonMetered_whitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
- "testBackgroundNetworkAccess_whitelisted");
- }
-
- @Test
- public void testDozeModeNonMetered_enabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
- "testBackgroundNetworkAccess_enabled");
- }
-
- @Test
- public void testDozeModeNonMetered_enabledButWhitelistedOnNotificationAction()
- throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
- "testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction");
- }
-
- /**********************
- * Mixed modes tests. *
- **********************/
-
- @Test
- public void testDataAndBatterySaverModes_meteredNetwork() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
- "testDataAndBatterySaverModes_meteredNetwork");
- }
-
- @Test
- public void testDataAndBatterySaverModes_nonMeteredNetwork() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
- "testDataAndBatterySaverModes_nonMeteredNetwork");
- }
-
- @Test
- public void testDozeAndBatterySaverMode_powerSaveWhitelists() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
- "testDozeAndBatterySaverMode_powerSaveWhitelists");
- }
-
- @Test
- public void testDozeAndAppIdle_powerSaveWhitelists() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
- "testDozeAndAppIdle_powerSaveWhitelists");
- }
-
- @Test
- public void testAppIdleAndDoze_tempPowerSaveWhitelists() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
- "testAppIdleAndDoze_tempPowerSaveWhitelists");
- }
-
- @Test
- public void testAppIdleAndBatterySaver_tempPowerSaveWhitelists() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
- "testAppIdleAndBatterySaver_tempPowerSaveWhitelists");
- }
-
- @Test
- public void testDozeAndAppIdle_appIdleWhitelist() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
- "testDozeAndAppIdle_appIdleWhitelist");
- }
-
- @Test
- public void testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
- "testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists");
- }
-
- @Test
- public void testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
- "testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists");
- }
-
- /**************************
- * Restricted mode tests. *
- **************************/
-
- @Test
- public void testNetworkAccess_restrictedMode() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".RestrictedModeTest",
- "testNetworkAccess");
- }
-
- @Test
- public void testNetworkAccess_restrictedMode_withBatterySaver() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".RestrictedModeTest",
- "testNetworkAccess_withBatterySaver");
- }
-
- /************************
- * Expedited job tests. *
- ************************/
-
- @Test
- public void testMeteredNetworkAccess_expeditedJob() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".ExpeditedJobMeteredTest");
- }
-
- @Test
- public void testNonMeteredNetworkAccess_expeditedJob() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".ExpeditedJobNonMeteredTest");
- }
-
- /*******************
- * Helper methods. *
- *******************/
-
- private void assertRestrictBackgroundWhitelist(int uid, boolean expected) throws Exception {
- final int max_tries = 5;
- boolean actual = false;
- for (int i = 1; i <= max_tries; i++) {
- final String output = runCommand("cmd netpolicy list restrict-background-whitelist ");
- actual = output.contains(Integer.toString(uid));
- if (expected == actual) {
- return;
- }
- Log.v(TAG, "whitelist check for uid " + uid + " doesn't match yet (expected "
- + expected + ", got " + actual + "); sleeping 1s before polling again");
- RunUtil.getDefault().sleep(1000);
- }
- fail("whitelist check for uid " + uid + " failed: expected "
- + expected + ", got " + actual);
- }
-
- private void assertPowerSaveModeWhitelist(String packageName, boolean expected)
- throws Exception {
- // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
- // need to use netpolicy for whitelisting
- assertDelayedCommand("dumpsys deviceidle whitelist =" + packageName,
- Boolean.toString(expected));
- }
-
- /**
- * Asserts the result of a command, wait and re-running it a couple times if necessary.
- */
- private void assertDelayedCommand(String command, String expectedResult)
- throws InterruptedException, DeviceNotAvailableException {
- final int maxTries = 5;
- for (int i = 1; i <= maxTries; i++) {
- final String result = runCommand(command).trim();
- if (result.equals(expectedResult)) return;
- Log.v(TAG, "Command '" + command + "' returned '" + result + " instead of '"
- + expectedResult + "' on attempt #; sleeping 1s before polling again");
- RunUtil.getDefault().sleep(1000);
- }
- fail("Command '" + command + "' did not return '" + expectedResult + "' after " + maxTries
- + " attempts");
- }
-
- protected void addRestrictBackgroundWhitelist(int uid) throws Exception {
- runCommand("cmd netpolicy add restrict-background-whitelist " + uid);
- assertRestrictBackgroundWhitelist(uid, true);
- }
-
- private void addPowerSaveModeWhitelist(String packageName) throws Exception {
- Log.i(TAG, "Adding package " + packageName + " to power-save-mode whitelist");
- // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
- // need to use netpolicy for whitelisting
- runCommand("dumpsys deviceidle whitelist +" + packageName);
- assertPowerSaveModeWhitelist(packageName, true);
- }
-
- protected boolean isDozeModeEnabled() throws Exception {
- final String result = runCommand("cmd deviceidle enabled deep").trim();
- return result.equals("1");
- }
-}
diff --git a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/NetworkPolicyTestsPreparer.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/NetworkPolicyTestsPreparer.java
deleted file mode 100644
index cbf2f4d..0000000
--- a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/NetworkPolicyTestsPreparer.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.netpolicy;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.TestInformation;
-import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.targetprep.ITargetPreparer;
-
-public class NetworkPolicyTestsPreparer implements ITargetPreparer {
- private ITestDevice mDevice;
- private boolean mOriginalAirplaneModeEnabled;
- private String mOriginalAppStandbyEnabled;
- private String mOriginalBatteryStatsConstants;
- private final static String KEY_STABLE_CHARGING_DELAY_MS = "battery_charged_delay_ms";
- private final static int DESIRED_STABLE_CHARGING_DELAY_MS = 0;
-
- @Override
- public void setUp(TestInformation testInformation) throws DeviceNotAvailableException {
- mDevice = testInformation.getDevice();
- mOriginalAppStandbyEnabled = getAppStandbyEnabled();
- setAppStandbyEnabled("1");
- LogUtil.CLog.d("Original app_standby_enabled: " + mOriginalAppStandbyEnabled);
-
- mOriginalBatteryStatsConstants = getBatteryStatsConstants();
- setBatteryStatsConstants(
- KEY_STABLE_CHARGING_DELAY_MS + "=" + DESIRED_STABLE_CHARGING_DELAY_MS);
- LogUtil.CLog.d("Original battery_saver_constants: " + mOriginalBatteryStatsConstants);
-
- mOriginalAirplaneModeEnabled = getAirplaneModeEnabled();
- // Turn off airplane mode in case another test left the device in that state.
- setAirplaneModeEnabled(false);
- LogUtil.CLog.d("Original airplane mode state: " + mOriginalAirplaneModeEnabled);
- }
-
- @Override
- public void tearDown(TestInformation testInformation, Throwable e)
- throws DeviceNotAvailableException {
- setAirplaneModeEnabled(mOriginalAirplaneModeEnabled);
- setAppStandbyEnabled(mOriginalAppStandbyEnabled);
- setBatteryStatsConstants(mOriginalBatteryStatsConstants);
- }
-
- private void setAirplaneModeEnabled(boolean enable) throws DeviceNotAvailableException {
- executeCmd("cmd connectivity airplane-mode " + (enable ? "enable" : "disable"));
- }
-
- private boolean getAirplaneModeEnabled() throws DeviceNotAvailableException {
- return "enabled".equals(executeCmd("cmd connectivity airplane-mode").trim());
- }
-
- private void setAppStandbyEnabled(String appStandbyEnabled) throws DeviceNotAvailableException {
- if ("null".equals(appStandbyEnabled)) {
- executeCmd("settings delete global app_standby_enabled");
- } else {
- executeCmd("settings put global app_standby_enabled " + appStandbyEnabled);
- }
- }
-
- private String getAppStandbyEnabled() throws DeviceNotAvailableException {
- return executeCmd("settings get global app_standby_enabled").trim();
- }
-
- private void setBatteryStatsConstants(String batteryStatsConstants)
- throws DeviceNotAvailableException {
- executeCmd("settings put global battery_stats_constants \"" + batteryStatsConstants + "\"");
- }
-
- private String getBatteryStatsConstants() throws DeviceNotAvailableException {
- return executeCmd("settings get global battery_stats_constants");
- }
-
- private String executeCmd(String cmd) throws DeviceNotAvailableException {
- final String output = mDevice.executeShellCommand(cmd).trim();
- LogUtil.CLog.d("Output for '%s': %s", cmd, output);
- return output;
- }
-}
diff --git a/tests/cts/multidevices/Android.bp b/tests/cts/multidevices/Android.bp
index 5f9b3ef..5f062f1 100644
--- a/tests/cts/multidevices/Android.bp
+++ b/tests/cts/multidevices/Android.bp
@@ -19,13 +19,15 @@
python_test_host {
name: "CtsConnectivityMultiDevicesTestCases",
- main: "connectivity_multi_devices_test.py",
+ main: "run_tests.py",
srcs: [
+ "apfv4_test.py",
"connectivity_multi_devices_test.py",
- "tether_utils.py",
+ "run_tests.py",
],
libs: [
"mobly",
+ "net-tests-utils-host-python-common",
],
test_suites: [
"cts",
diff --git a/tests/cts/multidevices/apfv4_test.py b/tests/cts/multidevices/apfv4_test.py
new file mode 100644
index 0000000..4633d37
--- /dev/null
+++ b/tests/cts/multidevices/apfv4_test.py
@@ -0,0 +1,35 @@
+# Copyright (C) 2024 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.
+
+from net_tests_utils.host.python import apf_test_base
+
+# Constants.
+COUNTER_DROPPED_ETHERTYPE_NOT_ALLOWED = "DROPPED_ETHERTYPE_NOT_ALLOWED"
+ETHER_BROADCAST_ADDR = "FFFFFFFFFFFF"
+ETH_P_ETHERCAT = "88A4"
+
+
+class ApfV4Test(apf_test_base.ApfTestBase):
+
+ def test_apf_drop_ethercat(self):
+ # Ethernet header (14 bytes).
+ packet = ETHER_BROADCAST_ADDR # Destination MAC (broadcast)
+ packet += self.server_mac_address.replace(":", "") # Source MAC
+ packet += ETH_P_ETHERCAT # EtherType (EtherCAT)
+
+ # EtherCAT header (2 bytes) + 44 bytes of zero padding.
+ packet += "00" * 46
+ self.send_packet_and_expect_counter_increased(
+ packet, COUNTER_DROPPED_ETHERTYPE_NOT_ALLOWED
+ )
diff --git a/tests/cts/multidevices/connectivity_multi_devices_test.py b/tests/cts/multidevices/connectivity_multi_devices_test.py
index 1d6fb51..eceb535 100644
--- a/tests/cts/multidevices/connectivity_multi_devices_test.py
+++ b/tests/cts/multidevices/connectivity_multi_devices_test.py
@@ -1,37 +1,24 @@
-# Lint as: python3
-"""Connectivity multi devices tests."""
-import sys
-from mobly import base_test
-from mobly import test_runner
-from mobly import utils
-from mobly.controllers import android_device
-import tether_utils
-from tether_utils import UpstreamType
+# Copyright (C) 2024 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.
-CONNECTIVITY_MULTI_DEVICES_SNIPPET_PACKAGE = "com.google.snippet.connectivity"
+from net_tests_utils.host.python import mdns_utils, multi_devices_test_base, tether_utils
+from net_tests_utils.host.python.tether_utils import UpstreamType
-class ConnectivityMultiDevicesTest(base_test.BaseTestClass):
-
- def setup_class(self):
- # Declare that two Android devices are needed.
- self.clientDevice, self.serverDevice = self.register_controller(
- android_device, min_number=2
- )
-
- def setup_device(device):
- device.load_snippet(
- "connectivity_multi_devices_snippet",
- CONNECTIVITY_MULTI_DEVICES_SNIPPET_PACKAGE,
- )
-
- # Set up devices in parallel to save time.
- utils.concurrent_exec(
- setup_device,
- ((self.clientDevice,), (self.serverDevice,)),
- max_workers=2,
- raise_on_exception=True,
- )
+class ConnectivityMultiDevicesTest(
+ multi_devices_test_base.MultiDevicesTestBase
+):
def test_hotspot_upstream_wifi(self):
tether_utils.assume_hotspot_test_preconditions(
@@ -61,10 +48,23 @@
self.serverDevice, UpstreamType.CELLULAR
)
-
-if __name__ == "__main__":
- # Take test args
- if "--" in sys.argv:
- index = sys.argv.index("--")
- sys.argv = sys.argv[:1] + sys.argv[index + 1 :]
- test_runner.main()
+ def test_mdns_via_hotspot(self):
+ tether_utils.assume_hotspot_test_preconditions(
+ self.serverDevice, self.clientDevice, UpstreamType.NONE
+ )
+ mdns_utils.assume_mdns_test_preconditions(
+ self.clientDevice, self.serverDevice
+ )
+ try:
+ # Connectivity of the client verified by asserting the validated capability.
+ tether_utils.setup_hotspot_and_client_for_upstream_type(
+ self.serverDevice, self.clientDevice, UpstreamType.NONE
+ )
+ mdns_utils.register_mdns_service_and_discover_resolve(
+ self.clientDevice, self.serverDevice
+ )
+ finally:
+ mdns_utils.cleanup_mdns_service(self.clientDevice, self.serverDevice)
+ tether_utils.cleanup_tethering_for_upstream_type(
+ self.serverDevice, UpstreamType.NONE
+ )
diff --git a/tests/cts/multidevices/run_tests.py b/tests/cts/multidevices/run_tests.py
new file mode 100644
index 0000000..1391d13
--- /dev/null
+++ b/tests/cts/multidevices/run_tests.py
@@ -0,0 +1,38 @@
+# Copyright (C) 2024 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.
+
+"""Main entrypoint for all of test cases."""
+
+import sys
+from apfv4_test import ApfV4Test
+from connectivity_multi_devices_test import ConnectivityMultiDevicesTest
+from mobly import suite_runner
+
+
+if __name__ == "__main__":
+ # For MoblyBinaryHostTest, this entry point will be called twice:
+ # 1. List tests.
+ # <mobly-par-file-name> -- --list_tests
+ # 2. Run tests.
+ # <mobly-par-file-name> -- --config=<yaml-path> \
+ # --device_serial=<device-serial> --log_path=<log-path>
+ # Strip the "--" since suite runner doesn't recognize it.
+ # While the parameters before "--" is for the infrastructure,
+ # ignore them if any. Also, do not alter parameters if there
+ # is no "--", in case the binary is invoked manually.
+ if "--" in sys.argv:
+ index = sys.argv.index("--")
+ sys.argv = sys.argv[:1] + sys.argv[index + 1 :]
+ # TODO: make the tests can be executed without manually list classes.
+ suite_runner.run_suite([ConnectivityMultiDevicesTest, ApfV4Test], sys.argv)
diff --git a/tests/cts/multidevices/snippet/Android.bp b/tests/cts/multidevices/snippet/Android.bp
index 5940cbb..c94087e 100644
--- a/tests/cts/multidevices/snippet/Android.bp
+++ b/tests/cts/multidevices/snippet/Android.bp
@@ -25,6 +25,8 @@
],
srcs: [
"ConnectivityMultiDevicesSnippet.kt",
+ "MdnsMultiDevicesSnippet.kt",
+ "Wifip2pMultiDevicesSnippet.kt",
],
manifest: "AndroidManifest.xml",
static_libs: [
diff --git a/tests/cts/multidevices/snippet/AndroidManifest.xml b/tests/cts/multidevices/snippet/AndroidManifest.xml
index 9ed8146..4637497 100644
--- a/tests/cts/multidevices/snippet/AndroidManifest.xml
+++ b/tests/cts/multidevices/snippet/AndroidManifest.xml
@@ -21,13 +21,17 @@
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
+ android:usesPermissionFlags="neverForLocation" />
<application>
<!-- Add any classes that implement the Snippet interface as meta-data, whose
value is a comma-separated string, each section being the package path
of a snippet class -->
<meta-data
android:name="mobly-snippets"
- android:value="com.google.snippet.connectivity.ConnectivityMultiDevicesSnippet" />
+ android:value="com.google.snippet.connectivity.ConnectivityMultiDevicesSnippet,
+ com.google.snippet.connectivity.MdnsMultiDevicesSnippet,
+ com.google.snippet.connectivity.Wifip2pMultiDevicesSnippet" />
</application>
<!-- Add an instrumentation tag so that the app can be launched through an
instrument command. The runner `com.google.android.mobly.snippet.SnippetRunner`
diff --git a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
index f4ad2c4..7368669 100644
--- a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
+++ b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
@@ -21,6 +21,7 @@
import android.content.pm.PackageManager.FEATURE_TELEPHONY
import android.content.pm.PackageManager.FEATURE_WIFI
import android.net.ConnectivityManager
+import android.net.Network
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkRequest
@@ -35,6 +36,7 @@
import android.net.wifi.WifiNetworkSpecifier
import android.net.wifi.WifiSsid
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.modules.utils.build.SdkLevel
import com.android.testutils.AutoReleaseNetworkCallbackRule
import com.android.testutils.ConnectUtil
import com.android.testutils.NetworkCallbackHelper
@@ -70,6 +72,9 @@
@Rpc(description = "Check whether the device supporters AP + STA concurrency.")
fun isStaApConcurrencySupported() = wifiManager.isStaApConcurrencySupported()
+ @Rpc(description = "Check whether the device SDK is as least T")
+ fun isAtLeastT() = SdkLevel.isAtLeastT()
+
@Rpc(description = "Request cellular connection and ensure it is the default network.")
fun requestCellularAndEnsureDefault() {
ctsNetUtils.disableWifi()
@@ -129,6 +134,12 @@
}
}
+ @Rpc(description = "Get interface name from NetworkHandle")
+ fun getInterfaceNameFromNetworkHandle(networkHandle: Long): String {
+ val network = Network.fromNetworkHandle(networkHandle)
+ return cm.getLinkProperties(network)!!.getInterfaceName()!!
+ }
+
@Rpc(description = "Check whether the device supports hotspot feature.")
fun hasHotspotFeature(): Boolean {
val tetheringCallback = ctsTetheringUtils.registerTetheringEventCallback()
@@ -140,7 +151,7 @@
}
@Rpc(description = "Start a hotspot with given SSID and passphrase.")
- fun startHotspot(ssid: String, passphrase: String) {
+ fun startHotspot(ssid: String, passphrase: String): String {
// Store old config.
runAsShell(OVERRIDE_WIFI_CONFIG) {
oldSoftApConfig = wifiManager.getSoftApConfiguration()
@@ -157,7 +168,7 @@
val tetheringCallback = ctsTetheringUtils.registerTetheringEventCallback()
try {
tetheringCallback.expectNoTetheringActive()
- ctsTetheringUtils.startWifiTethering(tetheringCallback)
+ return ctsTetheringUtils.startWifiTethering(tetheringCallback).getInterface()
} finally {
ctsTetheringUtils.unregisterTetheringEventCallback(tetheringCallback)
}
diff --git a/tests/cts/multidevices/snippet/MdnsMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/MdnsMultiDevicesSnippet.kt
new file mode 100644
index 0000000..1b288df
--- /dev/null
+++ b/tests/cts/multidevices/snippet/MdnsMultiDevicesSnippet.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 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.google.snippet.connectivity
+
+import android.net.nsd.NsdManager
+import android.net.nsd.NsdServiceInfo
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.testutils.NsdDiscoveryRecord
+import com.android.testutils.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped
+import com.android.testutils.NsdRegistrationRecord
+import com.android.testutils.NsdRegistrationRecord.RegistrationEvent.ServiceRegistered
+import com.android.testutils.NsdRegistrationRecord.RegistrationEvent.ServiceUnregistered
+import com.android.testutils.NsdResolveRecord
+import com.android.testutils.NsdResolveRecord.ResolveEvent.ServiceResolved
+import com.google.android.mobly.snippet.Snippet
+import com.google.android.mobly.snippet.rpc.Rpc
+import kotlin.test.assertEquals
+import org.junit.Assert.assertArrayEquals
+
+private const val SERVICE_NAME = "MultiDevicesTest"
+private const val SERVICE_TYPE = "_multi_devices._tcp"
+private const val SERVICE_ATTRIBUTES_KEY = "key"
+private const val SERVICE_ATTRIBUTES_VALUE = "value"
+private const val SERVICE_PORT = 12345
+private const val REGISTRATION_TIMEOUT_MS = 10_000L
+
+class MdnsMultiDevicesSnippet : Snippet {
+ private val context = InstrumentationRegistry.getInstrumentation().getTargetContext()
+ private val nsdManager = context.getSystemService(NsdManager::class.java)!!
+ private val registrationRecord = NsdRegistrationRecord()
+ private val discoveryRecord = NsdDiscoveryRecord()
+ private val resolveRecord = NsdResolveRecord()
+
+ @Rpc(description = "Register a mDns service")
+ fun registerMDnsService() {
+ val info = NsdServiceInfo()
+ info.setServiceName(SERVICE_NAME)
+ info.setServiceType(SERVICE_TYPE)
+ info.setPort(SERVICE_PORT)
+ info.setAttribute(SERVICE_ATTRIBUTES_KEY, SERVICE_ATTRIBUTES_VALUE)
+ nsdManager.registerService(info, NsdManager.PROTOCOL_DNS_SD, registrationRecord)
+ registrationRecord.expectCallback<ServiceRegistered>(REGISTRATION_TIMEOUT_MS)
+ }
+
+ @Rpc(description = "Unregister a mDns service")
+ fun unregisterMDnsService() {
+ nsdManager.unregisterService(registrationRecord)
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ }
+
+ @Rpc(description = "Ensure the discovery and resolution of the mDNS service")
+ // Suppress the warning, as the NsdManager#resolveService() method is deprecated.
+ @Suppress("DEPRECATION")
+ fun ensureMDnsServiceDiscoveryAndResolution() {
+ // Discover a mDns service that matches the test service
+ nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryRecord)
+ val info = discoveryRecord.waitForServiceDiscovered(SERVICE_NAME, SERVICE_TYPE)
+ // Resolve the retrieved mDns service.
+ nsdManager.resolveService(info, resolveRecord)
+ val serviceResolved = resolveRecord.expectCallbackEventually<ServiceResolved>()
+ serviceResolved.serviceInfo.let {
+ assertEquals(SERVICE_NAME, it.serviceName)
+ assertEquals(".$SERVICE_TYPE", it.serviceType)
+ assertEquals(SERVICE_PORT, it.port)
+ assertEquals(1, it.attributes.size)
+ assertArrayEquals(
+ SERVICE_ATTRIBUTES_VALUE.encodeToByteArray(),
+ it.attributes[SERVICE_ATTRIBUTES_KEY]
+ )
+ }
+ }
+
+ @Rpc(description = "Stop discovery")
+ fun stopMDnsServiceDiscovery() {
+ nsdManager.stopServiceDiscovery(discoveryRecord)
+ discoveryRecord.expectCallbackEventually<DiscoveryStopped>()
+ }
+}
diff --git a/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt
new file mode 100644
index 0000000..e0929bb
--- /dev/null
+++ b/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 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.google.snippet.connectivity
+
+import android.net.wifi.WifiManager
+import android.net.wifi.p2p.WifiP2pManager
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.android.mobly.snippet.Snippet
+import com.google.android.mobly.snippet.rpc.Rpc
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import kotlin.test.fail
+
+private const val TIMEOUT_MS = 60000L
+
+class Wifip2pMultiDevicesSnippet : Snippet {
+ private val context by lazy { InstrumentationRegistry.getInstrumentation().getTargetContext() }
+ private val wifiManager by lazy {
+ context.getSystemService(WifiManager::class.java)
+ ?: fail("Could not get WifiManager service")
+ }
+ private val wifip2pManager by lazy {
+ context.getSystemService(WifiP2pManager::class.java)
+ ?: fail("Could not get WifiP2pManager service")
+ }
+ private lateinit var wifip2pChannel: WifiP2pManager.Channel
+
+ @Rpc(description = "Check whether the device supports Wi-Fi P2P.")
+ fun isP2pSupported() = wifiManager.isP2pSupported()
+
+ @Rpc(description = "Start Wi-Fi P2P")
+ fun startWifiP2p() {
+ // Initialize Wi-Fi P2P
+ wifip2pChannel = wifip2pManager.initialize(context, context.mainLooper, null)
+
+ // Ensure the Wi-Fi P2P channel is available
+ val p2pStateEnabledFuture = CompletableFuture<Boolean>()
+ wifip2pManager.requestP2pState(wifip2pChannel) { state ->
+ if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
+ p2pStateEnabledFuture.complete(true)
+ }
+ }
+ p2pStateEnabledFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }
+
+ @Rpc(description = "Stop Wi-Fi P2P")
+ fun stopWifiP2p() {
+ if (this::wifip2pChannel.isInitialized) {
+ wifip2pManager.cancelConnect(wifip2pChannel, null)
+ wifip2pManager.removeGroup(wifip2pChannel, null)
+ }
+ }
+}
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index ae85701..1cd8327 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -111,25 +111,6 @@
min_sdk_version: "30",
}
-// Networking CTS tests that target the latest released SDK. These tests can be installed on release
-// devices at any point in the Android release cycle and are useful for qualifying mainline modules
-// on release devices.
-android_test {
- name: "CtsNetTestCasesLatestSdk",
- defaults: [
- "ConnectivityTestsLatestSdkDefaults",
- "CtsNetTestCasesDefaults",
- "CtsNetTestCasesApiStableDefaults",
- ],
- test_suites: [
- "general-tests",
- "mts-dnsresolver",
- "mts-networking",
- "mts-tethering",
- "mts-wifi",
- ],
-}
-
java_defaults {
name: "CtsNetTestCasesMaxTargetSdkDefaults",
defaults: [
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index 077c3ef..24431a6 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -25,6 +25,7 @@
<option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.tethering.apex" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+ <option name="has-server-side-config" value="false" />
<option name="target" value="device" />
<option name="config-filename" value="{MODULE}" />
<option name="version" value="1.0" />
@@ -48,8 +49,8 @@
those tests with an annotation matching the name of the APK.
This allows us to maintain one AndroidTestTemplate.xml for all CtsNetTestCases*.apk,
- and have CtsNetTestCases and CtsNetTestCasesLatestSdk run all tests, but have
- CtsNetTestCasesMaxTargetSdk31 run only tests that require target SDK 31.
+ and have CtsNetTestCases run all tests, but have CtsNetTestCasesMaxTargetSdk31 run only
+ tests that require target SDK 31.
This relies on the fact that if the class specified in include-annotation exists, then
the runner will only run the tests annotated with that annotation, but if it does not,
diff --git a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
index 764f117..9ac2c67 100644
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -27,9 +27,20 @@
import android.net.NetworkRequest
import android.net.apf.ApfCapabilities
import android.net.apf.ApfConstants.ETH_ETHERTYPE_OFFSET
+import android.net.apf.ApfConstants.ETH_HEADER_LEN
+import android.net.apf.ApfConstants.ICMP6_CHECKSUM_OFFSET
import android.net.apf.ApfConstants.ICMP6_TYPE_OFFSET
+import android.net.apf.ApfConstants.IPV6_DEST_ADDR_OFFSET
+import android.net.apf.ApfConstants.IPV6_HEADER_LEN
import android.net.apf.ApfConstants.IPV6_NEXT_HEADER_OFFSET
+import android.net.apf.ApfConstants.IPV6_SRC_ADDR_OFFSET
+import android.net.apf.ApfCounterTracker
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MULTICAST_PING
+import android.net.apf.ApfCounterTracker.Counter.FILTER_AGE_16384THS
+import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_ICMP
import android.net.apf.ApfV4Generator
+import android.net.apf.ApfV4GeneratorBase
+import android.net.apf.ApfV6Generator
import android.net.apf.BaseApfGenerator
import android.net.apf.BaseApfGenerator.MemorySlot
import android.net.apf.BaseApfGenerator.Register.R0
@@ -57,6 +68,12 @@
import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
import com.android.compatibility.common.util.VsrTest
import com.android.internal.util.HexDump
+import com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN
+import com.android.net.module.util.NetworkStackConstants.ETHER_DST_ADDR_OFFSET
+import com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN
+import com.android.net.module.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET
+import com.android.net.module.util.NetworkStackConstants.ICMPV6_HEADER_MIN_LEN
+import com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN
import com.android.net.module.util.PacketReader
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
@@ -73,7 +90,6 @@
import com.google.common.truth.Truth.assertWithMessage
import com.google.common.truth.TruthJUnit.assume
import java.io.FileDescriptor
-import java.lang.Thread
import java.net.InetSocketAddress
import java.nio.ByteBuffer
import java.util.concurrent.CompletableFuture
@@ -213,8 +229,8 @@
Os.sendto(sockFd!!, packet, 0, packet.size, 0, PING_DESTINATION)
}
- fun expectPingReply(): ByteArray {
- return futureReply!!.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ fun expectPingReply(timeoutMs: Long = TIMEOUT_MS): ByteArray {
+ return futureReply!!.get(timeoutMs, TimeUnit.MILLISECONDS)
}
fun expectPingDropped() {
@@ -394,7 +410,13 @@
}
}
- fun ApfV4Generator.addPassIfNotIcmpv6EchoReply() {
+ private fun installAndVerifyProgram(program: ByteArray) {
+ installProgram(program)
+ val readResult = readProgram().take(program.size).toByteArray()
+ assertThat(readResult).isEqualTo(program)
+ }
+
+ fun ApfV4GeneratorBase<*>.addPassIfNotIcmpv6EchoReply() {
// If not IPv6 -> PASS
addLoad16(R0, ETH_ETHERTYPE_OFFSET)
addJumpIfR0NotEquals(ETH_P_IPV6.toLong(), BaseApfGenerator.PASS_LABEL)
@@ -434,7 +456,11 @@
assertThat(packetReader.expectPingReply()).isEqualTo(data)
// Generate an APF program that drops the next ping
- val gen = ApfV4Generator(4)
+ val gen = ApfV4Generator(
+ caps.apfVersionSupported,
+ caps.maximumApfProgramSize,
+ caps.maximumApfProgramSize
+ )
// If not ICMPv6 Echo Reply -> PASS
gen.addPassIfNotIcmpv6EchoReply()
@@ -447,8 +473,7 @@
gen.addJump(BaseApfGenerator.DROP_LABEL)
val program = gen.generate()
- installProgram(program)
- readProgram() // wait for install completion
+ installAndVerifyProgram(program)
packetReader.sendPing(data, payloadSize)
packetReader.expectPingDropped()
@@ -468,7 +493,11 @@
// Test v4 memory slots on both v4 and v6 interpreters.
assumeApfVersionSupportAtLeast(4)
clearApfMemory()
- val gen = ApfV4Generator(4)
+ val gen = ApfV4Generator(
+ caps.apfVersionSupported,
+ caps.maximumApfProgramSize,
+ caps.maximumApfProgramSize
+ )
// If not ICMPv6 Echo Reply -> PASS
gen.addPassIfNotIcmpv6EchoReply()
@@ -489,8 +518,7 @@
val program = gen.generate()
assertThat(program.size).isLessThan(counterRegion)
- installProgram(program)
- readProgram() // wait for install completion
+ installAndVerifyProgram(program)
// Trigger the program by sending a ping and waiting on the reply.
val payloadSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
@@ -523,7 +551,11 @@
assume().that(getVsrApiLevel()).isAtLeast(34)
assumeApfVersionSupportAtLeast(4)
clearApfMemory()
- val gen = ApfV4Generator(4)
+ val gen = ApfV4Generator(
+ caps.apfVersionSupported,
+ caps.maximumApfProgramSize,
+ caps.maximumApfProgramSize
+ )
// If not ICMPv6 Echo Reply -> PASS
gen.addPassIfNotIcmpv6EchoReply()
@@ -534,8 +566,7 @@
gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS)
gen.addStoreData(R0, 0)
- installProgram(gen.generate())
- readProgram() // wait for install completion
+ installAndVerifyProgram(gen.generate())
val payloadSize = 56
val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
@@ -556,4 +587,147 @@
val timeDiff = filterAgeSeconds - filterAgeSecondsOrig
assertThat(timeDiff).isAnyOf(5, 6)
}
+
+ @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
+ @Test
+ fun testFilterAge16384thsIncreasesBetweenPackets() {
+ assumeApfVersionSupportAtLeast(6000)
+ clearApfMemory()
+ val gen = ApfV6Generator(
+ caps.apfVersionSupported,
+ caps.maximumApfProgramSize,
+ caps.maximumApfProgramSize
+ )
+
+ // If not ICMPv6 Echo Reply -> PASS
+ gen.addPassIfNotIcmpv6EchoReply()
+
+ // Store all prefilled memory slots in counter region [500, 520)
+ gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_16384THS)
+ gen.addStoreCounter(FILTER_AGE_16384THS, R0)
+
+ installAndVerifyProgram(gen.generate())
+
+ val payloadSize = 56
+ val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
+ packetReader.sendPing(data, payloadSize)
+ packetReader.expectPingReply()
+
+ var apfRam = readProgram()
+ val filterAge16384thSecondsOrig =
+ ApfCounterTracker.getCounterValue(apfRam, FILTER_AGE_16384THS)
+
+ Thread.sleep(5000)
+
+ packetReader.sendPing(data, payloadSize)
+ packetReader.expectPingReply()
+
+ apfRam = readProgram()
+ val filterAge16384thSeconds = ApfCounterTracker.getCounterValue(apfRam, FILTER_AGE_16384THS)
+ val timeDiff = (filterAge16384thSeconds - filterAge16384thSecondsOrig)
+ // Expect the HAL plus ping latency to be less than 800ms.
+ val timeDiffLowerBound = (4.99 * 16384).toInt()
+ val timeDiffUpperBound = (5.81 * 16384).toInt()
+ // Assert that filter age has increased, but not too much.
+ assertThat(timeDiff).isGreaterThan(timeDiffLowerBound)
+ assertThat(timeDiff).isLessThan(timeDiffUpperBound)
+ }
+
+ @VsrTest(
+ requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005", "VSR-5.3.12-012", "VSR-5.3.12-013",
+ "VSR-5.3.12-014", "VSR-5.3.12-015", "VSR-5.3.12-016", "VSR-5.3.12-017"]
+ )
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testReplyPing() {
+ assumeApfVersionSupportAtLeast(6000)
+ installProgram(ByteArray(caps.maximumApfProgramSize) { 0 }) // Clear previous program
+ readProgram() // Ensure installation is complete
+
+ val payloadSize = 56
+ val payload = ByteArray(payloadSize).also { Random.nextBytes(it) }
+ val firstByte = payload.take(1).toByteArray()
+
+ val pingRequestIpv6PayloadLen = PING_HEADER_LENGTH + 1
+ val pingRequestPktLen = ETH_HEADER_LEN + IPV6_HEADER_LEN + pingRequestIpv6PayloadLen
+
+ val gen = ApfV6Generator(
+ caps.apfVersionSupported,
+ caps.maximumApfProgramSize,
+ caps.maximumApfProgramSize
+ )
+ val skipPacketLabel = gen.uniqueLabel
+
+ // Summary of the program:
+ // if the packet is not ICMPv6 echo reply
+ // pass
+ // else if the echo reply payload size is 1
+ // increase PASSED_IPV6_ICMP counter
+ // pass
+ // else
+ // transmit a ICMPv6 echo request packet with the first byte of the payload in the reply
+ // increase DROPPED_IPV6_MULTICAST_PING counter
+ // drop
+ val program = gen
+ .addLoad16(R0, ETH_ETHERTYPE_OFFSET)
+ .addJumpIfR0NotEquals(ETH_P_IPV6.toLong(), skipPacketLabel)
+ .addLoad8(R0, IPV6_NEXT_HEADER_OFFSET)
+ .addJumpIfR0NotEquals(IPPROTO_ICMPV6.toLong(), skipPacketLabel)
+ .addLoad8(R0, ICMP6_TYPE_OFFSET)
+ .addJumpIfR0NotEquals(0x81, skipPacketLabel) // Echo reply type
+ .addLoadFromMemory(R0, MemorySlot.PACKET_SIZE)
+ .addCountAndPassIfR0Equals(
+ (ETHER_HEADER_LEN + IPV6_HEADER_LEN + PING_HEADER_LENGTH + firstByte.size)
+ .toLong(),
+ PASSED_IPV6_ICMP
+ )
+ // Ping Packet Generation
+ .addAllocate(pingRequestPktLen)
+ // Eth header
+ .addPacketCopy(ETHER_SRC_ADDR_OFFSET, ETHER_ADDR_LEN) // dst MAC address
+ .addPacketCopy(ETHER_DST_ADDR_OFFSET, ETHER_ADDR_LEN) // src MAC address
+ .addWriteU16(ETH_P_IPV6) // IPv6 type
+ // IPv6 Header
+ .addWrite32(0x60000000) // IPv6 Header: version, traffic class, flowlabel
+ // payload length (2 bytes) | next header: ICMPv6 (1 byte) | hop limit (1 byte)
+ .addWrite32(pingRequestIpv6PayloadLen shl 16 or (IPPROTO_ICMPV6 shl 8 or 64))
+ .addPacketCopy(IPV6_DEST_ADDR_OFFSET, IPV6_ADDR_LEN) // src ip
+ .addPacketCopy(IPV6_SRC_ADDR_OFFSET, IPV6_ADDR_LEN) // dst ip
+ // ICMPv6
+ .addWriteU8(0x80) // type: echo request
+ .addWriteU8(0) // code
+ .addWriteU16(pingRequestIpv6PayloadLen) // checksum
+ // identifier
+ .addPacketCopy(ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMPV6_HEADER_MIN_LEN, 2)
+ .addWriteU16(0) // sequence number
+ .addDataCopy(firstByte) // data
+ .addTransmitL4(
+ ETHER_HEADER_LEN, // ip_ofs
+ ICMP6_CHECKSUM_OFFSET, // csum_ofs
+ IPV6_SRC_ADDR_OFFSET, // csum_start
+ IPPROTO_ICMPV6, // partial_sum
+ false // udp
+ )
+ // Warning: the program abuse DROPPED_IPV6_MULTICAST_PING for debugging purpose
+ .addCountAndDrop(DROPPED_IPV6_MULTICAST_PING)
+ .defineLabel(skipPacketLabel)
+ .addPass()
+ .generate()
+
+ installAndVerifyProgram(program)
+
+ packetReader.sendPing(payload, payloadSize)
+
+ val replyPayload = try {
+ packetReader.expectPingReply(TIMEOUT_MS * 2)
+ } catch (e: TimeoutException) {
+ byteArrayOf() // Empty payload if timeout occurs
+ }
+
+ val apfCounterTracker = ApfCounterTracker()
+ apfCounterTracker.updateCountersFromData(readProgram())
+ Log.i(TAG, "counter map: ${apfCounterTracker.counters}")
+
+ assertThat(replyPayload).isEqualTo(firstByte)
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 60869b6..88c2d5a 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -195,7 +195,6 @@
import androidx.test.filters.RequiresDevice;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.DynamicConfigDeviceSide;
import com.android.internal.util.ArrayUtils;
@@ -207,15 +206,16 @@
import com.android.networkstack.apishim.common.ConnectivityManagerShim;
import com.android.testutils.AutoReleaseNetworkCallbackRule;
import com.android.testutils.CompatUtil;
+import com.android.testutils.ConnectUtil;
import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.DeviceConfigRule;
import com.android.testutils.DeviceInfoUtils;
import com.android.testutils.DumpTestUtils;
import com.android.testutils.RecorderCallback.CallbackEntry;
-import com.android.testutils.SkipMainlinePresubmit;
import com.android.testutils.SkipPresubmit;
import com.android.testutils.TestHttpServer;
import com.android.testutils.TestNetworkTracker;
@@ -275,7 +275,8 @@
import fi.iki.elonen.NanoHTTPD.Response.IStatus;
import fi.iki.elonen.NanoHTTPD.Response.Status;
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRunner.RestoreDefaultNetwork
public class ConnectivityManagerTest {
@Rule(order = 1)
public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
@@ -1054,7 +1055,6 @@
@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
- @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
public void testIsPrivateDnsBroken() throws InterruptedException {
final String invalidPrivateDnsServer = "invalidhostname.example.com";
final String goodPrivateDnsServer = "dns.google";
@@ -2943,20 +2943,10 @@
// This may also apply to wifi in principle, but in practice methods that mock validation
// URL all disconnect wifi forcefully anyway, so don't wait for wifi to validate.
if (mPackageManager.hasSystemFeature(FEATURE_TELEPHONY)) {
- ensureValidatedNetwork(makeCellNetworkRequest());
+ new ConnectUtil(mContext).ensureCellularValidated();
}
}
- private void ensureValidatedNetwork(NetworkRequest request) {
- final TestableNetworkCallback cb = new TestableNetworkCallback();
- mCm.registerNetworkCallback(request, cb);
- cb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED,
- NETWORK_CALLBACK_TIMEOUT_MS,
- entry -> ((CallbackEntry.CapabilitiesChanged) entry).getCaps()
- .hasCapability(NET_CAPABILITY_VALIDATED));
- mCm.unregisterNetworkCallback(cb);
- }
-
@AppModeFull(reason = "WRITE_DEVICE_CONFIG permission can't be granted to instant apps")
@Test
public void testAcceptPartialConnectivity_validatedNetwork() throws Exception {
diff --git a/tests/cts/net/src/android/net/cts/DnsResolverTest.java b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
index 752891f..fa44ae9 100644
--- a/tests/cts/net/src/android/net/cts/DnsResolverTest.java
+++ b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
@@ -852,13 +852,14 @@
}
public void doTestContinuousQueries(Executor executor) throws InterruptedException {
- final String msg = "Test continuous " + QUERY_TIMES + " queries " + TEST_DOMAIN;
for (Network network : getTestableNetworks()) {
for (int i = 0; i < QUERY_TIMES ; ++i) {
- final VerifyCancelInetAddressCallback callback =
- new VerifyCancelInetAddressCallback(msg, null);
// query v6/v4 in turn
boolean queryV6 = (i % 2 == 0);
+ final String msg = "Test continuous " + QUERY_TIMES + " queries " + TEST_DOMAIN
+ + " on " + network + ", queryV6=" + queryV6;
+ final VerifyCancelInetAddressCallback callback =
+ new VerifyCancelInetAddressCallback(msg, null);
mDns.query(network, TEST_DOMAIN, queryV6 ? TYPE_AAAA : TYPE_A,
FLAG_NO_CACHE_LOOKUP, executor, null, callback);
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index b5f43d3..e94d94f 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -451,9 +451,10 @@
long uidTxDelta = 0;
long uidRxDelta = 0;
for (int i = 0; i < 100; i++) {
- // Clear TrafficStats cache is needed to avoid rate-limit caching for
- // TrafficStats API results on V+ devices.
- if (SdkLevel.isAtLeastV()) {
+ // Since the target SDK of this test should always equal or be larger than V,
+ // TrafficStats caching is always enabled. Clearing the cache is needed to
+ // avoid rate-limiting on devices with a mainlined (T+) NetworkStatsService.
+ if (SdkLevel.isAtLeastT()) {
runAsShell(NETWORK_SETTINGS, () -> TrafficStats.clearRateLimitCaches());
}
uidTxDelta = TrafficStats.getUidTxPackets(Os.getuid()) - uidTxPackets;
@@ -530,9 +531,10 @@
}
private static void initStatsChecker() throws Exception {
- // Clear TrafficStats cache is needed to avoid rate-limit caching for
- // TrafficStats API results on V+ devices.
- if (SdkLevel.isAtLeastV()) {
+ // Since the target SDK of this test should always equal or be larger than V,
+ // TrafficStats caching is always enabled. Clearing the cache is needed to
+ // avoid rate-limiting on devices with a mainlined (T+) NetworkStatsService.
+ if (SdkLevel.isAtLeastT()) {
runAsShell(NETWORK_SETTINGS, () -> TrafficStats.clearRateLimitCaches());
}
uidTxBytes = TrafficStats.getUidTxBytes(Os.getuid());
diff --git a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
index 06a827b..2c7d5c6 100644
--- a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
+++ b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
@@ -40,10 +40,10 @@
import android.system.OsConstants;
import android.util.ArraySet;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.testutils.AutoReleaseNetworkCallbackRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.DeviceConfigRule;
import org.junit.Before;
@@ -53,7 +53,8 @@
import java.util.Set;
-@RunWith(AndroidJUnit4.class)
+@DevSdkIgnoreRunner.RestoreDefaultNetwork
+@RunWith(DevSdkIgnoreRunner.class)
public class MultinetworkApiTest {
@Rule(order = 1)
public final DeviceConfigRule mDeviceConfigRule = new DeviceConfigRule();
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index beb9274..60081d4 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -194,15 +194,16 @@
// TODO : enable this in a Mainline update or in V.
private const val SHOULD_CREATE_NETWORKS_IMMEDIATELY = false
-@RunWith(DevSdkIgnoreRunner::class)
-// NetworkAgent is not updatable in R-, so this test does not need to be compatible with older
-// versions. NetworkAgent was also based on AsyncChannel before S so cannot be tested the same way.
-@IgnoreUpTo(Build.VERSION_CODES.R)
+@AppModeFull(reason = "Instant apps can't use NetworkAgent because it needs NETWORK_FACTORY'.")
// NetworkAgent is updated as part of the connectivity module, and running NetworkAgent tests in MTS
// for modules other than Connectivity does not provide much value. Only run them in connectivity
// module MTS, so the tests only need to cover the case of an updated NetworkAgent.
@ConnectivityModuleTest
-@AppModeFull(reason = "Instant apps can't use NetworkAgent because it needs NETWORK_FACTORY'.")
+@DevSdkIgnoreRunner.RestoreDefaultNetwork
+// NetworkAgent is not updatable in R-, so this test does not need to be compatible with older
+// versions. NetworkAgent was also based on AsyncChannel before S so cannot be tested the same way.
+@IgnoreUpTo(Build.VERSION_CODES.R)
+@RunWith(DevSdkIgnoreRunner::class)
class NetworkAgentTest {
private val LOCAL_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1")
private val REMOTE_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.2")
diff --git a/tests/cts/net/src/android/net/cts/NetworkValidationTest.kt b/tests/cts/net/src/android/net/cts/NetworkValidationTest.kt
index 621af23..f9acb66 100644
--- a/tests/cts/net/src/android/net/cts/NetworkValidationTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkValidationTest.kt
@@ -19,7 +19,6 @@
import android.Manifest.permission.MANAGE_TEST_NETWORKS
import android.Manifest.permission.NETWORK_SETTINGS
import android.content.Context
-import android.content.pm.PackageManager
import android.net.ConnectivityManager
import android.net.EthernetManager
import android.net.InetAddresses
@@ -36,7 +35,6 @@
import android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_DISCOVER
import android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_REQUEST
import android.net.dhcp.DhcpRequestPacket
-import android.os.Build
import android.os.HandlerThread
import android.platform.test.annotations.AppModeFull
import androidx.test.platform.app.InstrumentationRegistry
@@ -44,7 +42,7 @@
import com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress
import com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address
import com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY
-import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.AutoCloseTestInterfaceRule
import com.android.testutils.DhcpClientPacketFilter
import com.android.testutils.DhcpOptionFilter
import com.android.testutils.RecorderCallback.CallbackEntry
@@ -79,10 +77,6 @@
@AppModeFull(reason = "Instant apps cannot create test networks")
@RunWith(AndroidJUnit4::class)
class NetworkValidationTest {
- @JvmField
- @Rule
- val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.Q)
-
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
private val tnm by lazy { context.assertHasService(TestNetworkManager::class.java) }
private val eth by lazy { context.assertHasService(EthernetManager::class.java) }
@@ -104,23 +98,24 @@
private var testSkipped = false
+ @get:Rule
+ val testInterfaceRule = AutoCloseTestInterfaceRule(context)
+
@Before
fun setUp() {
// This test requires using a tap interface as an ethernet interface.
- val pm = context.getPackageManager()
- testSkipped = !pm.hasSystemFeature(PackageManager.FEATURE_ETHERNET) &&
- context.getSystemService(EthernetManager::class.java) == null
+ testSkipped = context.getSystemService(EthernetManager::class.java) == null
assumeFalse(testSkipped)
// Register a request so the network does not get torn down
cm.requestNetwork(ethRequest, ethRequestCb)
runAsShell(NETWORK_SETTINGS, MANAGE_TEST_NETWORKS) {
eth.setIncludeTestInterfaces(true)
- // Keeping a reference to the test interface also makes sure the ParcelFileDescriptor
- // does not go out of scope, which would cause it to close the underlying FileDescriptor
- // in its finalizer.
- iface = tnm.createTapInterface()
}
+ // Keeping a reference to the test interface also makes sure the ParcelFileDescriptor
+ // does not go out of scope, which would cause it to close the underlying FileDescriptor
+ // in its finalizer.
+ iface = testInterfaceRule.createTapInterface()
handlerThread.start()
reader = TapPacketReader(
@@ -147,8 +142,6 @@
handlerThread.threadHandler.post { reader.stop() }
handlerThread.quitSafely()
handlerThread.join()
-
- iface.fileDescriptor.close()
}
@Test
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
index 1b1f367..24af42b 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
@@ -17,18 +17,22 @@
import android.net.EthernetTetheringTestBase
import android.net.LinkAddress
-import android.net.TestNetworkInterface
import android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL
import android.net.TetheringManager.TETHERING_ETHERNET
import android.net.TetheringManager.TetheringRequest
+import android.net.cts.util.EthernetTestInterface
import android.net.nsd.NsdManager
import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
import android.platform.test.annotations.AppModeFull
import androidx.test.filters.SmallTest
+import com.android.testutils.AutoCloseTestInterfaceRule
import com.android.testutils.ConnectivityModuleTest
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.TapPacketReader
+import com.android.testutils.NsdDiscoveryRecord
+import com.android.testutils.pollForQuery
import com.android.testutils.tryTest
import java.util.Random
import kotlin.test.assertEquals
@@ -36,9 +40,12 @@
import org.junit.After
import org.junit.Assume.assumeFalse
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+private const val TAG = "NsdManagerDownstreamTetheringTest"
+
@RunWith(DevSdkIgnoreRunner::class)
@SmallTest
@ConnectivityModuleTest
@@ -48,32 +55,41 @@
private val nsdManager by lazy { context.getSystemService(NsdManager::class.java)!! }
private val serviceType = "_nmt%09d._tcp".format(Random().nextInt(1_000_000_000))
+ private val handlerThread = HandlerThread("$TAG thread").apply { start() }
+ private val handler = Handler(handlerThread.looper)
+ private lateinit var downstreamIface: EthernetTestInterface
+ private var tetheringEventCallback: MyTetheringEventCallback? = null
+
+ @get:Rule
+ val testInterfaceRule = AutoCloseTestInterfaceRule(context)
+
@Before
override fun setUp() {
super.setUp()
- setIncludeTestInterfaces(true)
+ val iface = testInterfaceRule.createTapInterface()
+ downstreamIface = EthernetTestInterface(context, handler, iface)
}
@After
override fun tearDown() {
+ if (::downstreamIface.isInitialized) {
+ downstreamIface.destroy()
+ }
+ handlerThread.quitSafely()
+ handlerThread.join()
+ maybeUnregisterTetheringEventCallback(tetheringEventCallback)
super.tearDown()
- setIncludeTestInterfaces(false)
}
@Test
fun testMdnsDiscoveryCanSendPacketOnLocalOnlyDownstreamTetheringInterface() {
assumeFalse(isInterfaceForTetheringAvailable())
- var downstreamIface: TestNetworkInterface? = null
- var tetheringEventCallback: MyTetheringEventCallback? = null
- var downstreamReader: TapPacketReader? = null
-
val discoveryRecord = NsdDiscoveryRecord()
tryTest {
- downstreamIface = createTestInterface()
- val iface = tetheredInterface
- assertEquals(iface, downstreamIface?.interfaceName)
+ val iface = mTetheredInterfaceRequester.getInterface()
+ assertEquals(downstreamIface.name, iface)
val request = TetheringRequest.Builder(TETHERING_ETHERNET)
.setConnectivityScope(CONNECTIVITY_SCOPE_LOCAL).build()
tetheringEventCallback = enableEthernetTethering(
@@ -82,41 +98,27 @@
).apply {
awaitInterfaceLocalOnly()
}
- // This shouldn't be flaky because the TAP interface will buffer all packets even
- // before the reader is started.
- downstreamReader = makePacketReader(downstreamIface)
+ val downstreamReader = downstreamIface.packetReader
waitForRouterAdvertisement(downstreamReader, iface, WAIT_RA_TIMEOUT_MS)
nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryRecord)
discoveryRecord.expectCallback<NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted>()
- assertNotNull(downstreamReader?.pollForQuery("$serviceType.local", 12 /* type PTR */))
+ assertNotNull(downstreamReader.pollForQuery("$serviceType.local", 12 /* type PTR */))
} cleanupStep {
nsdManager.stopServiceDiscovery(discoveryRecord)
discoveryRecord.expectCallback<NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped>()
- } cleanupStep {
- maybeStopTapPacketReader(downstreamReader)
- } cleanupStep {
- maybeCloseTestInterface(downstreamIface)
- } cleanup {
- maybeUnregisterTetheringEventCallback(tetheringEventCallback)
}
}
@Test
fun testMdnsDiscoveryWorkOnTetheringInterface() {
assumeFalse(isInterfaceForTetheringAvailable())
- setIncludeTestInterfaces(true)
-
- var downstreamIface: TestNetworkInterface? = null
- var tetheringEventCallback: MyTetheringEventCallback? = null
- var downstreamReader: TapPacketReader? = null
val discoveryRecord = NsdDiscoveryRecord()
tryTest {
- downstreamIface = createTestInterface()
- val iface = tetheredInterface
- assertEquals(iface, downstreamIface?.interfaceName)
+ val iface = mTetheredInterfaceRequester.getInterface()
+ assertEquals(downstreamIface.name, iface)
val localAddr = LinkAddress("192.0.2.3/28")
val clientAddr = LinkAddress("192.0.2.2/28")
@@ -130,23 +132,14 @@
awaitInterfaceTethered()
}
- val fd = downstreamIface?.fileDescriptor?.fileDescriptor
- assertNotNull(fd)
- downstreamReader = makePacketReader(fd, getMTU(downstreamIface))
-
nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryRecord)
discoveryRecord.expectCallback<NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted>()
- assertNotNull(downstreamReader?.pollForQuery("$serviceType.local", 12 /* type PTR */))
+ val downstreamReader = downstreamIface.packetReader
+ assertNotNull(downstreamReader.pollForQuery("$serviceType.local", 12 /* type PTR */))
// TODO: Add another test to check packet reply can trigger serviceFound.
} cleanupStep {
nsdManager.stopServiceDiscovery(discoveryRecord)
discoveryRecord.expectCallback<NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped>()
- } cleanupStep {
- maybeStopTapPacketReader(downstreamReader)
- } cleanupStep {
- maybeCloseTestInterface(downstreamIface)
- } cleanup {
- maybeUnregisterTetheringEventCallback(tetheringEventCallback)
}
}
}
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 6394599..c71d925 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -39,19 +39,6 @@
import android.net.TestNetworkManager
import android.net.TestNetworkSpecifier
import android.net.connectivity.ConnectivityCompatChanges
-import android.net.cts.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted
-import android.net.cts.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped
-import android.net.cts.NsdDiscoveryRecord.DiscoveryEvent.ServiceFound
-import android.net.cts.NsdDiscoveryRecord.DiscoveryEvent.ServiceLost
-import android.net.cts.NsdRegistrationRecord.RegistrationEvent.RegistrationFailed
-import android.net.cts.NsdRegistrationRecord.RegistrationEvent.ServiceRegistered
-import android.net.cts.NsdRegistrationRecord.RegistrationEvent.ServiceUnregistered
-import android.net.cts.NsdResolveRecord.ResolveEvent.ResolutionStopped
-import android.net.cts.NsdResolveRecord.ResolveEvent.ServiceResolved
-import android.net.cts.NsdResolveRecord.ResolveEvent.StopResolutionFailed
-import android.net.cts.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdated
-import android.net.cts.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdatedLost
-import android.net.cts.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.UnregisterCallbackSucceeded
import android.net.cts.util.CtsNetUtils
import android.net.nsd.DiscoveryRequest
import android.net.nsd.NsdManager
@@ -92,16 +79,40 @@
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.DeviceConfigRule
import com.android.testutils.NSResponder
+import com.android.testutils.NsdDiscoveryRecord
+import com.android.testutils.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted
+import com.android.testutils.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped
+import com.android.testutils.NsdDiscoveryRecord.DiscoveryEvent.ServiceFound
+import com.android.testutils.NsdDiscoveryRecord.DiscoveryEvent.ServiceLost
+import com.android.testutils.NsdEvent
+import com.android.testutils.NsdRecord
+import com.android.testutils.NsdRegistrationRecord
+import com.android.testutils.NsdRegistrationRecord.RegistrationEvent.RegistrationFailed
+import com.android.testutils.NsdRegistrationRecord.RegistrationEvent.ServiceRegistered
+import com.android.testutils.NsdRegistrationRecord.RegistrationEvent.ServiceUnregistered
+import com.android.testutils.NsdResolveRecord
+import com.android.testutils.NsdResolveRecord.ResolveEvent.ResolutionStopped
+import com.android.testutils.NsdResolveRecord.ResolveEvent.ServiceResolved
+import com.android.testutils.NsdResolveRecord.ResolveEvent.StopResolutionFailed
+import com.android.testutils.NsdServiceInfoCallbackRecord
+import com.android.testutils.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdated
+import com.android.testutils.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdatedLost
+import com.android.testutils.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.UnregisterCallbackSucceeded
import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
import com.android.testutils.TapPacketReader
+import com.android.testutils.TestDnsPacket
import com.android.testutils.TestableNetworkAgent
import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkCreated
import com.android.testutils.TestableNetworkCallback
-import com.android.testutils.assertContainsExactly
import com.android.testutils.assertEmpty
import com.android.testutils.filters.CtsNetTestCasesMaxTargetSdk30
import com.android.testutils.filters.CtsNetTestCasesMaxTargetSdk33
+import com.android.testutils.pollForAdvertisement
+import com.android.testutils.pollForMdnsPacket
+import com.android.testutils.pollForProbe
+import com.android.testutils.pollForQuery
+import com.android.testutils.pollForReply
import com.android.testutils.runAsShell
import com.android.testutils.tryTest
import com.android.testutils.waitForIdle
@@ -116,8 +127,10 @@
import java.util.Random
import java.util.concurrent.Executor
import kotlin.math.min
+import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
+import kotlin.test.assertNotEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.fail
@@ -130,10 +143,10 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.test.assertNotEquals
private const val TAG = "NsdManagerTest"
private const val TIMEOUT_MS = 2000L
+
// Registration may take a long time if there are devices with the same hostname on the network,
// as the device needs to try another name and probe again. This is especially true since when using
// mdnsresponder the usual hostname is "Android", and on conflict "Android-2", "Android-3", ... are
@@ -175,11 +188,12 @@
private val customHostname = "NsdTestHost%09d".format(Random().nextInt(1_000_000_000))
private val customHostname2 = "NsdTestHost%09d".format(Random().nextInt(1_000_000_000))
private val publicKey = hexStringToByteArray(
- "0201030dc141d0637960b98cbc12cfca"
- + "221d2879dac26ee5b460e9007c992e19"
- + "02d897c391b03764d448f7d0c772fdb0"
- + "3b1d9d6d52ff8886769e8e2362513565"
- + "270962d3")
+ "0201030dc141d0637960b98cbc12cfca" +
+ "221d2879dac26ee5b460e9007c992e19" +
+ "02d897c391b03764d448f7d0c772fdb0" +
+ "3b1d9d6d52ff8886769e8e2362513565" +
+ "270962d3"
+ )
private val handlerThread = HandlerThread(NsdManagerTest::class.java.simpleName)
private val ctsNetUtils by lazy{ CtsNetUtils(context) }
@@ -231,11 +245,14 @@
val iface = tnm.createTapInterface()
val cb = TestableNetworkCallback()
val testNetworkSpecifier = TestNetworkSpecifier(iface.interfaceName)
- cm.requestNetwork(NetworkRequest.Builder()
+ cm.requestNetwork(
+ NetworkRequest.Builder()
.removeCapability(NET_CAPABILITY_TRUSTED)
.addTransportType(TRANSPORT_TEST)
.setNetworkSpecifier(testNetworkSpecifier)
- .build(), cb)
+ .build(),
+ cb
+ )
val agent = registerTestNetworkAgent(iface.interfaceName)
val network = agent.network ?: fail("Registered agent should have a network")
@@ -255,12 +272,17 @@
val lp = LinkProperties().apply {
interfaceName = ifaceName
}
- val agent = TestableNetworkAgent(context, handlerThread.looper,
+ val agent = TestableNetworkAgent(
+ context,
+ handlerThread.looper,
NetworkCapabilities().apply {
removeCapability(NET_CAPABILITY_TRUSTED)
addTransportType(TRANSPORT_TEST)
setNetworkSpecifier(TestNetworkSpecifier(ifaceName))
- }, lp, NetworkAgentConfig.Builder().build())
+ },
+ lp,
+ NetworkAgentConfig.Builder().build()
+ )
val network = agent.register()
agent.markConnected()
agent.expectCallback<OnNetworkCreated>()
@@ -334,15 +356,19 @@
Triple(null, null, "null key"),
Triple("", null, "empty key"),
Triple(string256, null, "key with 256 characters"),
- Triple("key", string256.substring(3),
- "key+value combination with more than 255 characters"),
+ Triple(
+ "key",
+ string256.substring(3),
+ "key+value combination with more than 255 characters"
+ ),
Triple("key", string256.substring(4), "key+value combination with 255 characters"),
Triple("\u0019", null, "key with invalid character"),
Triple("=", null, "key with invalid character"),
Triple("\u007f", null, "key with invalid character")
).forEach {
assertFailsWith<IllegalArgumentException>(
- "Setting invalid ${it.third} unexpectedly succeeded") {
+ "Setting invalid ${it.third} unexpectedly succeeded"
+ ) {
si.setAttribute(it.first, it.second)
}
}
@@ -365,7 +391,8 @@
// Test registering without an Executor
nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, registrationRecord)
val registeredInfo = registrationRecord.expectCallback<ServiceRegistered>(
- REGISTRATION_TIMEOUT_MS).serviceInfo
+ REGISTRATION_TIMEOUT_MS
+ ).serviceInfo
// Only service name is included in ServiceRegistered callbacks
assertNull(registeredInfo.serviceType)
@@ -380,7 +407,9 @@
// Expect a service record to be discovered
val foundInfo = discoveryRecord.waitForServiceDiscovered(
- registeredInfo.serviceName, serviceType)
+ registeredInfo.serviceName,
+ serviceType
+ )
// Test resolving without an Executor
val resolveRecord = NsdResolveRecord()
@@ -395,8 +424,10 @@
assertNull(resolvedService.attributes["booleanAttr"])
assertEquals("value", resolvedService.attributes["keyValueAttr"].utf8ToString())
assertEquals("=", resolvedService.attributes["keyEqualsAttr"].utf8ToString())
- assertEquals(" value ",
- resolvedService.attributes[" whiteSpaceKeyValueAttr "].utf8ToString())
+ assertEquals(
+ " value ",
+ resolvedService.attributes[" whiteSpaceKeyValueAttr "].utf8ToString()
+ )
assertEquals(string256.substring(9), resolvedService.attributes["longkey"].utf8ToString())
assertArrayEquals(testByteArray, resolvedService.attributes["binaryDataAttr"])
assertTrue(resolvedService.attributes.containsKey("nullBinaryDataAttr"))
@@ -430,12 +461,15 @@
val registrationRecord2 = NsdRegistrationRecord()
nsdManager.registerService(si2, NsdManager.PROTOCOL_DNS_SD, registrationRecord2)
val registeredInfo2 = registrationRecord2.expectCallback<ServiceRegistered>(
- REGISTRATION_TIMEOUT_MS).serviceInfo
+ REGISTRATION_TIMEOUT_MS
+ ).serviceInfo
// Expect a service record to be discovered (and filter the ones
// that are unrelated to this test)
val foundInfo2 = discoveryRecord.waitForServiceDiscovered(
- registeredInfo2.serviceName, serviceType)
+ registeredInfo2.serviceName,
+ serviceType
+ )
// Resolve the service
val resolveRecord2 = NsdResolveRecord()
@@ -938,7 +972,8 @@
// when the compat change is disabled.
// Note that before T the compat constant had a different int value.
assertFalse(CompatChanges.isChangeEnabled(
- ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER))
+ ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER
+ ))
}
@Test
@@ -1011,7 +1046,9 @@
// onStopResolutionFailed on the record directly then verify it is received.
val resolveRecord = NsdResolveRecord()
resolveRecord.onStopResolutionFailed(
- NsdServiceInfo(), NsdManager.FAILURE_OPERATION_NOT_RUNNING)
+ NsdServiceInfo(),
+ NsdManager.FAILURE_OPERATION_NOT_RUNNING
+ )
val failedCb = resolveRecord.expectCallback<StopResolutionFailed>()
assertEquals(NsdManager.FAILURE_OPERATION_NOT_RUNNING, failedCb.errorCode)
}
@@ -1262,15 +1299,22 @@
val si = makeTestServiceInfo(testNetwork1.network)
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
// Register service on testNetwork1
val registrationRecord = NsdRegistrationRecord()
- nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, { it.run() },
- registrationRecord)
+ nsdManager.registerService(
+ si,
+ NsdManager.PROTOCOL_DNS_SD,
+ { it.run() },
+ registrationRecord
+ )
tryTest {
assertNotNull(packetReader.pollForProbe(serviceName, serviceType),
@@ -1301,15 +1345,22 @@
parseNumericAddress("2001:db8::3"))
}
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
// Register service on testNetwork1
val registrationRecord = NsdRegistrationRecord()
- nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, { it.run() },
- registrationRecord)
+ nsdManager.registerService(
+ si,
+ NsdManager.PROTOCOL_DNS_SD,
+ { it.run() },
+ registrationRecord
+ )
tryTest {
assertNotNull(packetReader.pollForProbe(serviceName, serviceType),
@@ -1340,15 +1391,22 @@
hostname = customHostname
}
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
// Register service on testNetwork1
val registrationRecord = NsdRegistrationRecord()
- nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, { it.run() },
- registrationRecord)
+ nsdManager.registerService(
+ si,
+ NsdManager.PROTOCOL_DNS_SD,
+ { it.run() },
+ registrationRecord
+ )
tryTest {
assertNotNull(packetReader.pollForProbe(serviceName, serviceType),
@@ -1380,8 +1438,11 @@
val registrationRecord = NsdRegistrationRecord()
val discoveryRecord = NsdDiscoveryRecord()
val registeredService = registerService(registrationRecord, si)
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -1459,7 +1520,9 @@
val registeredService = registerService(registrationRecord, si)
val packetReader = TapPacketReader(
Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -1489,11 +1552,11 @@
val newRegistration =
registrationRecord
.expectCallbackEventually<ServiceRegistered>(REGISTRATION_TIMEOUT_MS) {
- it.serviceInfo.serviceName == serviceName
- && it.serviceInfo.hostname.let { hostname ->
- hostname != null
- && hostname.startsWith(customHostname)
- && hostname != customHostname
+ it.serviceInfo.serviceName == serviceName &&
+ it.serviceInfo.hostname.let { hostname ->
+ hostname != null &&
+ hostname.startsWith(customHostname) &&
+ hostname != customHostname
}
}
@@ -1524,8 +1587,11 @@
val registrationRecord = NsdRegistrationRecord()
val discoveryRecord = NsdDiscoveryRecord()
val registeredService = registerService(registrationRecord, si)
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -1564,13 +1630,21 @@
fun testDiscoveryWithPtrOnlyResponse_ServiceIsFound() {
// Register service on testNetwork1
val discoveryRecord = NsdDiscoveryRecord()
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
- nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
- testNetwork1.network, { it.run() }, discoveryRecord)
+ nsdManager.discoverServices(
+ serviceType,
+ NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network,
+ { it.run() },
+ discoveryRecord
+ )
tryTest {
discoveryRecord.expectCallback<DiscoveryStarted>()
@@ -1614,8 +1688,10 @@
fun testResolveWhenServerSendsNoAdditionalRecord() {
// Resolve service on testNetwork1
val resolveRecord = NsdResolveRecord()
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
)
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -1639,16 +1715,21 @@
rdata='testkey=testvalue')
))).hex()
*/
- val srvTxtResponsePayload = HexDump.hexStringToByteArray("000084000000000200000000104" +
+ val srvTxtResponsePayload = HexDump.hexStringToByteArray(
+ "000084000000000200000000104" +
"e7364546573743132333435363738390d5f6e6d74313233343536373839045f746370056c6f6" +
"3616c0000218001000000780011000000007a020874657374686f7374c030c00c00100001000" +
- "00078001211746573746b65793d7465737476616c7565")
+ "00078001211746573746b65793d7465737476616c7565"
+ )
replaceServiceNameAndTypeWithTestSuffix(srvTxtResponsePayload)
packetReader.sendResponse(buildMdnsPacket(srvTxtResponsePayload))
val testHostname = "testhost.local"
- val addressQuery = packetReader.pollForQuery(testHostname,
- DnsResolver.TYPE_A, DnsResolver.TYPE_AAAA)
+ val addressQuery = packetReader.pollForQuery(
+ testHostname,
+ DnsResolver.TYPE_A,
+ DnsResolver.TYPE_AAAA
+ )
assertNotNull(addressQuery)
/*
@@ -1660,9 +1741,11 @@
rdata='2001:db8::123')
))).hex()
*/
- val addressPayload = HexDump.hexStringToByteArray("0000840000000002000000000874657374" +
+ val addressPayload = HexDump.hexStringToByteArray(
+ "0000840000000002000000000874657374" +
"686f7374056c6f63616c0000010001000000780004c000027bc00c001c000100000078001020" +
- "010db8000000000000000000000123")
+ "010db8000000000000000000000123"
+ )
packetReader.sendResponse(buildMdnsPacket(addressPayload))
val serviceResolved = resolveRecord.expectCallback<ServiceResolved>()
@@ -1676,7 +1759,8 @@
}
assertEquals(
setOf(parseNumericAddress("192.0.2.123"), parseNumericAddress("2001:db8::123")),
- serviceResolved.serviceInfo.hostAddresses.toSet())
+ serviceResolved.serviceInfo.hostAddresses.toSet()
+ )
}
@Test
@@ -1703,7 +1787,7 @@
scapy.raw(scapy.DNS(rd=0, qr=0, aa=0, qd =
scapy.DNSQR(qname='_nmt123456789._tcp.local', qtype='PTR', qclass=0x8001)
)).hex()
- */
+ */
val mdnsPayload = HexDump.hexStringToByteArray("0000000000010000000000000d5f6e6d74313" +
"233343536373839045f746370056c6f63616c00000c8001")
replaceServiceNameAndTypeWithTestSuffix(mdnsPayload)
@@ -1759,7 +1843,7 @@
an = scapy.DNSRR(rrname='_nmt123456789._tcp.local', type='PTR', ttl=4500,
rdata='NsdTest123456789._nmt123456789._tcp.local')
)).hex()
- */
+ */
val query = HexDump.hexStringToByteArray("0000000000020001000000000d5f6e6d74313233343" +
"536373839045f746370056c6f63616c00000c8001104e7364546573743132333435363738390" +
"d5f6e6d74313233343536373839045f746370056c6f63616c00001080010d5f6e6d743132333" +
@@ -1794,7 +1878,7 @@
an = scapy.DNSRR(rrname='_nmt123456789._tcp.local', type='PTR', ttl=2150,
rdata='NsdTest123456789._nmt123456789._tcp.local')
)).hex()
- */
+ */
val query2 = HexDump.hexStringToByteArray("0000000000020001000000000d5f6e6d7431323334" +
"3536373839045f746370056c6f63616c00000c8001104e736454657374313233343536373839" +
"0d5f6e6d74313233343536373839045f746370056c6f63616c00001080010d5f6e6d74313233" +
@@ -1846,7 +1930,7 @@
scapy.DNSQR(qname='NsdTest123456789._nmt123456789._tcp.local', qtype='TXT',
qclass=0x8001)
)).hex()
- */
+ */
val query = HexDump.hexStringToByteArray("0000020000020000000000000d5f6e6d74313233343" +
"536373839045f746370056c6f63616c00000c8001104e7364546573743132333435363738390" +
"d5f6e6d74313233343536373839045f746370056c6f63616c0000108001")
@@ -1858,7 +1942,7 @@
an = scapy.DNSRR(rrname='_test._tcp.local', type='PTR', ttl=4500,
rdata='NsdTest._test._tcp.local')
)).hex()
- */
+ */
val knownAnswer1 = HexDump.hexStringToByteArray("000002000000000100000000055f74657374" +
"045f746370056c6f63616c00000c000100001194001a074e736454657374055f74657374045f" +
"746370056c6f63616c00")
@@ -1870,7 +1954,7 @@
an = scapy.DNSRR(rrname='_nmt123456789._tcp.local', type='PTR', ttl=4500,
rdata='NsdTest123456789._nmt123456789._tcp.local')
)).hex()
- */
+ */
val knownAnswer2 = HexDump.hexStringToByteArray("0000000000000001000000000d5f6e6d7431" +
"3233343536373839045f746370056c6f63616c00000c000100001194002b104e736454657374" +
"3132333435363738390d5f6e6d74313233343536373839045f746370056c6f63616c00")
@@ -1907,13 +1991,21 @@
// Register service on testNetwork1
val discoveryRecord = NsdDiscoveryRecord()
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
- nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
- testNetwork1.network, { it.run() }, discoveryRecord)
+ nsdManager.discoverServices(
+ serviceType,
+ NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network,
+ { it.run() },
+ discoveryRecord
+ )
tryTest {
discoveryRecord.expectCallback<DiscoveryStarted>()
@@ -1975,10 +2067,12 @@
val hostAddresses1 = listOf(
parseNumericAddress("192.0.2.23"),
parseNumericAddress("2001:db8::1"),
- parseNumericAddress("2001:db8::2"))
+ parseNumericAddress("2001:db8::2")
+ )
val hostAddresses2 = listOf(
parseNumericAddress("192.0.2.24"),
- parseNumericAddress("2001:db8::3"))
+ parseNumericAddress("2001:db8::3")
+ )
val si1 = NsdServiceInfo().also {
it.network = testNetwork1.network
it.serviceName = serviceName
@@ -2042,10 +2136,12 @@
val hostAddresses1 = listOf(
parseNumericAddress("192.0.2.23"),
parseNumericAddress("2001:db8::1"),
- parseNumericAddress("2001:db8::2"))
+ parseNumericAddress("2001:db8::2")
+ )
val hostAddresses2 = listOf(
parseNumericAddress("192.0.2.24"),
- parseNumericAddress("2001:db8::3"))
+ parseNumericAddress("2001:db8::3")
+ )
val si1 = NsdServiceInfo().also {
it.network = testNetwork1.network
it.hostname = customHostname
@@ -2093,7 +2189,8 @@
val hostAddresses = listOf(
parseNumericAddress("192.0.2.23"),
parseNumericAddress("2001:db8::1"),
- parseNumericAddress("2001:db8::2"))
+ parseNumericAddress("2001:db8::2")
+ )
val si1 = NsdServiceInfo().also {
it.network = testNetwork1.network
it.serviceType = serviceType
@@ -2258,8 +2355,11 @@
it.port = TEST_PORT
it.publicKey = publicKey
}
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -2310,8 +2410,11 @@
parseNumericAddress("2001:db8::2"))
it.publicKey = publicKey
}
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -2364,8 +2467,11 @@
it.hostAddresses = listOf()
it.publicKey = publicKey
}
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -2418,10 +2524,20 @@
val discoveryRecord1 = NsdDiscoveryRecord()
val discoveryRecord2 = NsdDiscoveryRecord()
val discoveryRecord3 = NsdDiscoveryRecord()
- nsdManager.discoverServices("_test1._tcp", NsdManager.PROTOCOL_DNS_SD,
- testNetwork1.network, { it.run() }, discoveryRecord1)
- nsdManager.discoverServices("_test2._tcp", NsdManager.PROTOCOL_DNS_SD,
- testNetwork1.network, { it.run() }, discoveryRecord2)
+ nsdManager.discoverServices(
+ "_test1._tcp",
+ NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network,
+ { it.run() },
+ discoveryRecord1
+ )
+ nsdManager.discoverServices(
+ "_test2._tcp",
+ NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network,
+ { it.run() },
+ discoveryRecord2
+ )
nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryRecord3)
tryTest {
@@ -2459,6 +2575,44 @@
}
}
+ @Test
+ fun testAdvertiseServiceWithNoAttributes_TxtRecordIstNotEmpty() {
+ deviceConfigRule.setConfig(
+ NAMESPACE_TETHERING,
+ "test_nsd_avoid_advertising_empty_txt_records",
+ "1"
+ )
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
+ packetReader.startAsyncForTest()
+ handlerThread.waitForIdle(TIMEOUT_MS)
+
+ // Test behavior described in RFC6763 6.1: empty TXT records are not allowed, but TXT
+ // records with a zero length string are equivalent.
+ val si = makeTestServiceInfo(testNetwork1.network)
+ // Register service on testNetwork1
+ val registrationRecord = NsdRegistrationRecord()
+ registerService(registrationRecord, si)
+
+ tryTest {
+ val announcement =
+ packetReader.pollForReply("$serviceName.$serviceType.local", DnsResolver.TYPE_TXT)
+ assertNotNull(announcement)
+ val txtRecords = announcement.records[ANSECTION].filter {
+ it.nsType == DnsResolver.TYPE_TXT
+ }
+ assertEquals(1, txtRecords.size)
+ // The TXT record should contain as single zero
+ assertContentEquals(byteArrayOf(0), txtRecords[0].rr)
+ } cleanup {
+ nsdManager.unregisterService(registrationRecord)
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ }
+ }
+
private fun hasServiceTypeClientsForNetwork(clients: List<String>, network: Network): Boolean {
return clients.any { client -> client.substring(
client.indexOf("network=") + "network=".length,
@@ -2477,11 +2631,16 @@
*/
private fun getServiceTypeClients(): List<String> {
return SystemUtil.runShellCommand(
- InstrumentationRegistry.getInstrumentation(), "dumpsys servicediscovery")
+ InstrumentationRegistry.getInstrumentation(),
+ "dumpsys servicediscovery"
+ )
.split("\n").mapNotNull { line ->
line.indexOf("ServiceTypeClient:").let { idx ->
- if (idx == -1) null
- else line.substring(idx)
+ if (idx == -1) {
+ null
+ } else {
+ line.substring(idx)
+ }
}
}
}
@@ -2494,9 +2653,11 @@
rclass=0x8001, port=31234, target='conflict.local', ttl=120)
)).hex()
*/
- val mdnsPayload = HexDump.hexStringToByteArray("000084000000000100000000104e736454657" +
+ val mdnsPayload = HexDump.hexStringToByteArray(
+ "000084000000000100000000104e736454657" +
"3743132333435363738390d5f6e6d74313233343536373839045f746370056c6f63616c00002" +
- "18001000000780016000000007a0208636f6e666c696374056c6f63616c00")
+ "18001000000780016000000007a0208636f6e666c696374056c6f63616c00"
+ )
replaceServiceNameAndTypeWithTestSuffix(mdnsPayload)
return buildMdnsPacket(mdnsPayload)
@@ -2510,9 +2671,11 @@
rdata='2001:db8::321')
)).hex()
*/
- val mdnsPayload = HexDump.hexStringToByteArray("000084000000000100000000144e7364" +
+ val mdnsPayload = HexDump.hexStringToByteArray(
+ "000084000000000100000000144e7364" +
"54657374486f7374313233343536373839056c6f63616c00001c000100000078001020010db80000" +
- "00000000000000000321")
+ "00000000000000000321"
+ )
replaceCustomHostnameWithTestSuffix(mdnsPayload)
return buildMdnsPacket(mdnsPayload)
@@ -2563,22 +2726,29 @@
mdnsPayload: ByteArray,
srcAddr: Inet6Address = testSrcAddr
): ByteBuffer {
- val packetBuffer = PacketBuilder.allocate(true /* hasEther */, IPPROTO_IPV6,
- IPPROTO_UDP, mdnsPayload.size)
+ val packetBuffer = PacketBuilder.allocate(
+ true /* hasEther */,
+ IPPROTO_IPV6,
+ IPPROTO_UDP,
+ mdnsPayload.size
+ )
val packetBuilder = PacketBuilder(packetBuffer)
// Multicast ethernet address for IPv6 to ff02::fb
val multicastEthAddr = MacAddress.fromBytes(
- byteArrayOf(0x33, 0x33, 0, 0, 0, 0xfb.toByte()))
+ byteArrayOf(0x33, 0x33, 0, 0, 0, 0xfb.toByte())
+ )
packetBuilder.writeL2Header(
MacAddress.fromBytes(byteArrayOf(1, 2, 3, 4, 5, 6)) /* srcMac */,
multicastEthAddr,
- ETH_P_IPV6.toShort())
+ ETH_P_IPV6.toShort()
+ )
packetBuilder.writeIpv6Header(
0x60000000, // version=6, traffic class=0x0, flowlabel=0x0
IPPROTO_UDP.toByte(),
64 /* hop limit */,
srcAddr,
- multicastIpv6Addr /* dstIp */)
+ multicastIpv6Addr /* dstIp */
+ )
packetBuilder.writeUdpHeader(MDNS_PORT /* srcPort */, MDNS_PORT /* dstPort */)
packetBuffer.put(mdnsPayload)
return packetBuilder.finalizePacket()
diff --git a/tests/cts/net/util/java/android/net/cts/util/EthernetTestInterface.kt b/tests/cts/net/util/java/android/net/cts/util/EthernetTestInterface.kt
new file mode 100644
index 0000000..32d6899
--- /dev/null
+++ b/tests/cts/net/util/java/android/net/cts/util/EthernetTestInterface.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts.util
+
+import android.Manifest.permission.NETWORK_SETTINGS
+import android.content.Context
+import android.net.EthernetManager
+import android.net.EthernetManager.InterfaceStateListener
+import android.net.EthernetManager.STATE_ABSENT
+import android.net.EthernetManager.STATE_LINK_UP
+import android.net.IpConfiguration
+import android.net.TestNetworkInterface
+import android.net.cts.util.EthernetTestInterface.EthernetStateListener.CallbackEntry.InterfaceStateChanged
+import android.os.Handler
+import android.util.Log
+import com.android.net.module.util.ArrayTrackRecord
+import com.android.testutils.TapPacketReader
+import com.android.testutils.runAsShell
+import com.android.testutils.waitForIdle
+import java.net.NetworkInterface
+import kotlin.concurrent.Volatile
+import kotlin.test.assertNotNull
+
+private const val TAG = "EthernetTestInterface"
+private const val TIMEOUT_MS = 5_000L
+
+/**
+ * A class to manage the lifecycle of an ethernet interface.
+ *
+ * This class encapsulates creating new tun/tap interfaces and registering them with ethernet
+ * service.
+ */
+class EthernetTestInterface(
+ private val context: Context,
+ private val handler: Handler,
+ val testIface: TestNetworkInterface
+) {
+ private class EthernetStateListener(private val trackedIface: String) : InterfaceStateListener {
+ val events = ArrayTrackRecord<CallbackEntry>().newReadHead()
+
+ sealed class CallbackEntry {
+ data class InterfaceStateChanged(
+ val iface: String,
+ val state: Int,
+ val role: Int,
+ val cfg: IpConfiguration?
+ ) : CallbackEntry()
+ }
+
+ override fun onInterfaceStateChanged(
+ iface: String,
+ state: Int,
+ role: Int,
+ cfg: IpConfiguration?
+ ) {
+ // filter out callbacks for other interfaces
+ if (iface != trackedIface) return
+ events.add(InterfaceStateChanged(iface, state, role, cfg))
+ }
+
+ fun eventuallyExpect(state: Int) {
+ val cb = events.poll(TIMEOUT_MS) { it is InterfaceStateChanged && it.state == state }
+ assertNotNull(cb, "Never received state $state. Got: ${events.backtrace()}")
+ }
+ }
+
+ val name get() = testIface.interfaceName
+ val mtu: Int
+ get() {
+ val nif = NetworkInterface.getByName(name)
+ assertNotNull(nif)
+ return nif.mtu
+ }
+ val packetReader = TapPacketReader(handler, testIface.fileDescriptor.fileDescriptor, mtu)
+ private val listener = EthernetStateListener(name)
+ private val em = context.getSystemService(EthernetManager::class.java)!!
+ @Volatile private var cleanedUp = false
+
+ init{
+ em.addInterfaceStateListener(handler::post, listener)
+ runAsShell(NETWORK_SETTINGS) {
+ em.setIncludeTestInterfaces(true)
+ }
+ // Wait for link up to be processed in EthernetManager before returning.
+ listener.eventuallyExpect(STATE_LINK_UP)
+ handler.post { packetReader.start() }
+ handler.waitForIdle(TIMEOUT_MS)
+ }
+
+ fun destroy() {
+ // packetReader.stop() closes the test interface.
+ handler.post { packetReader.stop() }
+ handler.waitForIdle(TIMEOUT_MS)
+ listener.eventuallyExpect(STATE_ABSENT)
+
+ // setIncludeTestInterfaces() posts on the handler and does not run synchronously. However,
+ // there should be no need for a synchronization mechanism here. If the next test is
+ // bringing up its interface, a RTM_NEWLINK will be put on the back of the handler and is
+ // guaranteed to be in order with (i.e. after) this call, so there is no race here.
+ runAsShell(NETWORK_SETTINGS) {
+ em.setIncludeTestInterfaces(false)
+ }
+ em.removeInterfaceStateListener(listener)
+
+ cleanedUp = true
+ }
+
+ protected fun finalize() {
+ if (!cleanedUp) {
+ Log.wtf(TAG, "destroy() was not called for interface $name.")
+ destroy()
+ }
+ }
+}
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 81608f7..8794847 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -235,6 +235,12 @@
assertEquals(TETHERING_USB, tr2.getTetheringType());
assertTrue(tr2.isExemptFromEntitlementCheck());
assertFalse(tr2.getShouldShowEntitlementUi());
+
+ final TetheringRequest tr3 = new TetheringRequest.Builder(TETHERING_USB)
+ .setStaticIpv4Addresses(localAddr, clientAddr)
+ .setExemptFromEntitlementCheck(true)
+ .setShouldShowEntitlementUi(false).build();
+ assertEquals(tr2, tr3);
}
@Test
@@ -246,15 +252,7 @@
.setExemptFromEntitlementCheck(true)
.setShouldShowEntitlementUi(false).build();
final TetheringRequest parceled = ParcelUtils.parcelingRoundTrip(unparceled);
- assertEquals(unparceled.getTetheringType(), parceled.getTetheringType());
- assertEquals(unparceled.getConnectivityScope(), parceled.getConnectivityScope());
- assertEquals(unparceled.getLocalIpv4Address(), parceled.getLocalIpv4Address());
- assertEquals(unparceled.getClientStaticIpv4Address(),
- parceled.getClientStaticIpv4Address());
- assertEquals(unparceled.isExemptFromEntitlementCheck(),
- parceled.isExemptFromEntitlementCheck());
- assertEquals(unparceled.getShouldShowEntitlementUi(),
- parceled.getShouldShowEntitlementUi());
+ assertEquals(unparceled, parceled);
}
@Test
diff --git a/tests/mts/Android.bp b/tests/mts/Android.bp
index c118d0a..9d158fd 100644
--- a/tests/mts/Android.bp
+++ b/tests/mts/Android.bp
@@ -40,6 +40,6 @@
srcs: [
"bpf_existence_test.cpp",
],
- compile_multilib: "first",
+ compile_multilib: "both",
min_sdk_version: "30", // Ensure test runs on R and above.
}
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index b02c61c..29f5cd2 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -93,8 +93,6 @@
SHARED "map_dscpPolicy_ipv4_dscp_policies_map",
SHARED "map_dscpPolicy_ipv6_dscp_policies_map",
SHARED "map_dscpPolicy_socket_policy_cache_map",
- SHARED "map_gentle_test",
- SHARED "prog_gentle_skfilter_accept",
NETD "map_netd_app_uid_stats_map",
NETD "map_netd_configuration_map",
NETD "map_netd_cookie_tag_map",
diff --git a/tests/native/connectivity_native_test/Android.bp b/tests/native/connectivity_native_test/Android.bp
index c5088c6..39a08fa 100644
--- a/tests/native/connectivity_native_test/Android.bp
+++ b/tests/native/connectivity_native_test/Android.bp
@@ -21,6 +21,7 @@
stl: "libc++_static",
shared_libs: [
"libbinder_ndk",
+ "libcom.android.tethering.connectivity_native",
"liblog",
"libnetutils",
"libprocessgroup",
@@ -32,7 +33,7 @@
"libmodules-utils-build",
"libutils",
],
- compile_multilib: "first",
+ compile_multilib: "both",
}
filegroup {
diff --git a/tests/native/connectivity_native_test/AndroidTestTemplate.xml b/tests/native/connectivity_native_test/AndroidTestTemplate.xml
index 44e35a9..4ea114c 100644
--- a/tests/native/connectivity_native_test/AndroidTestTemplate.xml
+++ b/tests/native/connectivity_native_test/AndroidTestTemplate.xml
@@ -15,8 +15,10 @@
<configuration description="Configuration for connectivity {MODULE} tests">
<option name="test-suite-tag" value="mts" />
<option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
- <!-- The tested code is only part of a SDK 30+ module (Tethering) -->
- <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+ <!-- The tested library is only usable on U+ -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk34ModuleController" />
+ <!-- The tested library is only available with the primary abi -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
diff --git a/tests/native/connectivity_native_test/connectivity_native_test.cpp b/tests/native/connectivity_native_test/connectivity_native_test.cpp
index f62a30b..faaf150 100644
--- a/tests/native/connectivity_native_test/connectivity_native_test.cpp
+++ b/tests/native/connectivity_native_test/connectivity_native_test.cpp
@@ -14,25 +14,17 @@
* limitations under the License.
*/
+#include <android-modules-utils/sdk_level.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
-#include <android-modules-utils/sdk_level.h>
-#include <cutils/misc.h> // FIRST_APPLICATION_UID
+#include <connectivity_native.h>
+#include <cutils/misc.h> // FIRST_APPLICATION_UID
#include <dlfcn.h>
#include <gtest/gtest.h>
#include <netinet/in.h>
#include "bpf/BpfUtils.h"
-typedef int (*GetPortsBlockedForBind)(in_port_t*, size_t*);
-GetPortsBlockedForBind getPortsBlockedForBind;
-typedef int (*BlockPortForBind)(in_port_t);
-BlockPortForBind blockPortForBind;
-typedef int (*UnblockPortForBind)(in_port_t);
-UnblockPortForBind unblockPortForBind;
-typedef int (*UnblockAllPortsForBind)();
-UnblockAllPortsForBind unblockAllPortsForBind;
-
class ConnectivityNativeBinderTest : public ::testing::Test {
public:
in_port_t mActualBlockedPorts[65535];
@@ -50,29 +42,12 @@
if (!android::bpf::isAtLeastKernelVersion(5, 4, 0))
GTEST_SKIP() << "Kernel should be at least 5.4.";
- // Necessary to use dlopen/dlsym since the lib is only available on U and there
- // is no Sdk34ModuleController in tradefed yet.
- // TODO: link against the library directly and add Sdk34ModuleController to
- // AndroidTest.txml when available.
- void* nativeLib = dlopen("libcom.android.tethering.connectivity_native.so", RTLD_NOW);
- ASSERT_NE(nullptr, nativeLib);
- getPortsBlockedForBind = reinterpret_cast<GetPortsBlockedForBind>(
- dlsym(nativeLib, "AConnectivityNative_getPortsBlockedForBind"));
- ASSERT_NE(nullptr, getPortsBlockedForBind);
- blockPortForBind = reinterpret_cast<BlockPortForBind>(
- dlsym(nativeLib, "AConnectivityNative_blockPortForBind"));
- ASSERT_NE(nullptr, blockPortForBind);
- unblockPortForBind = reinterpret_cast<UnblockPortForBind>(
- dlsym(nativeLib, "AConnectivityNative_unblockPortForBind"));
- ASSERT_NE(nullptr, unblockPortForBind);
- unblockAllPortsForBind = reinterpret_cast<UnblockAllPortsForBind>(
- dlsym(nativeLib, "AConnectivityNative_unblockAllPortsForBind"));
- ASSERT_NE(nullptr, unblockAllPortsForBind);
-
- // If there are already ports being blocked on device unblockAllPortsForBind() store
- // the currently blocked ports and add them back at the end of the test. Do this for
- // every test case so additional test cases do not forget to add ports back.
- int err = getPortsBlockedForBind(mActualBlockedPorts, &mActualBlockedPortsCount);
+ // If there are already ports being blocked on device store the
+ // currently blocked ports and add them back at the end of the test. Do
+ // this for every test case so additional test cases do not forget to
+ // add ports back.
+ int err = AConnectivityNative_getPortsBlockedForBind(
+ mActualBlockedPorts, &mActualBlockedPortsCount);
EXPECT_EQ(err, 0);
restoreBlockedPorts = true;
}
@@ -81,8 +56,9 @@
int err;
if (mActualBlockedPortsCount > 0 && restoreBlockedPorts) {
for (int i=0; i < mActualBlockedPortsCount; i++) {
- err = blockPortForBind(mActualBlockedPorts[i]);
- EXPECT_EQ(err, 0);
+ err =
+ AConnectivityNative_blockPortForBind(mActualBlockedPorts[i]);
+ EXPECT_EQ(err, 0);
}
}
}
@@ -99,7 +75,7 @@
int blockedPort = 0;
if (blockPort) {
blockedPort = ntohs(port);
- err = blockPortForBind(blockedPort);
+ err = AConnectivityNative_blockPortForBind(blockedPort);
EXPECT_EQ(err, 0);
}
@@ -107,7 +83,7 @@
if (blockPort) {
EXPECT_EQ(-1, sock3);
- err = unblockPortForBind(blockedPort);
+ err = AConnectivityNative_unblockPortForBind(blockedPort);
EXPECT_EQ(err, 0);
} else {
EXPECT_NE(-1, sock3);
@@ -197,11 +173,11 @@
}
TEST_F(ConnectivityNativeBinderTest, BlockPortTwice) {
- int err = blockPortForBind(5555);
+ int err = AConnectivityNative_blockPortForBind(5555);
EXPECT_EQ(err, 0);
- err = blockPortForBind(5555);
+ err = AConnectivityNative_blockPortForBind(5555);
EXPECT_EQ(err, 0);
- err = unblockPortForBind(5555);
+ err = AConnectivityNative_unblockPortForBind(5555);
EXPECT_EQ(err, 0);
}
@@ -210,16 +186,17 @@
in_port_t blockedPorts[8] = {1, 100, 1220, 1333, 2700, 5555, 5600, 65000};
if (mActualBlockedPortsCount > 0) {
- err = unblockAllPortsForBind();
+ err = AConnectivityNative_unblockAllPortsForBind();
}
for (int i : blockedPorts) {
- err = blockPortForBind(i);
+ err = AConnectivityNative_blockPortForBind(i);
EXPECT_EQ(err, 0);
}
size_t actualBlockedPortsCount = 8;
in_port_t actualBlockedPorts[actualBlockedPortsCount];
- err = getPortsBlockedForBind((in_port_t*) actualBlockedPorts, &actualBlockedPortsCount);
+ err = AConnectivityNative_getPortsBlockedForBind(
+ (in_port_t *)actualBlockedPorts, &actualBlockedPortsCount);
EXPECT_EQ(err, 0);
EXPECT_NE(actualBlockedPortsCount, 0);
for (int i=0; i < actualBlockedPortsCount; i++) {
@@ -227,9 +204,10 @@
}
// Remove the ports we added.
- err = unblockAllPortsForBind();
+ err = AConnectivityNative_unblockAllPortsForBind();
EXPECT_EQ(err, 0);
- err = getPortsBlockedForBind(actualBlockedPorts, &actualBlockedPortsCount);
+ err = AConnectivityNative_getPortsBlockedForBind(actualBlockedPorts,
+ &actualBlockedPortsCount);
EXPECT_EQ(err, 0);
EXPECT_EQ(actualBlockedPortsCount, 0);
}
@@ -239,23 +217,25 @@
in_port_t blockedPorts[8] = {1, 100, 1220, 1333, 2700, 5555, 5600, 65000};
if (mActualBlockedPortsCount > 0) {
- err = unblockAllPortsForBind();
+ err = AConnectivityNative_unblockAllPortsForBind();
}
for (int i : blockedPorts) {
- err = blockPortForBind(i);
+ err = AConnectivityNative_blockPortForBind(i);
EXPECT_EQ(err, 0);
}
size_t actualBlockedPortsCount = 8;
in_port_t actualBlockedPorts[actualBlockedPortsCount];
- err = getPortsBlockedForBind((in_port_t*) actualBlockedPorts, &actualBlockedPortsCount);
+ err = AConnectivityNative_getPortsBlockedForBind(
+ (in_port_t *)actualBlockedPorts, &actualBlockedPortsCount);
EXPECT_EQ(err, 0);
EXPECT_EQ(actualBlockedPortsCount, 8);
- err = unblockAllPortsForBind();
+ err = AConnectivityNative_unblockAllPortsForBind();
EXPECT_EQ(err, 0);
- err = getPortsBlockedForBind((in_port_t*) actualBlockedPorts, &actualBlockedPortsCount);
+ err = AConnectivityNative_getPortsBlockedForBind(
+ (in_port_t *)actualBlockedPorts, &actualBlockedPortsCount);
EXPECT_EQ(err, 0);
EXPECT_EQ(actualBlockedPortsCount, 0);
// If mActualBlockedPorts is not empty, ports will be added back in teardown.
@@ -264,7 +244,7 @@
TEST_F(ConnectivityNativeBinderTest, CheckPermission) {
int curUid = getuid();
EXPECT_EQ(0, seteuid(FIRST_APPLICATION_UID + 2000)) << "seteuid failed: " << strerror(errno);
- int err = blockPortForBind((in_port_t) 5555);
+ int err = AConnectivityNative_blockPortForBind((in_port_t)5555);
EXPECT_EQ(EPERM, err);
EXPECT_EQ(0, seteuid(curUid)) << "seteuid failed: " << strerror(errno);
}
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 2f88c41..ef3ebb0 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -92,6 +92,7 @@
"frameworks-net-integration-testutils",
"framework-protos",
"mockito-target-minus-junit4",
+ "modules-utils-build",
"net-tests-utils",
"net-utils-services-common",
"platform-compat-test-rules",
diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java
index b71a46f..9a77c89 100644
--- a/tests/unit/java/android/net/ConnectivityManagerTest.java
+++ b/tests/unit/java/android/net/ConnectivityManagerTest.java
@@ -52,6 +52,7 @@
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -72,6 +73,7 @@
import android.os.Messenger;
import android.os.Process;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.internal.util.test.BroadcastInterceptingContext;
@@ -83,6 +85,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -240,7 +243,7 @@
// register callback
when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
- anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(request);
+ anyInt(), anyInt(), any(), nullable(String.class), anyInt())).thenReturn(request);
manager.requestNetwork(request, callback, handler);
// callback triggers
@@ -269,7 +272,7 @@
// register callback
when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
- anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req1);
+ anyInt(), anyInt(), any(), nullable(String.class), anyInt())).thenReturn(req1);
manager.requestNetwork(req1, callback, handler);
// callback triggers
@@ -287,7 +290,7 @@
// callback can be registered again
when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
- anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req2);
+ anyInt(), anyInt(), any(), nullable(String.class), anyInt())).thenReturn(req2);
manager.requestNetwork(req2, callback, handler);
// callback triggers
@@ -311,7 +314,7 @@
when(mCtx.getApplicationInfo()).thenReturn(info);
when(mService.requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(),
- anyInt(), any(), nullable(String.class))).thenReturn(request);
+ anyInt(), any(), nullable(String.class), anyInt())).thenReturn(request);
Handler handler = new Handler(Looper.getMainLooper());
manager.requestNetwork(request, callback, handler);
@@ -403,15 +406,15 @@
manager.requestNetwork(request, callback);
verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities),
eq(REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
reset(mService);
// Verify that register network callback does not calls requestNetwork at all.
manager.registerNetworkCallback(request, callback);
verify(mService, never()).requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(),
- anyInt(), anyInt(), any(), any());
+ anyInt(), anyInt(), any(), any(), anyInt());
verify(mService).listenForNetwork(eq(request.networkCapabilities), any(), any(), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
reset(mService);
Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
@@ -419,24 +422,24 @@
manager.registerDefaultNetworkCallback(callback);
verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null),
eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
reset(mService);
manager.registerDefaultNetworkCallbackForUid(42, callback, handler);
verify(mService).requestNetwork(eq(42), eq(null),
eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
manager.requestBackgroundNetwork(request, callback, handler);
verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities),
eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
reset(mService);
manager.registerSystemDefaultNetworkCallback(callback, handler);
verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null),
eq(TRACK_SYSTEM_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
reset(mService);
}
@@ -516,16 +519,154 @@
+ " attempts", ref.get());
}
- private <T> void mockService(Class<T> clazz, String name, T service) {
- doReturn(service).when(mCtx).getSystemService(name);
- doReturn(name).when(mCtx).getSystemServiceName(clazz);
+ @Test
+ public void testDeclaredMethodsFlag_requestWithMixedMethods_RegistrationFlagsMatch()
+ throws Exception {
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
- // If the test suite uses the inline mock maker library, such as for coverage tests,
- // then the final version of getSystemService must also be mocked, as the real
- // method will not be called by the test and null object is returned since no mock.
- // Otherwise, mocking a final method will fail the test.
- if (mCtx.getSystemService(clazz) == null) {
- doReturn(service).when(mCtx).getSystemService(clazz);
- }
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ final NetworkCallback callback1 = new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onPreCheck(@NonNull Network network) {}
+ @Override
+ public void onAvailable(@NonNull Network network) {}
+ @Override
+ public void onLost(@NonNull Network network) {}
+ @Override
+ public void onCapabilitiesChanged(@NonNull Network network,
+ @NonNull NetworkCapabilities networkCapabilities) {}
+ @Override
+ public void onLocalNetworkInfoChanged(@NonNull Network network,
+ @NonNull LocalNetworkInfo localNetworkInfo) {}
+ @Override
+ public void onNetworkResumed(@NonNull Network network) {}
+ @Override
+ public void onBlockedStatusChanged(@NonNull Network network, int blocked) {}
+ };
+ manager.requestNetwork(request, callback1);
+
+ final InOrder inOrder = inOrder(mService);
+ inOrder.verify(mService).requestNetwork(
+ anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(), any(), any(),
+ eq(1 << ConnectivityManager.CALLBACK_PRECHECK
+ | 1 << ConnectivityManager.CALLBACK_AVAILABLE
+ | 1 << ConnectivityManager.CALLBACK_LOST
+ | 1 << ConnectivityManager.CALLBACK_CAP_CHANGED
+ | 1 << ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED
+ | 1 << ConnectivityManager.CALLBACK_RESUMED
+ | 1 << ConnectivityManager.CALLBACK_BLK_CHANGED));
+ }
+
+ @Test
+ public void testDeclaredMethodsFlag_listenWithMixedMethods_RegistrationFlagsMatch()
+ throws Exception {
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+
+ final NetworkCallback callback2 = new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onLosing(@NonNull Network network, int maxMsToLive) {}
+ @Override
+ public void onUnavailable() {}
+ @Override
+ public void onLinkPropertiesChanged(@NonNull Network network,
+ @NonNull LinkProperties linkProperties) {}
+ @Override
+ public void onNetworkSuspended(@NonNull Network network) {}
+ };
+ manager.registerNetworkCallback(request, callback2);
+ // Call a second time with the same callback to exercise caching
+ manager.registerNetworkCallback(request, callback2);
+
+ verify(mService, times(2)).listenForNetwork(
+ any(), any(), any(), anyInt(), any(), any(),
+ eq(1 << ConnectivityManager.CALLBACK_LOSING
+ // AVAILABLE calls IP_CHANGED and SUSPENDED so it gets added
+ | 1 << ConnectivityManager.CALLBACK_AVAILABLE
+ | 1 << ConnectivityManager.CALLBACK_UNAVAIL
+ | 1 << ConnectivityManager.CALLBACK_IP_CHANGED
+ | 1 << ConnectivityManager.CALLBACK_SUSPENDED));
+ }
+
+ @Test
+ public void testDeclaredMethodsFlag_requestWithHiddenAvailableCallback_RegistrationFlagsMatch()
+ throws Exception {
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+
+ final NetworkCallback hiddenOnAvailableCb = new ConnectivityManager.NetworkCallback() {
+ // This overload is @hide but might still be used by (bad) apps
+ @Override
+ public void onAvailable(@NonNull Network network,
+ @NonNull NetworkCapabilities networkCapabilities,
+ @NonNull LinkProperties linkProperties, boolean blocked) {}
+ };
+ manager.registerDefaultNetworkCallback(hiddenOnAvailableCb);
+
+ verify(mService).requestNetwork(
+ anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(), any(), any(),
+ eq(1 << ConnectivityManager.CALLBACK_AVAILABLE));
+ }
+
+ public static class NetworkCallbackWithOnLostOnly extends NetworkCallback {
+ @Override
+ public void onLost(@NonNull Network network) {}
+ }
+
+ @Test
+ public void testDeclaredMethodsFlag_requestWithoutAvailableCallback_RegistrationFlagsMatch()
+ throws Exception {
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ final Handler handler = new Handler(Looper.getMainLooper());
+
+ final NetworkCallback noOnAvailableCb = new NetworkCallbackWithOnLostOnly();
+ manager.registerSystemDefaultNetworkCallback(noOnAvailableCb, handler);
+
+ verify(mService).requestNetwork(
+ anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(), any(), any(),
+ eq(1 << ConnectivityManager.CALLBACK_LOST));
+ }
+
+ @Test
+ public void testDeclaredMethodsFlag_listenWithMock_OptimizationDisabled()
+ throws Exception {
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ final Handler handler = new Handler(Looper.getMainLooper());
+
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ manager.registerNetworkCallback(request, mock(NetworkCallbackWithOnLostOnly.class),
+ handler);
+
+ verify(mService).listenForNetwork(
+ any(), any(), any(), anyInt(), any(), any(),
+ // Mock that does not call the constructor -> do not use the optimization
+ eq(~0));
+ }
+
+ @Test
+ public void testDeclaredMethodsFlag_requestWitNoCallback_OptimizationDisabled()
+ throws Exception {
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ final Handler handler = new Handler(Looper.getMainLooper());
+
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ final NetworkCallback noCallbackAtAll = new ConnectivityManager.NetworkCallback() {};
+ manager.requestBackgroundNetwork(request, noCallbackAtAll, handler);
+
+ verify(mService).requestNetwork(
+ anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(), any(), any(),
+ // No callbacks overridden -> do not use the optimization
+ eq(~0));
}
}
diff --git a/tests/unit/java/android/net/NetworkCallbackFlagsTest.kt b/tests/unit/java/android/net/NetworkCallbackFlagsTest.kt
new file mode 100644
index 0000000..af06a64
--- /dev/null
+++ b/tests/unit/java/android/net/NetworkCallbackFlagsTest.kt
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2024 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 android.net.ConnectivityManager.NetworkCallback
+import android.net.ConnectivityManager.NetworkCallbackMethodsHolder
+import android.os.Build
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import java.lang.reflect.Method
+import java.lang.reflect.Modifier
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.any
+import org.mockito.Mockito.doCallRealMethod
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.mockingDetails
+
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@RunWith(DevSdkIgnoreRunner::class)
+class NetworkCallbackFlagsTest {
+
+ // To avoid developers forgetting to update NETWORK_CB_METHODS when modifying NetworkCallbacks,
+ // or using wrong values, calculate it from annotations here and verify that it matches.
+ // This avoids the runtime cost of reflection, but still ensures that the list is correct.
+ @Test
+ fun testNetworkCallbackMethods_calculateFromAnnotations_matchesHardcodedList() {
+ val calculatedMethods = getNetworkCallbackMethodsFromAnnotations()
+ assertEquals(
+ calculatedMethods.toSet(),
+ NetworkCallbackMethodsHolder.NETWORK_CB_METHODS.map {
+ NetworkCallbackMethodWithEquals(
+ it.mName,
+ it.mParameterTypes.toList(),
+ callbacksCallingThisMethod = it.mCallbacksCallingThisMethod
+ )
+ }.toSet()
+ )
+ }
+
+ data class NetworkCallbackMethodWithEquals(
+ val name: String,
+ val parameterTypes: List<Class<*>>,
+ val callbacksCallingThisMethod: Int
+ )
+
+ data class NetworkCallbackMethodBuilder(
+ val name: String,
+ val parameterTypes: List<Class<*>>,
+ val isFinal: Boolean,
+ val methodId: Int,
+ val mayCall: Set<Int>?,
+ var callbacksCallingThisMethod: Int
+ ) {
+ fun build() = NetworkCallbackMethodWithEquals(
+ name,
+ parameterTypes,
+ callbacksCallingThisMethod
+ )
+ }
+
+ /**
+ * Build [NetworkCallbackMethodsHolder.NETWORK_CB_METHODS] from [NetworkCallback] annotations.
+ */
+ private fun getNetworkCallbackMethodsFromAnnotations(): List<NetworkCallbackMethodWithEquals> {
+ val parsedMethods = mutableListOf<NetworkCallbackMethodBuilder>()
+ val methods = NetworkCallback::class.java.declaredMethods
+ methods.forEach { method ->
+ val cb = method.getAnnotation(
+ NetworkCallback.FilteredCallback::class.java
+ ) ?: return@forEach
+ val callbacksCallingThisMethod = if (cb.calledByCallbackId == 0) {
+ 0
+ } else {
+ 1 shl cb.calledByCallbackId
+ }
+ parsedMethods.add(
+ NetworkCallbackMethodBuilder(
+ method.name,
+ method.parameterTypes.toList(),
+ Modifier.isFinal(method.modifiers),
+ cb.methodId,
+ cb.mayCall.toSet(),
+ callbacksCallingThisMethod
+ )
+ )
+ }
+
+ // Propagate callbacksCallingThisMethod for transitive calls
+ do {
+ var hadChange = false
+ parsedMethods.forEach { caller ->
+ parsedMethods.forEach { callee ->
+ if (caller.mayCall?.contains(callee.methodId) == true) {
+ // Callbacks that call the caller also cause calls to the callee. So
+ // callbacksCallingThisMethod for the callee should include
+ // callbacksCallingThisMethod from the caller.
+ val newValue =
+ caller.callbacksCallingThisMethod or callee.callbacksCallingThisMethod
+ hadChange = hadChange || callee.callbacksCallingThisMethod != newValue
+ callee.callbacksCallingThisMethod = newValue
+ }
+ }
+ }
+ } while (hadChange)
+
+ // Final methods may affect the flags for transitive calls, but cannot be overridden, so do
+ // not need to be in the list (no overridden method in NetworkCallback will match them).
+ return parsedMethods.filter { !it.isFinal }.map { it.build() }
+ }
+
+ @Test
+ fun testMethodsAreAnnotated() {
+ val annotations = NetworkCallback::class.java.declaredMethods.mapNotNull { method ->
+ if (!Modifier.isPublic(method.modifiers) && !Modifier.isProtected(method.modifiers)) {
+ return@mapNotNull null
+ }
+ val annotation = method.getAnnotation(NetworkCallback.FilteredCallback::class.java)
+ assertNotNull(annotation, "$method is missing the @FilteredCallback annotation")
+ return@mapNotNull annotation
+ }
+
+ annotations.groupingBy { it.methodId }.eachCount().forEach { (methodId, cnt) ->
+ assertEquals(1, cnt, "Method ID $methodId is used more than once in @FilteredCallback")
+ }
+ }
+
+ @Test
+ fun testObviousCalleesAreInAnnotation() {
+ NetworkCallback::class.java.declaredMethods.forEach { method ->
+ val annotation = method.getAnnotation(NetworkCallback.FilteredCallback::class.java)
+ ?: return@forEach
+ val missingFlags = getObviousCallees(method).toMutableSet().apply {
+ removeAll(annotation.mayCall.toSet())
+ }
+ val msg = "@FilteredCallback on $method is missing flags " +
+ "$missingFlags in mayCall. There may be other " +
+ "calls that are not detected if they are done conditionally."
+ assertEquals(emptySet(), missingFlags, msg)
+ }
+ }
+
+ /**
+ * Invoke the specified NetworkCallback method with mock arguments, return a set of transitively
+ * called methods.
+ *
+ * This provides an idea of which methods are transitively called by the specified method. It's
+ * not perfect as some callees could be called or not depending on the exact values of the mock
+ * arguments that are passed in (for example, onAvailable calls onNetworkSuspended only if the
+ * capabilities lack the NOT_SUSPENDED capability), but it should catch obvious forgotten calls.
+ */
+ private fun getObviousCallees(method: Method): Set<Int> {
+ // Create a mock NetworkCallback that mocks all methods except the one specified by the
+ // caller.
+ val mockCallback = mock(NetworkCallback::class.java)
+
+ if (!Modifier.isFinal(method.modifiers) ||
+ // The mock class will be NetworkCallback (not a subclass) if using mockito-inline,
+ // which mocks final methods too
+ mockCallback.javaClass == NetworkCallback::class.java) {
+ doCallRealMethod().`when`(mockCallback).let { mockObj ->
+ val anyArgs = method.parameterTypes.map { any(it) }
+ method.invoke(mockObj, *anyArgs.toTypedArray())
+ }
+ }
+
+ // Invoke the target method with mock parameters
+ val mockParameters = method.parameterTypes.map { getMockFor(method, it) }
+ method.invoke(mockCallback, *mockParameters.toTypedArray())
+
+ // Aggregate callees
+ val mockingDetails = mockingDetails(mockCallback)
+ return mockingDetails.invocations.mapNotNull { inv ->
+ if (inv.method == method) {
+ null
+ } else {
+ inv.method.getAnnotation(NetworkCallback.FilteredCallback::class.java)?.methodId
+ }
+ }.toSet()
+ }
+
+ private fun getMockFor(method: Method, c: Class<*>): Any {
+ if (!c.isPrimitive && !Modifier.isFinal(c.modifiers)) {
+ return mock(c)
+ }
+ return when (c) {
+ NetworkCapabilities::class.java -> NetworkCapabilities()
+ LinkProperties::class.java -> LinkProperties()
+ LocalNetworkInfo::class.java -> LocalNetworkInfo(null)
+ Boolean::class.java -> false
+ Int::class.java -> 0
+ else -> fail("No mock set for parameter type $c used in $method")
+ }
+ }
+}
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index 859c54a..c1c15ca 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -41,6 +41,7 @@
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_ADMIN_DISABLED;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
@@ -136,6 +137,12 @@
private static final int TEST_UID = 10086;
private static final int[] TEST_UIDS = {10002, 10003};
+ private static final int[] CORE_AIDS = {
+ Process.ROOT_UID,
+ Process.SYSTEM_UID,
+ Process.FIRST_APPLICATION_UID - 10,
+ Process.FIRST_APPLICATION_UID - 1,
+ };
private static final String TEST_IF_NAME = "wlan0";
private static final int TEST_IF_INDEX = 7;
private static final int NO_IIF = 0;
@@ -1261,15 +1268,9 @@
assertTrue(BpfNetMapsUtils.isUidNetworkingBlocked(TEST_UID, false, mConfigurationMap,
mUidOwnerMap, mDataSaverEnabledMap));
- final int[] coreAids = new int[] {
- Process.ROOT_UID,
- Process.SYSTEM_UID,
- Process.FIRST_APPLICATION_UID - 10,
- Process.FIRST_APPLICATION_UID - 1,
- };
// Core appIds are not on the chain but should still be allowed on any user.
for (int userId = 0; userId < 20; userId++) {
- for (final int aid : coreAids) {
+ for (final int aid : CORE_AIDS) {
final int uid = UserHandle.getUid(userId, aid);
assertFalse(BpfNetMapsUtils.isUidNetworkingBlocked(uid, false, mConfigurationMap,
mUidOwnerMap, mDataSaverEnabledMap));
@@ -1277,6 +1278,26 @@
}
}
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testGetUidNetworkingBlockedReasonsForCoreUids() throws Exception {
+ // Enable BACKGROUND_MATCH that is an allowlist match.
+ mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(BACKGROUND_MATCH));
+
+ // Non-core uid that is not on this chain is blocked by BLOCKED_REASON_APP_BACKGROUND.
+ assertEquals(BLOCKED_REASON_APP_BACKGROUND, BpfNetMapsUtils.getUidNetworkingBlockedReasons(
+ TEST_UID, mConfigurationMap, mUidOwnerMap, mDataSaverEnabledMap));
+
+ // Core appIds are not on the chain but should not be blocked on any users.
+ for (int userId = 0; userId < 20; userId++) {
+ for (final int aid : CORE_AIDS) {
+ final int uid = UserHandle.getUid(userId, aid);
+ assertEquals(BLOCKED_REASON_NONE, BpfNetMapsUtils.getUidNetworkingBlockedReasons(
+ uid, mConfigurationMap, mUidOwnerMap, mDataSaverEnabledMap));
+ }
+ }
+ }
+
private void doTestIsUidRestrictedOnMeteredNetworks(
final long enabledMatches,
final long uidRules,
diff --git a/tests/unit/java/com/android/server/CallbackQueueTest.kt b/tests/unit/java/com/android/server/CallbackQueueTest.kt
new file mode 100644
index 0000000..d8d35c1
--- /dev/null
+++ b/tests/unit/java/com/android/server/CallbackQueueTest.kt
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2024 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
+
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.CALLBACK_AVAILABLE
+import android.net.ConnectivityManager.CALLBACK_CAP_CHANGED
+import android.net.ConnectivityManager.CALLBACK_IP_CHANGED
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import java.lang.reflect.Modifier
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TEST_NETID_1 = 123
+
+// Maximum 16 bits unsigned value
+private const val TEST_NETID_2 = 0xffff
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+class CallbackQueueTest {
+ @Test
+ fun testAddCallback() {
+ val cbs = listOf(
+ TEST_NETID_1 to CALLBACK_AVAILABLE,
+ TEST_NETID_2 to CALLBACK_AVAILABLE,
+ TEST_NETID_1 to CALLBACK_CAP_CHANGED,
+ TEST_NETID_1 to CALLBACK_CAP_CHANGED
+ )
+ val queue = CallbackQueue(intArrayOf()).apply {
+ cbs.forEach { addCallback(it.first, it.second) }
+ }
+
+ assertQueueEquals(cbs, queue)
+ }
+
+ @Test
+ fun testHasCallback() {
+ val queue = CallbackQueue(intArrayOf()).apply {
+ addCallback(TEST_NETID_1, CALLBACK_AVAILABLE)
+ addCallback(TEST_NETID_2, CALLBACK_AVAILABLE)
+ addCallback(TEST_NETID_1, CALLBACK_CAP_CHANGED)
+ addCallback(TEST_NETID_1, CALLBACK_CAP_CHANGED)
+ }
+
+ assertTrue(queue.hasCallback(TEST_NETID_1, CALLBACK_AVAILABLE))
+ assertTrue(queue.hasCallback(TEST_NETID_2, CALLBACK_AVAILABLE))
+ assertTrue(queue.hasCallback(TEST_NETID_1, CALLBACK_CAP_CHANGED))
+
+ assertFalse(queue.hasCallback(TEST_NETID_2, CALLBACK_CAP_CHANGED))
+ assertFalse(queue.hasCallback(1234, CALLBACK_AVAILABLE))
+ assertFalse(queue.hasCallback(TEST_NETID_1, 5678))
+ assertFalse(queue.hasCallback(1234, 5678))
+ }
+
+ @Test
+ fun testRemoveCallbacks() {
+ val queue = CallbackQueue(intArrayOf()).apply {
+ assertFalse(removeCallbacks(TEST_NETID_1, CALLBACK_AVAILABLE))
+ addCallback(TEST_NETID_1, CALLBACK_AVAILABLE)
+ addCallback(TEST_NETID_1, CALLBACK_CAP_CHANGED)
+ addCallback(TEST_NETID_2, CALLBACK_AVAILABLE)
+ addCallback(TEST_NETID_1, CALLBACK_AVAILABLE)
+ assertTrue(removeCallbacks(TEST_NETID_1, CALLBACK_AVAILABLE))
+ }
+ assertQueueEquals(listOf(
+ TEST_NETID_1 to CALLBACK_CAP_CHANGED,
+ TEST_NETID_2 to CALLBACK_AVAILABLE
+ ), queue)
+ }
+
+ @Test
+ fun testRemoveCallbacksForNetId() {
+ val queue = CallbackQueue(intArrayOf()).apply {
+ assertFalse(removeCallbacksForNetId(TEST_NETID_2))
+ addCallback(TEST_NETID_2, CALLBACK_AVAILABLE)
+ assertFalse(removeCallbacksForNetId(TEST_NETID_1))
+ addCallback(TEST_NETID_1, CALLBACK_AVAILABLE)
+ addCallback(TEST_NETID_1, CALLBACK_CAP_CHANGED)
+ addCallback(TEST_NETID_2, CALLBACK_CAP_CHANGED)
+ addCallback(TEST_NETID_1, CALLBACK_AVAILABLE)
+ addCallback(TEST_NETID_2, CALLBACK_IP_CHANGED)
+ assertTrue(removeCallbacksForNetId(TEST_NETID_2))
+ }
+ assertQueueEquals(listOf(
+ TEST_NETID_1 to CALLBACK_AVAILABLE,
+ TEST_NETID_1 to CALLBACK_CAP_CHANGED,
+ TEST_NETID_1 to CALLBACK_AVAILABLE,
+ ), queue)
+ }
+
+ @Test
+ fun testConstructorFromExistingArray() {
+ val queue1 = CallbackQueue(intArrayOf()).apply {
+ addCallback(TEST_NETID_1, CALLBACK_AVAILABLE)
+ addCallback(TEST_NETID_2, CALLBACK_AVAILABLE)
+ }
+ val queue2 = CallbackQueue(queue1.minimizedBackingArray)
+ assertQueueEquals(listOf(
+ TEST_NETID_1 to CALLBACK_AVAILABLE,
+ TEST_NETID_2 to CALLBACK_AVAILABLE
+ ), queue2)
+ }
+
+ @Test
+ fun testToString() {
+ assertEquals("[]", CallbackQueue(intArrayOf()).toString())
+ assertEquals(
+ "[CALLBACK_AVAILABLE($TEST_NETID_1)]",
+ CallbackQueue(intArrayOf()).apply {
+ addCallback(TEST_NETID_1, CALLBACK_AVAILABLE)
+ }.toString()
+ )
+ assertEquals(
+ "[CALLBACK_AVAILABLE($TEST_NETID_1),CALLBACK_CAP_CHANGED($TEST_NETID_2)]",
+ CallbackQueue(intArrayOf()).apply {
+ addCallback(TEST_NETID_1, CALLBACK_AVAILABLE)
+ addCallback(TEST_NETID_2, CALLBACK_CAP_CHANGED)
+ }.toString()
+ )
+ }
+
+ @Test
+ fun testMaxNetId() {
+ // CallbackQueue assumes netIds are at most 16 bits
+ assertTrue(NetIdManager.MAX_NET_ID <= 0xffff)
+ }
+
+ @Test
+ fun testMaxCallbackId() {
+ // CallbackQueue assumes callback IDs are at most 16 bits.
+ val constants = ConnectivityManager::class.java.declaredFields.filter {
+ Modifier.isStatic(it.modifiers) && Modifier.isFinal(it.modifiers) &&
+ it.name.startsWith("CALLBACK_")
+ }
+ constants.forEach {
+ it.isAccessible = true
+ assertTrue(it.get(null) as Int <= 0xffff)
+ }
+ }
+}
+
+private fun assertQueueEquals(expected: List<Pair<Int, Int>>, actual: CallbackQueue) {
+ assertEquals(
+ expected.size,
+ actual.length(),
+ "Size mismatch between expected: $expected and actual: $actual"
+ )
+
+ var nextIndex = 0
+ actual.forEach { netId, cbId ->
+ val (expNetId, expCbId) = expected[nextIndex]
+ val msg = "$actual does not match $expected at index $nextIndex"
+ assertEquals(expNetId, netId, msg)
+ assertEquals(expCbId, cbId, msg)
+ nextIndex++
+ }
+ // Ensure forEach iterations and size are consistent
+ assertEquals(expected.size, nextIndex)
+}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 8526a9a..999d17d 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -2180,6 +2180,7 @@
case ConnectivityFlags.CARRIER_SERVICE_CHANGED_USE_CALLBACK:
case ConnectivityFlags.REQUEST_RESTRICTED_WIFI:
case ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS:
+ case ConnectivityFlags.QUEUE_CALLBACKS_FOR_FROZEN_APPS:
case KEY_DESTROY_FROZEN_SOCKETS_VERSION:
return true;
default:
@@ -2871,7 +2872,7 @@
};
final NetworkRequest request = mService.listenForNetwork(caps, messenger, binder,
NetworkCallback.FLAG_NONE, mContext.getOpPackageName(),
- mContext.getAttributionTag());
+ mContext.getAttributionTag(), ~0 /* declaredMethodsFlag */);
mService.releaseNetworkRequest(request);
deathRecipient.get().binderDied();
// Wait for the release message to be processed.
@@ -5407,7 +5408,7 @@
mService.requestNetwork(Process.INVALID_UID, networkCapabilities,
NetworkRequest.Type.REQUEST.ordinal(), null, 0, null,
ConnectivityManager.TYPE_WIFI, NetworkCallback.FLAG_NONE,
- mContext.getPackageName(), getAttributionTag());
+ mContext.getPackageName(), getAttributionTag(), ~0 /* declaredMethodsFlag */);
});
final NetworkRequest.Builder builder =
@@ -13655,7 +13656,8 @@
IllegalArgumentException.class,
() -> mService.requestNetwork(Process.INVALID_UID, nc, reqTypeInt, null, 0,
null, ConnectivityManager.TYPE_NONE, NetworkCallback.FLAG_NONE,
- mContext.getPackageName(), getAttributionTag())
+ mContext.getPackageName(), getAttributionTag(),
+ ~0 /* declaredMethodsFlag */)
);
}
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
index fb3d183..4c71991 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
@@ -18,8 +18,10 @@
import static com.android.server.connectivity.mdns.MdnsSocketProvider.SocketCallback;
import static com.android.server.connectivity.mdns.MulticastPacketReader.PacketHandler;
+import static com.android.testutils.Cleanup.testAndCleanup;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
@@ -35,6 +37,7 @@
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
+import android.util.Log;
import com.android.net.module.util.HexDump;
import com.android.net.module.util.SharedLog;
@@ -59,6 +62,7 @@
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
@RunWith(DevSdkIgnoreRunner.class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
@@ -437,4 +441,34 @@
inOrder.verify(mSocket).send(packets.get(i));
}
}
+
+ @Test
+ public void testSendPacketWithMultiplePacketsWithDifferentAddresses() throws IOException {
+ final SocketCallback callback = expectSocketCallback();
+ final DatagramPacket ipv4Packet = new DatagramPacket(BUFFER, 0 /* offset */, BUFFER.length,
+ InetAddresses.parseNumericAddress("192.0.2.1"), 0 /* port */);
+ final DatagramPacket ipv6Packet = new DatagramPacket(BUFFER, 0 /* offset */, BUFFER.length,
+ InetAddresses.parseNumericAddress("2001:db8::"), 0 /* port */);
+ doReturn(true).when(mSocket).hasJoinedIpv4();
+ doReturn(true).when(mSocket).hasJoinedIpv6();
+ doReturn(createEmptyNetworkInterface()).when(mSocket).getInterface();
+
+ // Notify socket created
+ callback.onSocketCreated(mSocketKey, mSocket, List.of());
+ verify(mSocketCreationCallback).onSocketCreated(mSocketKey);
+
+ // Send packets with IPv4 and IPv6 then verify wtf logs and sending has never been called.
+ // Override the default TerribleFailureHandler, as that handler might terminate the process
+ // (if we're on an eng build).
+ final AtomicBoolean hasFailed = new AtomicBoolean(false);
+ final Log.TerribleFailureHandler originalHandler =
+ Log.setWtfHandler((tag, what, system) -> hasFailed.set(true));
+ testAndCleanup(() -> {
+ mSocketClient.sendPacketRequestingMulticastResponse(List.of(ipv4Packet, ipv6Packet),
+ mSocketKey, false /* onlyUseIpv6OnIpv6OnlyNetworks */);
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ assertTrue(hasFailed.get());
+ verify(mSocket, never()).send(any());
+ }, () -> Log.setWtfHandler(originalHandler));
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
index 2cb97c9..9674da3 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
@@ -123,18 +123,18 @@
}
private val TEST_PUBLIC_KEY = hexStringToByteArray(
- "0201030dc141d0637960b98cbc12cfca"
- + "221d2879dac26ee5b460e9007c992e19"
- + "02d897c391b03764d448f7d0c772fdb0"
- + "3b1d9d6d52ff8886769e8e2362513565"
- + "270962d3")
+ "0201030dc141d0637960b98cbc12cfca" +
+ "221d2879dac26ee5b460e9007c992e19" +
+ "02d897c391b03764d448f7d0c772fdb0" +
+ "3b1d9d6d52ff8886769e8e2362513565" +
+ "270962d3")
private val TEST_PUBLIC_KEY_2 = hexStringToByteArray(
- "0201030dc141d0637960b98cbc12cfca"
- + "221d2879dac26ee5b460e9007c992e19"
- + "02d897c391b03764d448f7d0c772fdb0"
- + "3b1d9d6d52ff8886769e8e2362513565"
- + "270962d4")
+ "0201030dc141d0637960b98cbc12cfca" +
+ "221d2879dac26ee5b460e9007c992e19" +
+ "02d897c391b03764d448f7d0c772fdb0" +
+ "3b1d9d6d52ff8886769e8e2362513565" +
+ "270962d4")
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
@@ -159,7 +159,7 @@
@Before
fun setUp() {
- deps.resetElapsedRealTime();
+ deps.resetElapsedRealTime()
thread.start()
}
@@ -172,11 +172,13 @@
private fun makeFlags(
includeInetAddressesInProbing: Boolean = false,
isKnownAnswerSuppressionEnabled: Boolean = false,
- unicastReplyEnabled: Boolean = true
+ unicastReplyEnabled: Boolean = true,
+ avoidAdvertisingEmptyTxtRecords: Boolean = true
) = MdnsFeatureFlags.Builder()
.setIncludeInetAddressRecordsInProbing(includeInetAddressesInProbing)
.setIsKnownAnswerSuppressionEnabled(isKnownAnswerSuppressionEnabled)
.setIsUnicastReplyEnabled(unicastReplyEnabled)
+ .setAvoidAdvertisingEmptyTxtRecords(avoidAdvertisingEmptyTxtRecords)
.build()
@Test
@@ -1721,6 +1723,30 @@
}
@Test
+ fun testGetConflictingServices_ZeroLengthTxtRecord_NoConflict() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.addServiceAndFinishProbing(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+
+ val packet = MdnsPacket(
+ 0 /* flags */,
+ emptyList() /* questions */,
+ listOf(
+ MdnsTextRecord(
+ arrayOf("MyOtherTestService", "_testservice", "_tcp", "local"),
+ 0L /* receiptTimeMillis */,
+ true /* cacheFlush */,
+ SHORT_TTL,
+ listOf(TextEntry("", null as ByteArray?))
+ ),
+ ) /* answers */,
+ emptyList() /* authorityRecords */,
+ emptyList() /* additionalRecords */
+ )
+
+ assertEquals(emptyMap(), repository.getConflictingServices(packet))
+ }
+
+ @Test
fun testGetServiceRepliedRequestsCount() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
@@ -2168,6 +2194,46 @@
assertEquals(knownAnswers, reply.knownAnswers)
}
+ private fun doAddServiceWithEmptyTxtRecordTest(flags: MdnsFeatureFlags): MdnsTextRecord {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
+ repository.addServiceAndFinishProbing(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+
+ val questions = listOf(MdnsTextRecord(
+ arrayOf("MyTestService", "_testservice", "_tcp", "local"),
+ true /* isUnicast */
+ ))
+ val query = MdnsPacket(
+ 0 /* flags */,
+ questions,
+ emptyList() /* answers */,
+ emptyList() /* authorityRecords */,
+ emptyList() /* additionalRecords */
+ )
+ val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
+ val reply = repository.getReply(query, src)
+
+ assertNotNull(reply)
+ assertEquals(1, reply.answers.size)
+ assertTrue(reply.answers[0] is MdnsTextRecord)
+ return reply.answers[0] as MdnsTextRecord
+ }
+
+ @Test
+ fun testAddService_AvoidEmptyTxtRecords_HasTxtRecordWithEmptyString() {
+ val answerRecord = doAddServiceWithEmptyTxtRecordTest(makeFlags())
+ assertEquals(1, answerRecord.entries.size)
+ assertEquals(0, answerRecord.entries[0].key.length)
+ assertNull(answerRecord.entries[0].value)
+ }
+
+ @Test
+ fun testAddService_UsesEmptyTxtRecords_HasEmptyTxtRecord() {
+ val answerRecord = doAddServiceWithEmptyTxtRecordTest(makeFlags(
+ avoidAdvertisingEmptyTxtRecords = false
+ ))
+ assertEquals(0, answerRecord.entries.size)
+ }
+
@Test
fun testRestartProbingForHostname() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTests.java
index 63548c1..784c502 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTests.java
@@ -28,6 +28,8 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import static java.util.Collections.emptyList;
+
import android.util.Log;
import com.android.net.module.util.HexDump;
@@ -372,6 +374,30 @@
assertEquals(dataInText, dataOutText);
}
+ private static MdnsTextRecord makeTextRecordWithEntries(List<TextEntry> entries) {
+ return new MdnsTextRecord(new String[] { "test", "record" }, 0L /* receiptTimeMillis */,
+ true /* cacheFlush */, 120_000L /* ttlMillis */, entries);
+ }
+
+ @Test
+ public void testTextRecord_EmptyRecordsAreEquivalent() {
+ final MdnsTextRecord record1 = makeTextRecordWithEntries(emptyList());
+ final MdnsTextRecord record2 = makeTextRecordWithEntries(
+ List.of(new TextEntry("", (byte[]) null)));
+ final MdnsTextRecord record3 = makeTextRecordWithEntries(
+ List.of(new TextEntry(null, (byte[]) null)));
+ final MdnsTextRecord nonEmptyRecord = makeTextRecordWithEntries(
+ List.of(new TextEntry("a", (byte[]) null)));
+
+ assertEquals(record1, record1);
+ assertEquals(record1, record2);
+ assertEquals(record1, record3);
+
+ assertNotEquals(nonEmptyRecord, record1);
+ assertNotEquals(nonEmptyRecord, record2);
+ assertNotEquals(nonEmptyRecord, record3);
+ }
+
private static String toHex(MdnsRecord record) throws IOException {
MdnsPacketWriter writer = new MdnsPacketWriter(MAX_PACKET_SIZE);
record.write(writer, record.getReceiptTime());
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
index b040ab6..0a8f108 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
@@ -154,6 +154,11 @@
serviceCache.registerServiceExpiredCallback(cacheKey, callback)
}
+ private fun removeServices(
+ serviceCache: MdnsServiceCache,
+ cacheKey: CacheKey
+ ): Unit = runningOnHandlerAndReturn { serviceCache.removeServices(cacheKey) }
+
@Test
fun testAddAndRemoveService() {
val serviceCache = MdnsServiceCache(thread.looper, makeFlags(), clock)
@@ -291,6 +296,37 @@
assertEquals(response4, responses[3])
}
+ @Test
+ fun testRemoveServices() {
+ val serviceCache = MdnsServiceCache(thread.looper, makeFlags(), clock)
+ addOrUpdateService(serviceCache, cacheKey1, createResponse(SERVICE_NAME_1, SERVICE_TYPE_1))
+ addOrUpdateService(serviceCache, cacheKey1, createResponse(SERVICE_NAME_2, SERVICE_TYPE_1))
+ addOrUpdateService(serviceCache, cacheKey2, createResponse(SERVICE_NAME_1, SERVICE_TYPE_2))
+ val responses1 = getServices(serviceCache, cacheKey1)
+ assertEquals(2, responses1.size)
+ assertTrue(responses1.stream().anyMatch { response ->
+ response.serviceInstanceName == SERVICE_NAME_1
+ })
+ assertTrue(responses1.any { response ->
+ response.serviceInstanceName == SERVICE_NAME_2
+ })
+ val responses2 = getServices(serviceCache, cacheKey2)
+ assertEquals(1, responses2.size)
+ assertTrue(responses2.stream().anyMatch { response ->
+ response.serviceInstanceName == SERVICE_NAME_1
+ })
+
+ removeServices(serviceCache, cacheKey1)
+ val responses3 = getServices(serviceCache, cacheKey1)
+ assertEquals(0, responses3.size)
+ val responses4 = getServices(serviceCache, cacheKey2)
+ assertEquals(1, responses4.size)
+
+ removeServices(serviceCache, cacheKey2)
+ val responses5 = getServices(serviceCache, cacheKey2)
+ assertEquals(0, responses5.size)
+ }
+
private fun createResponse(
serviceInstanceName: String,
serviceType: String,
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index 44fa55c..569f4d7 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -562,10 +562,7 @@
//MdnsConfigsFlagsImpl.alwaysAskForUnicastResponseInEachBurst.override(true);
MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
.addSubtype(SUBTYPE).setQueryMode(ACTIVE_QUERY_MODE).build();
- QueryTaskConfig config = new QueryTaskConfig(
- searchOptions.getQueryMode(),
- false /* onlyUseIpv6OnIpv6OnlyNetworks */, 3 /* numOfQueriesBeforeBackoff */,
- socketKey);
+ QueryTaskConfig config = new QueryTaskConfig(searchOptions.getQueryMode());
// This is the first query. We will ask for unicast response.
assertTrue(config.expectUnicastResponse);
@@ -574,14 +571,14 @@
// For the rest of queries in this burst, we will NOT ask for unicast response.
for (int i = 1; i < MdnsConfigs.queriesPerBurst(); i++) {
int oldTransactionId = config.transactionId;
- config = config.getConfigForNextRun();
+ config = config.getConfigForNextRun(ACTIVE_QUERY_MODE);
assertFalse(config.expectUnicastResponse);
assertEquals(config.transactionId, oldTransactionId + 1);
}
// This is the first query of a new burst. We will ask for unicast response.
int oldTransactionId = config.transactionId;
- config = config.getConfigForNextRun();
+ config = config.getConfigForNextRun(ACTIVE_QUERY_MODE);
assertTrue(config.expectUnicastResponse);
assertEquals(config.transactionId, oldTransactionId + 1);
}
@@ -590,10 +587,7 @@
public void testQueryTaskConfig_askForUnicastInFirstQuery() {
MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
.addSubtype(SUBTYPE).setQueryMode(ACTIVE_QUERY_MODE).build();
- QueryTaskConfig config = new QueryTaskConfig(
- searchOptions.getQueryMode(),
- false /* onlyUseIpv6OnIpv6OnlyNetworks */, 3 /* numOfQueriesBeforeBackoff */,
- socketKey);
+ QueryTaskConfig config = new QueryTaskConfig(searchOptions.getQueryMode());
// This is the first query. We will ask for unicast response.
assertTrue(config.expectUnicastResponse);
@@ -602,14 +596,14 @@
// For the rest of queries in this burst, we will NOT ask for unicast response.
for (int i = 1; i < MdnsConfigs.queriesPerBurst(); i++) {
int oldTransactionId = config.transactionId;
- config = config.getConfigForNextRun();
+ config = config.getConfigForNextRun(ACTIVE_QUERY_MODE);
assertFalse(config.expectUnicastResponse);
assertEquals(config.transactionId, oldTransactionId + 1);
}
// This is the first query of a new burst. We will NOT ask for unicast response.
int oldTransactionId = config.transactionId;
- config = config.getConfigForNextRun();
+ config = config.getConfigForNextRun(ACTIVE_QUERY_MODE);
assertFalse(config.expectUnicastResponse);
assertEquals(config.transactionId, oldTransactionId + 1);
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
index 1989ed3..ab70e38 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
@@ -16,6 +16,7 @@
package com.android.server.connectivity.mdns;
+import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertFalse;
@@ -38,9 +39,11 @@
import android.annotation.RequiresPermission;
import android.content.Context;
import android.net.ConnectivityManager;
+import android.net.InetAddresses;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.MulticastLock;
import android.text.format.DateUtils;
+import android.util.Log;
import com.android.net.module.util.HexDump;
import com.android.net.module.util.SharedLog;
@@ -594,6 +597,29 @@
}
}
+ @Test
+ public void testSendPacketWithMultiplePacketsWithDifferentAddresses() throws IOException {
+ mdnsClient.startDiscovery();
+ final byte[] buffer = new byte[10];
+ final DatagramPacket ipv4Packet = new DatagramPacket(buffer, 0 /* offset */, buffer.length,
+ InetAddresses.parseNumericAddress("192.0.2.1"), 0 /* port */);
+ final DatagramPacket ipv6Packet = new DatagramPacket(buffer, 0 /* offset */, buffer.length,
+ InetAddresses.parseNumericAddress("2001:db8::"), 0 /* port */);
+
+ // Send packets with IPv4 and IPv6 then verify wtf logs and sending has never been called.
+ // Override the default TerribleFailureHandler, as that handler might terminate the process
+ // (if we're on an eng build).
+ final AtomicBoolean hasFailed = new AtomicBoolean(false);
+ final Log.TerribleFailureHandler originalHandler =
+ Log.setWtfHandler((tag, what, system) -> hasFailed.set(true));
+ testAndCleanup(() -> {
+ mdnsClient.sendPacketRequestingMulticastResponse(List.of(ipv4Packet, ipv6Packet),
+ false /* onlyUseIpv6OnIpv6OnlyNetworks */);
+ assertTrue(hasFailed.get());
+ verify(mockMulticastSocket, never()).send(any());
+ }, () -> Log.setWtfHandler(originalHandler));
+ }
+
private DatagramPacket getTestDatagramPacket() {
return new DatagramPacket(buf, 0, 5,
new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), 5353 /* port */));
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
index 009205e..5c3ad22 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
@@ -16,24 +16,22 @@
package com.android.server.connectivity.mdns.util
+import android.net.InetAddresses
import android.os.Build
import com.android.server.connectivity.mdns.MdnsConstants
import com.android.server.connectivity.mdns.MdnsConstants.FLAG_TRUNCATED
+import com.android.server.connectivity.mdns.MdnsConstants.IPV4_SOCKET_ADDR
+import com.android.server.connectivity.mdns.MdnsConstants.IPV6_SOCKET_ADDR
import com.android.server.connectivity.mdns.MdnsPacket
import com.android.server.connectivity.mdns.MdnsPacketReader
import com.android.server.connectivity.mdns.MdnsPointerRecord
import com.android.server.connectivity.mdns.MdnsRecord
import com.android.server.connectivity.mdns.util.MdnsUtils.createQueryDatagramPackets
-import com.android.server.connectivity.mdns.util.MdnsUtils.equalsDnsLabelIgnoreDnsCase
-import com.android.server.connectivity.mdns.util.MdnsUtils.equalsIgnoreDnsCase
-import com.android.server.connectivity.mdns.util.MdnsUtils.toDnsLabelsLowerCase
-import com.android.server.connectivity.mdns.util.MdnsUtils.toDnsLowerCase
import com.android.server.connectivity.mdns.util.MdnsUtils.truncateServiceName
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
import java.net.DatagramPacket
import kotlin.test.assertContentEquals
-import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -43,59 +41,6 @@
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
class MdnsUtilsTest {
- @Test
- fun testToDnsLowerCase() {
- assertEquals("test", toDnsLowerCase("TEST"))
- assertEquals("test", toDnsLowerCase("TeSt"))
- assertEquals("test", toDnsLowerCase("test"))
- assertEquals("tÉst", toDnsLowerCase("TÉST"))
- assertEquals("ţést", toDnsLowerCase("ţést"))
- // Unicode characters 0x10000 (𐀀), 0x10001 (𐀁), 0x10041 (𐁁)
- // Note the last 2 bytes of 0x10041 are identical to 'A', but it should remain unchanged.
- assertEquals(
- "test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- ",
- toDnsLowerCase("Test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- ")
- )
- // Also test some characters where the first surrogate is not \ud800
- assertEquals(
- "test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
- "\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<",
- toDnsLowerCase(
- "Test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
- "\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<"
- )
- )
- }
-
- @Test
- fun testToDnsLabelsLowerCase() {
- assertArrayEquals(
- arrayOf("test", "tÉst", "ţést"),
- toDnsLabelsLowerCase(arrayOf("TeSt", "TÉST", "ţést"))
- )
- }
-
- @Test
- fun testEqualsIgnoreDnsCase() {
- assertTrue(equalsIgnoreDnsCase("TEST", "Test"))
- assertTrue(equalsIgnoreDnsCase("TEST", "test"))
- assertTrue(equalsIgnoreDnsCase("test", "TeSt"))
- assertTrue(equalsIgnoreDnsCase("Tést", "tést"))
- assertFalse(equalsIgnoreDnsCase("ŢÉST", "ţést"))
- // Unicode characters 0x10000 (𐀀), 0x10001 (𐀁), 0x10041 (𐁁)
- // Note the last 2 bytes of 0x10041 are identical to 'A', but it should remain unchanged.
- assertTrue(equalsIgnoreDnsCase(
- "test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- ",
- "Test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- "
- ))
- // Also test some characters where the first surrogate is not \ud800
- assertTrue(equalsIgnoreDnsCase(
- "test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
- "\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<",
- "Test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
- "\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<"
- ))
- }
@Test
fun testTruncateServiceName() {
@@ -104,14 +49,6 @@
}
@Test
- fun testEqualsLabelIgnoreDnsCase() {
- assertTrue(equalsDnsLabelIgnoreDnsCase(arrayOf("TEST", "Test"), arrayOf("test", "test")))
- assertFalse(equalsDnsLabelIgnoreDnsCase(arrayOf("TEST", "Test"), arrayOf("test")))
- assertFalse(equalsDnsLabelIgnoreDnsCase(arrayOf("Test"), arrayOf("test", "test")))
- assertFalse(equalsDnsLabelIgnoreDnsCase(arrayOf("TEST", "Test"), arrayOf("test", "tést")))
- }
-
- @Test
fun testTypeEqualsOrIsSubtype() {
assertTrue(MdnsUtils.typeEqualsOrIsSubtype(
arrayOf("_type", "_tcp", "local"),
@@ -193,4 +130,31 @@
}
return MdnsPacket(flags, questions, answers, emptyList(), emptyList())
}
+
+ @Test
+ fun testCheckAllPacketsWithSameAddress() {
+ val buffer = ByteArray(10)
+ val v4Packet = DatagramPacket(buffer, buffer.size, IPV4_SOCKET_ADDR)
+ val otherV4Packet = DatagramPacket(
+ buffer,
+ buffer.size,
+ InetAddresses.parseNumericAddress("192.0.2.1"),
+ 1234
+ )
+ val v6Packet = DatagramPacket(ByteArray(10), 10, IPV6_SOCKET_ADDR)
+ val otherV6Packet = DatagramPacket(
+ buffer,
+ buffer.size,
+ InetAddresses.parseNumericAddress("2001:db8::"),
+ 1234
+ )
+ assertTrue(MdnsUtils.checkAllPacketsWithSameAddress(listOf()))
+ assertTrue(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v4Packet)))
+ assertTrue(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v4Packet, v4Packet)))
+ assertFalse(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v4Packet, otherV4Packet)))
+ assertTrue(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v6Packet)))
+ assertTrue(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v6Packet, v6Packet)))
+ assertFalse(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v6Packet, otherV6Packet)))
+ assertFalse(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v4Packet, v6Packet)))
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt
index 3ad8de8..985d403 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt
@@ -419,4 +419,30 @@
deps.setChangeIdEnabled(true, NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION)
doTestBlockedReasonsNoInternetPermission(blockedByNoInternetPermission = true)
}
+
+ private fun doTestEnforceMeteredApnPolicy(restricted: Boolean) {
+ doReturn(restricted).`when`(bpfNetMaps).isUidRestrictedOnMeteredNetworks(Process.myUid())
+
+ val cellAgent = Agent(nc = cellNc())
+ cellAgent.connect()
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(cellRequest(), cb)
+
+ if (restricted) {
+ waitForIdle()
+ cb.assertNoCallback()
+ } else {
+ cb.expectAvailableCallbacks(cellAgent.network, validated = false)
+ }
+ }
+
+ @Test
+ fun testEnforceMeteredApnPolicy_restricted() {
+ doTestEnforceMeteredApnPolicy(restricted = true)
+ }
+
+ @Test
+ fun testEnforceMeteredApnPolicy_notRestricted() {
+ doTestEnforceMeteredApnPolicy(restricted = false)
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt
new file mode 100644
index 0000000..a7083dc
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2024 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.connectivityservice
+
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.CALLBACK_AVAILABLE
+import android.net.ConnectivityManager.CALLBACK_BLK_CHANGED
+import android.net.ConnectivityManager.CALLBACK_CAP_CHANGED
+import android.net.ConnectivityManager.CALLBACK_IP_CHANGED
+import android.net.ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED
+import android.net.ConnectivityManager.CALLBACK_LOST
+import android.net.ConnectivityManager.NetworkCallback.DECLARED_METHODS_ALL
+import android.net.LinkAddress
+import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
+import android.net.NetworkRequest
+import android.os.Build
+import com.android.net.module.util.BitUtils.packBits
+import com.android.server.CSTest
+import com.android.server.ConnectivityService
+import com.android.server.defaultLp
+import com.android.server.defaultNc
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry
+import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.tryTest
+import java.lang.reflect.Modifier
+import java.util.concurrent.atomic.AtomicInteger
+import kotlin.test.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.spy
+
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner::class)
+class CSDeclaredMethodsForCallbacksTest : CSTest() {
+ private val mockedCallbackFlags = AtomicInteger(DECLARED_METHODS_ALL)
+ private lateinit var wrappedService: ConnectivityService
+
+ private val instrumentedCm by lazy { ConnectivityManager(context, wrappedService) }
+
+ @Before
+ fun setUpWrappedService() {
+ // Mock the callback flags set by ConnectivityManager when calling ConnectivityService, to
+ // simulate methods not being overridden
+ wrappedService = spy(service)
+ doAnswer { inv ->
+ service.requestNetwork(
+ inv.getArgument(0),
+ inv.getArgument(1),
+ inv.getArgument(2),
+ inv.getArgument(3),
+ inv.getArgument(4),
+ inv.getArgument(5),
+ inv.getArgument(6),
+ inv.getArgument(7),
+ inv.getArgument(8),
+ inv.getArgument(9),
+ mockedCallbackFlags.get())
+ }.`when`(wrappedService).requestNetwork(
+ anyInt(),
+ any(),
+ anyInt(),
+ any(),
+ anyInt(),
+ any(),
+ anyInt(),
+ anyInt(),
+ any(),
+ any(),
+ anyInt()
+ )
+ doAnswer { inv ->
+ service.listenForNetwork(
+ inv.getArgument(0),
+ inv.getArgument(1),
+ inv.getArgument(2),
+ inv.getArgument(3),
+ inv.getArgument(4),
+ inv.getArgument(5),
+ mockedCallbackFlags.get()
+ )
+ }.`when`(wrappedService)
+ .listenForNetwork(any(), any(), any(), anyInt(), any(), any(), anyInt())
+ }
+
+ @Test
+ fun testCallbacksAreFiltered() {
+ val requestCb = TestableNetworkCallback()
+ val listenCb = TestableNetworkCallback()
+ mockedCallbackFlags.withFlags(CALLBACK_IP_CHANGED, CALLBACK_LOST) {
+ instrumentedCm.requestNetwork(NetworkRequest.Builder().build(), requestCb)
+ }
+ mockedCallbackFlags.withFlags(CALLBACK_CAP_CHANGED) {
+ instrumentedCm.registerNetworkCallback(NetworkRequest.Builder().build(), listenCb)
+ }
+
+ with(Agent()) {
+ connect()
+ sendLinkProperties(defaultLp().apply {
+ addLinkAddress(LinkAddress("fe80:db8::123/64"))
+ })
+ sendNetworkCapabilities(defaultNc().apply {
+ addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+ })
+ disconnect()
+ }
+ waitForIdle()
+
+ // Only callbacks for the corresponding flags are called
+ requestCb.expect<CallbackEntry.LinkPropertiesChanged>()
+ requestCb.expect<CallbackEntry.Lost>()
+ requestCb.assertNoCallback(timeoutMs = 0L)
+
+ listenCb.expect<CallbackEntry.CapabilitiesChanged>()
+ listenCb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testDeclaredMethodsFlagsToString() {
+ assertEquals("NONE", ConnectivityService.declaredMethodsFlagsToString(0))
+ assertEquals("ALL", ConnectivityService.declaredMethodsFlagsToString(0.inv()))
+ assertEquals("AVAIL|NC|LP|BLK|LOCALINF", ConnectivityService.declaredMethodsFlagsToString(
+ (1 shl CALLBACK_AVAILABLE) or
+ (1 shl CALLBACK_CAP_CHANGED) or
+ (1 shl CALLBACK_IP_CHANGED) or
+ (1 shl CALLBACK_BLK_CHANGED) or
+ (1 shl CALLBACK_LOCAL_NETWORK_INFO_CHANGED)
+ ))
+
+ // EXPIRE_LEGACY_REQUEST (=8) is only used in ConnectivityManager and not included.
+ // CALLBACK_TRANSITIVE_CALLS_ONLY (=0) is not a callback so not included either.
+ assertEquals(
+ "PRECHK|AVAIL|LOSING|LOST|UNAVAIL|NC|LP|SUSP|RESUME|BLK|LOCALINF|0x7fffe101",
+ ConnectivityService.declaredMethodsFlagsToString(0x7fff_ffff)
+ )
+ // The toString method and the assertion above need to be updated if constants are added
+ val constants = ConnectivityManager::class.java.declaredFields.filter {
+ Modifier.isStatic(it.modifiers) && Modifier.isFinal(it.modifiers) &&
+ it.name.startsWith("CALLBACK_")
+ }
+ assertEquals(12, constants.size)
+ }
+}
+
+private fun AtomicInteger.withFlags(vararg flags: Int, action: () -> Unit) {
+ tryTest {
+ set(packBits(flags).toInt())
+ action()
+ } cleanup {
+ set(DECLARED_METHODS_ALL)
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt
index 93f6e81..77b06b2 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt
@@ -26,7 +26,9 @@
import android.net.NetworkCapabilities.TRANSPORT_VPN
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkRequest
+import android.net.VpnManager.TYPE_VPN_OEM
import android.net.VpnManager.TYPE_VPN_SERVICE
+import android.net.VpnManager.TYPE_VPN_LEGACY
import android.net.VpnTransportInfo
import android.os.Build
import androidx.test.filters.SmallTest
@@ -49,19 +51,19 @@
private const val TIMEOUT_MS = 1_000L
private const val LONG_TIMEOUT_MS = 5_000
-private fun vpnNc() = NetworkCapabilities.Builder()
- .addTransportType(TRANSPORT_VPN)
- .removeCapability(NET_CAPABILITY_NOT_VPN)
- .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
- .setTransportInfo(
- VpnTransportInfo(
- TYPE_VPN_SERVICE,
- "MySession12345",
- false /* bypassable */,
- false /* longLivedTcpConnectionsExpensive */
- )
- )
- .build()
+private fun vpnNc(vpnType: Int = TYPE_VPN_SERVICE) = NetworkCapabilities.Builder().apply {
+ addTransportType(TRANSPORT_VPN)
+ removeCapability(NET_CAPABILITY_NOT_VPN)
+ addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ setTransportInfo(
+ VpnTransportInfo(
+ vpnType,
+ "MySession12345",
+ false /* bypassable */,
+ false /* longLivedTcpConnectionsExpensive */
+ )
+ )
+}.build()
private fun wifiNc() = NetworkCapabilities.Builder()
.addTransportType(TRANSPORT_WIFI)
@@ -310,4 +312,38 @@
// IngressDiscardRule should not be added since feature is disabled
verify(bpfNetMaps, never()).setIngressDiscardRule(any(), any())
}
+
+ fun doTestVpnIngressDiscardRule_VpnType(vpnType: Int, expectAddRule: Boolean) {
+ val nr = nr(TRANSPORT_VPN)
+ val cb = TestableNetworkCallback()
+ cm.registerNetworkCallback(nr, cb)
+ val nc = vpnNc(vpnType)
+ val lp = lp(VPN_IFNAME, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ val agent = Agent(nc = nc, lp = lp)
+ agent.connect()
+ cb.expectAvailableCallbacks(agent.network, validated = false)
+
+ if (expectAddRule) {
+ verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
+ } else {
+ verify(bpfNetMaps, never()).setIngressDiscardRule(any(), any())
+ }
+ }
+
+ @Test
+ fun testVpnIngressDiscardRule_ServiceVpn() {
+ doTestVpnIngressDiscardRule_VpnType(TYPE_VPN_SERVICE, expectAddRule = true)
+ }
+
+ @Test
+ fun testVpnIngressDiscardRule_LegacyVpn() {
+ // IngressDiscardRule should not be added to Legacy VPN
+ doTestVpnIngressDiscardRule_VpnType(TYPE_VPN_LEGACY, expectAddRule = false)
+ }
+
+ @Test
+ fun testVpnIngressDiscardRule_OemVpn() {
+ // IngressDiscardRule should not be added to OEM VPN
+ doTestVpnIngressDiscardRule_VpnType(TYPE_VPN_OEM, expectAddRule = false)
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSQueuedCallbacksTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSQueuedCallbacksTest.kt
new file mode 100644
index 0000000..fc2a06c
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSQueuedCallbacksTest.kt
@@ -0,0 +1,636 @@
+/*
+ * Copyright (C) 2024 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.connectivityservice
+
+import android.app.ActivityManager.UidFrozenStateChangedCallback
+import android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN
+import android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN
+import android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER
+import android.net.ConnectivityManager.BLOCKED_REASON_NONE
+import android.net.ConnectivitySettingsManager
+import android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS
+import android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS
+import android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.LocalNetworkConfig
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
+import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkPolicyManager.NetworkPolicyCallback
+import android.net.NetworkRequest
+import android.os.Build
+import android.os.Process
+import com.android.server.CALLING_UID_UNMOCKED
+import com.android.server.CSAgentWrapper
+import com.android.server.CSTest
+import com.android.server.FromS
+import com.android.server.HANDLER_TIMEOUT_MS
+import com.android.server.connectivity.ConnectivityFlags.QUEUE_CALLBACKS_FOR_FROZEN_APPS
+import com.android.server.defaultLp
+import com.android.server.defaultNc
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
+import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.LocalInfoChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.Lost
+import com.android.testutils.RecorderCallback.CallbackEntry.Resumed
+import com.android.testutils.RecorderCallback.CallbackEntry.Suspended
+import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.visibleOnHandlerThread
+import com.android.testutils.waitForIdleSerialExecutor
+import java.util.Collections
+import kotlin.test.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
+
+private const val TEST_UID = 42
+
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner::class)
+class CSQueuedCallbacksTest(freezingBehavior: FreezingBehavior) : CSTest() {
+ companion object {
+ enum class FreezingBehavior {
+ UID_FROZEN,
+ UID_NOT_FROZEN,
+ UID_FROZEN_FEATURE_DISABLED
+ }
+
+ // Use a parameterized test with / without freezing to make it easy to compare and make sure
+ // freezing behavior (which callbacks are sent in which order) stays close to what happens
+ // without freezing.
+ @JvmStatic
+ @Parameterized.Parameters(name = "freezingBehavior={0}")
+ fun freezingBehavior() = listOf(
+ FreezingBehavior.UID_FROZEN,
+ FreezingBehavior.UID_NOT_FROZEN,
+ FreezingBehavior.UID_FROZEN_FEATURE_DISABLED
+ )
+
+ private val TAG = CSQueuedCallbacksTest::class.simpleName
+ ?: fail("Could not get test class name")
+ }
+
+ @get:Rule
+ val ignoreRule = DevSdkIgnoreRule()
+
+ private val mockedBlockedReasonsPerUid = Collections.synchronizedMap(mutableMapOf(
+ Process.myUid() to BLOCKED_REASON_NONE,
+ TEST_UID to BLOCKED_REASON_NONE
+ ))
+
+ private val freezeUids = freezingBehavior != FreezingBehavior.UID_NOT_FROZEN
+ private val expectAllCallbacks = freezingBehavior == FreezingBehavior.UID_NOT_FROZEN ||
+ freezingBehavior == FreezingBehavior.UID_FROZEN_FEATURE_DISABLED
+ init {
+ setFeatureEnabled(
+ QUEUE_CALLBACKS_FOR_FROZEN_APPS,
+ freezingBehavior != FreezingBehavior.UID_FROZEN_FEATURE_DISABLED
+ )
+ }
+
+ @Before
+ fun subclassSetUp() {
+ // Ensure cellular stays up. CS is recreated for each test so no cleanup is necessary.
+// cm.requestNetwork(
+// NetworkRequest.Builder().addTransportType(TRANSPORT_CELLULAR).build(),
+// TestableNetworkCallback()
+// )
+ }
+
+ @Test
+ fun testFrozenWhileNetworkConnects_UpdatesAreReceived() {
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.registerNetworkCallback(NetworkRequest.Builder().build(), cb)
+ }
+ val agent = Agent(TRANSPORT_CELLULAR).apply { connect() }
+ val lpChangeOnConnect = agent.sendLpChange { setLinkAddresses("fe80:db8::123/64") }
+ val ncChangeOnConnect = agent.sendNcChange {
+ addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+ }
+
+ maybeSetUidsFrozen(true, TEST_UID)
+
+ val lpChange1WhileFrozen = agent.sendLpChange {
+ setLinkAddresses("fe80:db8::126/64")
+ }
+ val ncChange1WhileFrozen = agent.sendNcChange {
+ removeCapability(NET_CAPABILITY_NOT_ROAMING)
+ }
+ val ncChange2WhileFrozen = agent.sendNcChange {
+ addCapability(NET_CAPABILITY_NOT_ROAMING)
+ addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ }
+ val lpChange2WhileFrozen = agent.sendLpChange {
+ setLinkAddresses("fe80:db8::125/64")
+ }
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ // Verify callbacks that are sent before freezing
+ cb.expectAvailableCallbacks(agent.network, validated = false)
+ cb.expectLpWith(agent, lpChangeOnConnect)
+ cb.expectNcWith(agent, ncChangeOnConnect)
+
+ // Below callbacks should be skipped if the processes were frozen, since a single callback
+ // will be sent with the latest state after unfreezing
+ if (expectAllCallbacks) {
+ cb.expectLpWith(agent, lpChange1WhileFrozen)
+ cb.expectNcWith(agent, ncChange1WhileFrozen)
+ }
+
+ cb.expectNcWith(agent, ncChange2WhileFrozen)
+ cb.expectLpWith(agent, lpChange2WhileFrozen)
+
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testFrozenWhileNetworkConnects_SuspendedUnsuspendedWhileFrozen() {
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.registerNetworkCallback(NetworkRequest.Builder().build(), cb)
+ }
+
+ val agent = Agent(TRANSPORT_CELLULAR).apply { connect() }
+ maybeSetUidsFrozen(true, TEST_UID)
+ val rmCap = agent.sendNcChange { removeCapability(NET_CAPABILITY_NOT_SUSPENDED) }
+ val addCap = agent.sendNcChange { addCapability(NET_CAPABILITY_NOT_SUSPENDED) }
+
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ cb.expectAvailableCallbacks(agent.network, validated = false)
+ if (expectAllCallbacks) {
+ cb.expectNcWith(agent, rmCap)
+ cb.expect<Suspended>(agent)
+ cb.expectNcWith(agent, addCap)
+ cb.expect<Resumed>(agent)
+ } else {
+ // When frozen, a single NetworkCapabilitiesChange will be sent at unfreezing time,
+ // with nc actually identical to the original ones. This is because NetworkCapabilities
+ // callbacks were sent, but CS does not keep initial NetworkCapabilities in memory, so
+ // it cannot detect A->B->A.
+ cb.expect<CapabilitiesChanged>(agent) {
+ it.caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ }
+ }
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testFrozenWhileNetworkConnects_UnsuspendedWhileFrozen_GetResumedCallbackWhenUnfrozen() {
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.registerNetworkCallback(NetworkRequest.Builder().build(), cb)
+ }
+
+ val agent = Agent(TRANSPORT_CELLULAR).apply { connect() }
+ val rmCap = agent.sendNcChange { removeCapability(NET_CAPABILITY_NOT_SUSPENDED) }
+ maybeSetUidsFrozen(true, TEST_UID)
+ val addCap = agent.sendNcChange { addCapability(NET_CAPABILITY_NOT_SUSPENDED) }
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ cb.expectAvailableCallbacks(agent.network, validated = false)
+ cb.expectNcWith(agent, rmCap)
+ cb.expect<Suspended>(agent)
+ cb.expectNcWith(agent, addCap)
+ cb.expect<Resumed>(agent)
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testFrozenWhileNetworkConnects_BlockedUnblockedWhileFrozen_SingleCallbackIfFrozen() {
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.registerNetworkCallback(NetworkRequest.Builder().build(), cb)
+ }
+ val agent = Agent(TRANSPORT_CELLULAR).apply { connect() }
+
+ maybeSetUidsFrozen(true, TEST_UID)
+ setUidsBlockedForDataSaver(true, TEST_UID)
+ setUidsBlockedForDataSaver(false, TEST_UID)
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ cb.expectAvailableCallbacks(agent.network, validated = false)
+ if (expectAllCallbacks) {
+ cb.expect<BlockedStatus>(agent) { it.blocked }
+ }
+ // The unblocked callback is sent in any case (with the latest blocked reason), as the
+ // blocked reason may have changed, and ConnectivityService cannot know that it is the same
+ // as the original reason as it does not keep pre-freeze blocked reasons in memory.
+ cb.expect<BlockedStatus>(agent) { !it.blocked }
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testFrozenWhileNetworkConnects_BlockedWhileFrozen_GetLastBlockedCallbackOnlyIfFrozen() {
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.registerNetworkCallback(NetworkRequest.Builder().build(), cb)
+ }
+ val agent = Agent(TRANSPORT_CELLULAR).apply { connect() }
+
+ maybeSetUidsFrozen(true, TEST_UID)
+ setUidsBlockedForDataSaver(true, TEST_UID)
+ setUidsBlockedForDataSaver(false, TEST_UID)
+ setUidsBlockedForDataSaver(true, TEST_UID)
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ cb.expectAvailableCallbacks(agent.network, validated = false)
+ if (expectAllCallbacks) {
+ cb.expect<BlockedStatus>(agent) { it.blocked }
+ cb.expect<BlockedStatus>(agent) { !it.blocked }
+ }
+ cb.expect<BlockedStatus>(agent) { it.blocked }
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testNetworkCallback_NetworkToggledWhileFrozen_NotSeen() {
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.registerNetworkCallback(NetworkRequest.Builder().build(), cb)
+ }
+ val cellAgent = Agent(TRANSPORT_CELLULAR).apply { connect() }
+ maybeSetUidsFrozen(true, TEST_UID)
+ val wifiAgent = Agent(TRANSPORT_WIFI).apply { connect() }
+ wifiAgent.disconnect()
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ cb.expectAvailableCallbacks(cellAgent.network, validated = false)
+ if (expectAllCallbacks) {
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+ cb.expect<Lost>(wifiAgent)
+ }
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testNetworkCallback_NetworkAppearedWhileFrozen_ReceiveLatestInfoInOnAvailable() {
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.registerNetworkCallback(NetworkRequest.Builder().build(), cb)
+ }
+ maybeSetUidsFrozen(true, TEST_UID)
+ val agent = Agent(TRANSPORT_CELLULAR).apply { connect() }
+ waitForIdle()
+ agent.makeValidationSuccess()
+ val lpChange = agent.sendLpChange {
+ setLinkAddresses("fe80:db8::123/64")
+ }
+ val suspendedChange = agent.sendNcChange {
+ removeCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ }
+ setUidsBlockedForDataSaver(true, TEST_UID)
+
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ val expectLatestStatusInOnAvailable = !expectAllCallbacks
+ cb.expectAvailableCallbacks(
+ agent.network,
+ suspended = expectLatestStatusInOnAvailable,
+ validated = expectLatestStatusInOnAvailable,
+ blocked = expectLatestStatusInOnAvailable
+ )
+ if (expectAllCallbacks) {
+ cb.expectNcWith(agent) { addCapability(NET_CAPABILITY_VALIDATED) }
+ cb.expectLpWith(agent, lpChange)
+ cb.expectNcWith(agent, suspendedChange)
+ cb.expect<Suspended>(agent)
+ cb.expect<BlockedStatus>(agent) { it.blocked }
+ }
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun testNetworkCallback_LocalNetworkAppearedWhileFrozen_ReceiveLatestInfoInOnAvailable() {
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.registerNetworkCallback(
+ NetworkRequest.Builder().addCapability(NET_CAPABILITY_LOCAL_NETWORK).build(),
+ cb
+ )
+ }
+ val upstreamAgent = Agent(
+ nc = defaultNc()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET),
+ lp = defaultLp().apply { interfaceName = "wlan0" }
+ ).apply { connect() }
+ maybeSetUidsFrozen(true, TEST_UID)
+
+ val lnc = LocalNetworkConfig.Builder().build()
+ val localAgent = Agent(
+ nc = defaultNc()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .removeCapability(NET_CAPABILITY_INTERNET),
+ lp = defaultLp().apply { interfaceName = "local42" },
+ lnc = FromS(lnc)
+ ).apply { connect() }
+ localAgent.sendLocalNetworkConfig(
+ LocalNetworkConfig.Builder()
+ .setUpstreamSelector(
+ NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .build()
+ )
+ .build()
+ )
+
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ cb.expectAvailableCallbacks(
+ localAgent.network,
+ validated = false,
+ upstream = if (expectAllCallbacks) null else upstreamAgent.network
+ )
+ if (expectAllCallbacks) {
+ cb.expect<LocalInfoChanged>(localAgent) {
+ it.info.upstreamNetwork == upstreamAgent.network
+ }
+ }
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testNetworkRequest_NetworkSwitchesWhileFrozen_ReceiveLastNetworkUpdatesOnly() {
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.requestNetwork(NetworkRequest.Builder().build(), cb)
+ }
+ val cellAgent = Agent(TRANSPORT_CELLULAR).apply { connect() }
+ maybeSetUidsFrozen(true, TEST_UID)
+ val wifiAgent = Agent(TRANSPORT_WIFI).apply { connect() }
+ val ethAgent = Agent(TRANSPORT_ETHERNET).apply { connect() }
+ waitForIdle()
+ ethAgent.makeValidationSuccess()
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ cb.expectAvailableCallbacks(cellAgent.network, validated = false)
+ if (expectAllCallbacks) {
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+ cb.expectAvailableCallbacks(ethAgent.network, validated = false)
+ cb.expectNcWith(ethAgent) { addCapability(NET_CAPABILITY_VALIDATED) }
+ } else {
+ cb.expectAvailableCallbacks(ethAgent.network, validated = true)
+ }
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testNetworkRequest_NetworkSwitchesBackWhileFrozen_ReceiveNoAvailableCallback() {
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.requestNetwork(NetworkRequest.Builder().build(), cb)
+ }
+ val cellAgent = Agent(TRANSPORT_CELLULAR).apply { connect() }
+ maybeSetUidsFrozen(true, TEST_UID)
+ val wifiAgent = Agent(TRANSPORT_WIFI).apply { connect() }
+ waitForIdle()
+
+ // CS switches back to validated cell over non-validated Wi-Fi
+ cellAgent.makeValidationSuccess()
+ val cellLpChange = cellAgent.sendLpChange {
+ setLinkAddresses("fe80:db8::123/64")
+ }
+ setUidsBlockedForDataSaver(true, TEST_UID)
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ cb.expectAvailableCallbacks(cellAgent.network, validated = false)
+ if (expectAllCallbacks) {
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+ // There is an extra "double validated" CapabilitiesChange callback (b/245893397), so
+ // callbacks are (AVAIL, NC, LP), extra NC, then further updates (LP and BLK here).
+ cb.expectAvailableDoubleValidatedCallbacks(cellAgent.network)
+ cb.expectLpWith(cellAgent, cellLpChange)
+ cb.expect<BlockedStatus>(cellAgent) { it.blocked }
+ } else {
+ cb.expectNcWith(cellAgent) {
+ addCapability(NET_CAPABILITY_VALIDATED)
+ }
+ cb.expectLpWith(cellAgent, cellLpChange)
+ cb.expect<BlockedStatus>(cellAgent) { it.blocked }
+ }
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testTrackDefaultRequest_AppFrozenWhilePerAppDefaultRequestFiled_ReceiveChangeCallbacks() {
+ val cellAgent = Agent(TRANSPORT_CELLULAR, baseNc = makeInternetNc()).apply { connect() }
+ waitForIdle()
+
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.registerDefaultNetworkCallback(cb)
+ }
+ maybeSetUidsFrozen(true, TEST_UID)
+
+ // Change LinkProperties twice before the per-app network request is applied
+ val lpChange1 = cellAgent.sendLpChange {
+ setLinkAddresses("fe80:db8::123/64")
+ }
+ val lpChange2 = cellAgent.sendLpChange {
+ setLinkAddresses("fe80:db8::124/64")
+ }
+ setMobileDataPreferredUids(setOf(TEST_UID))
+
+ // Change NetworkCapabilities after the per-app network request is applied
+ val ncChange = cellAgent.sendNcChange {
+ addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+ }
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ // Even if a per-app network request was filed to replace the default network request for
+ // the app, all network change callbacks are received
+ cb.expectAvailableCallbacks(cellAgent.network, validated = false)
+ if (expectAllCallbacks) {
+ cb.expectLpWith(cellAgent, lpChange1)
+ }
+ cb.expectLpWith(cellAgent, lpChange2)
+ cb.expectNcWith(cellAgent, ncChange)
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testTrackDefaultRequest_AppFrozenWhilePerAppDefaultToggled_GetStatusUpdateCallbacksOnly() {
+ // Add validated Wi-Fi and non-validated cell, expect Wi-Fi is preferred by default
+ val wifiAgent = Agent(TRANSPORT_WIFI, baseNc = makeInternetNc()).apply { connect() }
+ wifiAgent.makeValidationSuccess()
+ val cellAgent = Agent(TRANSPORT_CELLULAR, baseNc = makeInternetNc()).apply { connect() }
+ waitForIdle()
+
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.registerDefaultNetworkCallback(cb)
+ }
+ maybeSetUidsFrozen(true, TEST_UID)
+
+ // LP change on the original Wi-Fi network
+ val lpChange = wifiAgent.sendLpChange {
+ setLinkAddresses("fe80:db8::123/64")
+ }
+ // Set per-app default to cell, then unset it
+ setMobileDataPreferredUids(setOf(TEST_UID))
+ setMobileDataPreferredUids(emptySet())
+
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ cb.expectAvailableCallbacks(wifiAgent.network)
+ if (expectAllCallbacks) {
+ cb.expectLpWith(wifiAgent, lpChange)
+ cb.expectAvailableCallbacks(cellAgent.network, validated = false)
+ // Cellular stops being foreground since it is now matched for this app
+ cb.expect<CapabilitiesChanged> { it.caps.hasCapability(NET_CAPABILITY_FOREGROUND) }
+ cb.expectAvailableCallbacks(wifiAgent.network)
+ } else {
+ // After switching to cell and back while frozen, only network attribute update
+ // callbacks (and not AVAILABLE) for the original Wi-Fi network should be sent
+ cb.expect<CapabilitiesChanged>(wifiAgent)
+ cb.expectLpWith(wifiAgent, lpChange)
+ cb.expect<BlockedStatus> { !it.blocked }
+ }
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ private fun setUidsBlockedForDataSaver(blocked: Boolean, vararg uid: Int) {
+ val reason = if (blocked) {
+ BLOCKED_METERED_REASON_DATA_SAVER
+ } else {
+ BLOCKED_REASON_NONE
+ }
+ if (deps.isAtLeastV) {
+ visibleOnHandlerThread(csHandler) {
+ service.handleBlockedReasonsChanged(uid.map { android.util.Pair(it, reason) })
+ }
+ } else {
+ notifyLegacyBlockedReasonChanged(reason, uid)
+ waitForIdle()
+ }
+ }
+
+ @Suppress("DEPRECATION")
+ private fun notifyLegacyBlockedReasonChanged(reason: Int, uids: IntArray) {
+ val cbCaptor = ArgumentCaptor.forClass(NetworkPolicyCallback::class.java)
+ verify(context.networkPolicyManager).registerNetworkPolicyCallback(
+ any(),
+ cbCaptor.capture()
+ )
+ uids.forEach {
+ cbCaptor.value.onUidBlockedReasonChanged(it, reason)
+ }
+ }
+
+ private fun withCallingUid(uid: Int, action: () -> Unit) {
+ deps.callingUid = uid
+ action()
+ deps.callingUid = CALLING_UID_UNMOCKED
+ }
+
+ private fun getUidFrozenStateChangedCallback(): UidFrozenStateChangedCallback {
+ val captor = ArgumentCaptor.forClass(UidFrozenStateChangedCallback::class.java)
+ verify(activityManager).registerUidFrozenStateChangedCallback(any(), captor.capture())
+ return captor.value
+ }
+
+ private fun maybeSetUidsFrozen(frozen: Boolean, vararg uids: Int) {
+ if (!freezeUids) return
+ val state = if (frozen) UID_FROZEN_STATE_FROZEN else UID_FROZEN_STATE_UNFROZEN
+ getUidFrozenStateChangedCallback()
+ .onUidFrozenStateChanged(uids, IntArray(uids.size) { state })
+ waitForIdle()
+ }
+
+ private fun CSAgentWrapper.makeValidationSuccess() {
+ setValidationResult(
+ NETWORK_VALIDATION_RESULT_VALID,
+ probesCompleted = NETWORK_VALIDATION_PROBE_DNS or NETWORK_VALIDATION_PROBE_HTTPS,
+ probesSucceeded = NETWORK_VALIDATION_PROBE_DNS or NETWORK_VALIDATION_PROBE_HTTPS
+ )
+ cm.reportNetworkConnectivity(network, true)
+ // Ensure validation is scheduled
+ waitForIdle()
+ // Ensure validation completes on mock executor
+ waitForIdleSerialExecutor(CSTestExecutor, HANDLER_TIMEOUT_MS)
+ // Ensure validation results are processed
+ waitForIdle()
+ }
+
+ private fun setMobileDataPreferredUids(uids: Set<Int>) {
+ ConnectivitySettingsManager.setMobileDataPreferredUids(context, uids)
+ service.updateMobileDataPreferredUids()
+ waitForIdle()
+ }
+}
+
+private fun makeInternetNc() = NetworkCapabilities.Builder(defaultNc())
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .build()
+
+private fun CSAgentWrapper.sendLpChange(
+ mutator: LinkProperties.() -> Unit
+): LinkProperties.() -> Unit {
+ lp.mutator()
+ sendLinkProperties(lp)
+ return mutator
+}
+
+private fun CSAgentWrapper.sendNcChange(
+ mutator: NetworkCapabilities.() -> Unit
+): NetworkCapabilities.() -> Unit {
+ nc.mutator()
+ sendNetworkCapabilities(nc)
+ return mutator
+}
+
+private fun TestableNetworkCallback.expectLpWith(
+ agent: CSAgentWrapper,
+ change: LinkProperties.() -> Unit
+) = expect<LinkPropertiesChanged>(agent) {
+ // This test uses changes that are no-op when already applied (idempotent): verify that the
+ // change is already applied.
+ it.lp == LinkProperties(it.lp).apply(change)
+}
+
+private fun TestableNetworkCallback.expectNcWith(
+ agent: CSAgentWrapper,
+ change: NetworkCapabilities.() -> Unit
+) = expect<CapabilitiesChanged>(agent) {
+ it.caps == NetworkCapabilities(it.caps).apply(change)
+}
+
+private fun LinkProperties.setLinkAddresses(vararg addrs: String) {
+ setLinkAddresses(addrs.map { LinkAddress(it) })
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
index 13c5cbc..1f5ee32 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
@@ -192,7 +192,8 @@
connect()
}
- fun setProbesStatus(probesCompleted: Int, probesSucceeded: Int) {
+ fun setValidationResult(result: Int, probesCompleted: Int, probesSucceeded: Int) {
+ nmValidationResult = result
nmProbesCompleted = probesCompleted
nmProbesSucceeded = probesSucceeded
}
@@ -204,8 +205,10 @@
// in the beginning. Because NETWORK_VALIDATION_PROBE_HTTP is the decisive probe for captive
// portal, considering the NETWORK_VALIDATION_PROBE_HTTPS hasn't probed yet and set only
// DNS and HTTP probes completed.
- setProbesStatus(
- NETWORK_VALIDATION_PROBE_DNS or NETWORK_VALIDATION_PROBE_HTTP /* probesCompleted */,
- VALIDATION_RESULT_INVALID /* probesSucceeded */)
+ setValidationResult(
+ VALIDATION_RESULT_INVALID,
+ probesCompleted = NETWORK_VALIDATION_PROBE_DNS or NETWORK_VALIDATION_PROBE_HTTP,
+ probesSucceeded = NO_PROBE_RESULT
+ )
}
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
index ed72fd2..46c25d2 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -165,6 +165,7 @@
it[ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN] = true
it[ConnectivityFlags.DELAY_DESTROY_SOCKETS] = true
it[ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS] = true
+ it[ConnectivityFlags.QUEUE_CALLBACKS_FOR_FROZEN_APPS] = true
}
fun setFeatureEnabled(flag: String, enabled: Boolean) = enabledFeatures.set(flag, enabled)
@@ -537,8 +538,12 @@
provider: NetworkProvider? = null
) = CSAgentWrapper(context, deps, csHandlerThread, networkStack,
nac, nc, lp, lnc, score, provider)
- fun Agent(vararg transports: Int, lp: LinkProperties = defaultLp()): CSAgentWrapper {
- val nc = NetworkCapabilities.Builder().apply {
+ fun Agent(
+ vararg transports: Int,
+ baseNc: NetworkCapabilities = defaultNc(),
+ lp: LinkProperties = defaultLp()
+ ): CSAgentWrapper {
+ val nc = NetworkCapabilities.Builder(baseNc).apply {
transports.forEach {
addTransportType(it)
}
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetInterfaceStateMachineTest.kt b/tests/unit/java/com/android/server/ethernet/EthernetInterfaceStateMachineTest.kt
new file mode 100644
index 0000000..c8b2f65
--- /dev/null
+++ b/tests/unit/java/com/android/server/ethernet/EthernetInterfaceStateMachineTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+// ktlint does not allow annotating function argument literals inline. Disable the specific rule
+// since this negatively affects readability.
+@file:Suppress("ktlint:standard:comment-wrapping")
+
+package com.android.server.ethernet
+
+import android.content.Context
+import android.net.NetworkCapabilities
+import android.net.NetworkProvider
+import android.net.NetworkProvider.NetworkOfferCallback
+import android.os.Build
+import android.os.Handler
+import android.os.test.TestLooper
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+private const val IFACE = "eth0"
+private val CAPS = NetworkCapabilities.Builder().build()
+
+@SmallTest
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class EthernetInterfaceStateMachineTest {
+ private lateinit var looper: TestLooper
+ private lateinit var handler: Handler
+ private lateinit var ifaceState: EthernetInterfaceStateMachine
+
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var provider: NetworkProvider
+ @Mock private lateinit var deps: EthernetNetworkFactory.Dependencies
+
+ // There seems to be no (obvious) way to force execution of @Before and @Test annotation on the
+ // same thread. Since SyncStateMachine requires all interactions to be called from the same
+ // thread that is provided at construction time (in this case, the thread that TestLooper() is
+ // called on), setUp() must be called directly from the @Test method.
+ // TODO: find a way to fix this in the test runner.
+ fun setUp() {
+ looper = TestLooper()
+ handler = Handler(looper.looper)
+ MockitoAnnotations.initMocks(this)
+
+ ifaceState = EthernetInterfaceStateMachine(IFACE, handler, context, CAPS, provider, deps)
+ }
+
+ @Test
+ fun testUpdateLinkState_networkOfferRegisteredAndRetracted() {
+ setUp()
+
+ ifaceState.updateLinkState(/* up= */ true)
+
+ // link comes up: validate the NetworkOffer is registered and capture callback object.
+ val inOrder = inOrder(provider)
+ val networkOfferCb = ArgumentCaptor.forClass(NetworkOfferCallback::class.java).also {
+ inOrder.verify(provider).registerNetworkOffer(any(), any(), any(), it.capture())
+ }.value
+
+ ifaceState.updateLinkState(/* up */ false)
+
+ // link goes down: validate the NetworkOffer is retracted
+ inOrder.verify(provider).unregisterNetworkOffer(eq(networkOfferCb))
+ }
+}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 7e0a225..3d2f389 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -69,6 +69,8 @@
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_RAT_CHANGED;
import static com.android.server.net.NetworkStatsEventLogger.PollEvent.pollReasonNameOf;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
+import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
+import static com.android.server.net.NetworkStatsService.BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG;
import static com.android.server.net.NetworkStatsService.DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS;
import static com.android.server.net.NetworkStatsService.DEFAULT_TRAFFIC_STATS_CACHE_MAX_ENTRIES;
import static com.android.server.net.NetworkStatsService.NETSTATS_FASTDATAINPUT_FALLBACKS_COUNTER_NAME;
@@ -82,6 +84,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.AdditionalMatchers.aryEq;
@@ -101,8 +104,10 @@
import android.annotation.NonNull;
import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -138,6 +143,7 @@
import android.provider.Settings;
import android.system.ErrnoException;
import android.telephony.TelephonyManager;
+import android.testing.TestableLooper;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
@@ -150,6 +156,7 @@
import com.android.connectivity.resources.R;
import com.android.internal.util.FileRotator;
import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.net.module.util.ArrayTrackRecord;
import com.android.net.module.util.BpfDump;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.LocationPermissionChecker;
@@ -618,6 +625,12 @@
}
@Override
+ public boolean enabledBroadcastNetworkStatsUpdatedRateLimiting(Context ctx) {
+ return mFeatureFlags.getOrDefault(
+ BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG, true);
+ }
+
+ @Override
public int getTrafficStatsRateLimitCacheExpiryDuration() {
return DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS;
}
@@ -2617,6 +2630,8 @@
private void mockDefaultSettings() throws Exception {
mockSettings(HOUR_IN_MILLIS, WEEK_IN_MILLIS);
+ mSettings.setBroadcastNetworkStatsUpdateDelayMs(
+ NetworkStatsService.BROADCAST_NETWORK_STATS_UPDATED_DELAY_MS);
}
private void mockSettings(long bucketDuration, long deleteAge) {
@@ -2631,6 +2646,8 @@
@NonNull
private volatile Config mConfig;
private final AtomicBoolean mCombineSubtypeEnabled = new AtomicBoolean();
+ private long mBroadcastNetworkStatsUpdateDelayMs =
+ NetworkStatsService.BROADCAST_NETWORK_STATS_UPDATED_DELAY_MS;
TestNetworkStatsSettings(long bucketDuration, long deleteAge) {
mConfig = new Config(bucketDuration, deleteAge, deleteAge);
@@ -2693,6 +2710,15 @@
public boolean getAugmentEnabled() {
return false;
}
+
+ @Override
+ public long getBroadcastNetworkStatsUpdateDelayMs() {
+ return mBroadcastNetworkStatsUpdateDelayMs;
+ }
+
+ public void setBroadcastNetworkStatsUpdateDelayMs(long broadcastDelay) {
+ mBroadcastNetworkStatsUpdateDelayMs = broadcastDelay;
+ }
}
private void assertStatsFilesExist(boolean exist) {
@@ -3064,4 +3090,91 @@
final String dump = getDump();
assertDumpContains(dump, "Log for testing");
}
+
+ private static class TestNetworkStatsUpdatedReceiver extends BroadcastReceiver {
+ private final ArrayTrackRecord<Intent>.ReadHead mHistory;
+
+ TestNetworkStatsUpdatedReceiver() {
+ mHistory = (new ArrayTrackRecord<Intent>()).newReadHead();
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mHistory.add(intent);
+ }
+
+ /**
+ * Assert no broadcast intent is received in blocking manner
+ */
+ public void assertNoBroadcastIntentReceived() {
+ assertNull(mHistory.peek());
+ }
+
+ /**
+ * Assert an intent is received and remove it from queue
+ */
+ public void assertBroadcastIntentReceived() {
+ assertNotNull(mHistory.poll(WAIT_TIMEOUT, number -> true));
+ }
+ }
+
+ @FeatureFlag(name = BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG)
+ @Test
+ public void testNetworkStatsUpdatedIntentSpam_rateLimitOn() throws Exception {
+ // Set the update delay long enough that messages won't be processed before unblocked
+ // Set a short time to test the behavior before reaching delay.
+ // Constraint: test running time < toleranceMs < update delay time
+ mSettings.setBroadcastNetworkStatsUpdateDelayMs(100_000L);
+ final long toleranceMs = 5000;
+
+ final TestableLooper mTestableLooper = new TestableLooper(mHandlerThread.getLooper());
+ final TestNetworkStatsUpdatedReceiver receiver = new TestNetworkStatsUpdatedReceiver();
+ mServiceContext.registerReceiver(receiver, new IntentFilter(ACTION_NETWORK_STATS_UPDATED));
+
+ try {
+ // Test that before anything, the intent is delivered immediately
+ mService.forceUpdate();
+ mTestableLooper.processAllMessages();
+ receiver.assertBroadcastIntentReceived();
+ receiver.assertNoBroadcastIntentReceived();
+
+ // Test that the next two intents results in exactly one intent delivered
+ for (int i = 0; i < 2; i++) {
+ mService.forceUpdate();
+ }
+ // Test that the delay depends on our set value
+ mTestableLooper.moveTimeForward(mSettings.getBroadcastNetworkStatsUpdateDelayMs()
+ - toleranceMs);
+ mTestableLooper.processAllMessages();
+ receiver.assertNoBroadcastIntentReceived();
+
+ // Unblock messages and test that the second and third update
+ // is broadcasted right after the delay
+ mTestableLooper.moveTimeForward(toleranceMs);
+ mTestableLooper.processAllMessages();
+ receiver.assertBroadcastIntentReceived();
+ receiver.assertNoBroadcastIntentReceived();
+
+ } finally {
+ mTestableLooper.destroy();
+ }
+ }
+
+ @FeatureFlag(name = BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG, enabled = false)
+ @Test
+ public void testNetworkStatsUpdatedIntentSpam_rateLimitOff() throws Exception {
+ // Set the update delay long enough to ensure that messages are processed
+ // despite the rate limit.
+ mSettings.setBroadcastNetworkStatsUpdateDelayMs(100_000L);
+
+ final TestNetworkStatsUpdatedReceiver receiver = new TestNetworkStatsUpdatedReceiver();
+ mServiceContext.registerReceiver(receiver, new IntentFilter(ACTION_NETWORK_STATS_UPDATED));
+
+ for (int i = 0; i < 2; i++) {
+ mService.forceUpdate();
+ waitForIdle();
+ receiver.assertBroadcastIntentReceived();
+ }
+ receiver.assertNoBroadcastIntentReceived();
+ }
}
diff --git a/thread/framework/java/android/net/thread/ChannelMaxPower.aidl b/thread/framework/java/android/net/thread/ChannelMaxPower.aidl
index bcda8a8..abc00b9 100644
--- a/thread/framework/java/android/net/thread/ChannelMaxPower.aidl
+++ b/thread/framework/java/android/net/thread/ChannelMaxPower.aidl
@@ -16,11 +16,11 @@
package android.net.thread;
- /**
- * Mapping from a channel to its max power.
- *
- * {@hide}
- */
+/**
+ * Mapping from a channel to its max power.
+ *
+ * {@hide}
+ */
parcelable ChannelMaxPower {
int channel; // The Thread radio channel.
int maxPower; // The max power in the unit of 0.01dBm. Passing INT16_MAX(32767) will
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeNonMeteredTest.java b/thread/framework/java/android/net/thread/IConfigurationReceiver.aidl
similarity index 64%
rename from tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeNonMeteredTest.java
rename to thread/framework/java/android/net/thread/IConfigurationReceiver.aidl
index f343df5..dcc4545 100644
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeNonMeteredTest.java
+++ b/thread/framework/java/android/net/thread/IConfigurationReceiver.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright 2024 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.
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.cts.netpolicy.hostside;
+package android.net.thread;
-import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
+import android.net.thread.ThreadConfiguration;
-@RequiredProperties({NON_METERED_NETWORK})
-public class DozeModeNonMeteredTest extends AbstractDozeModeTestCase {
+/** Receives the result of a Thread Configuration change. @hide */
+oneway interface IConfigurationReceiver {
+ void onConfigurationChanged(in ThreadConfiguration configuration);
}
diff --git a/thread/framework/java/android/net/thread/IOperationalDatasetCallback.aidl b/thread/framework/java/android/net/thread/IOperationalDatasetCallback.aidl
index b576b33..3fece65 100644
--- a/thread/framework/java/android/net/thread/IOperationalDatasetCallback.aidl
+++ b/thread/framework/java/android/net/thread/IOperationalDatasetCallback.aidl
@@ -23,6 +23,8 @@
* @hide
*/
oneway interface IOperationalDatasetCallback {
- void onActiveOperationalDatasetChanged(in @nullable ActiveOperationalDataset activeOpDataset);
- void onPendingOperationalDatasetChanged(in @nullable PendingOperationalDataset pendingOpDataset);
+ void onActiveOperationalDatasetChanged(
+ in @nullable ActiveOperationalDataset activeOpDataset);
+ void onPendingOperationalDatasetChanged(
+ in @nullable PendingOperationalDataset pendingOpDataset);
}
diff --git a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
index c5ca557..b7f68c9 100644
--- a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
+++ b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
@@ -19,16 +19,18 @@
import android.net.thread.ActiveOperationalDataset;
import android.net.thread.ChannelMaxPower;
import android.net.thread.IActiveOperationalDatasetReceiver;
-import android.net.thread.IOperationalDatasetCallback;
+import android.net.thread.IConfigurationReceiver;
import android.net.thread.IOperationReceiver;
+import android.net.thread.IOperationalDatasetCallback;
import android.net.thread.IScheduleMigrationReceiver;
import android.net.thread.IStateCallback;
import android.net.thread.PendingOperationalDataset;
+import android.net.thread.ThreadConfiguration;
/**
-* Interface for communicating with ThreadNetworkControllerService.
-* @hide
-*/
+ * Interface for communicating with ThreadNetworkControllerService.
+ * @hide
+ */
interface IThreadNetworkController {
void registerStateCallback(in IStateCallback callback);
void unregisterStateCallback(in IStateCallback callback);
@@ -36,14 +38,19 @@
void unregisterOperationalDatasetCallback(in IOperationalDatasetCallback callback);
void join(in ActiveOperationalDataset activeOpDataset, in IOperationReceiver receiver);
- void scheduleMigration(in PendingOperationalDataset pendingOpDataset, in IOperationReceiver receiver);
+ void scheduleMigration(
+ in PendingOperationalDataset pendingOpDataset, in IOperationReceiver receiver);
void leave(in IOperationReceiver receiver);
- void setTestNetworkAsUpstream(in String testNetworkInterfaceName, in IOperationReceiver receiver);
+ void setTestNetworkAsUpstream(
+ in String testNetworkInterfaceName, in IOperationReceiver receiver);
void setChannelMaxPowers(in ChannelMaxPower[] channelMaxPowers, in IOperationReceiver receiver);
int getThreadVersion();
void createRandomizedDataset(String networkName, IActiveOperationalDatasetReceiver receiver);
void setEnabled(boolean enabled, in IOperationReceiver receiver);
+ void setConfiguration(in ThreadConfiguration config, in IOperationReceiver receiver);
+ void registerConfigurationCallback(in IConfigurationReceiver receiver);
+ void unregisterConfigurationCallback(in IConfigurationReceiver receiver);
}
diff --git a/thread/framework/java/android/net/thread/IThreadNetworkManager.aidl b/thread/framework/java/android/net/thread/IThreadNetworkManager.aidl
index 0e394b1..b63cd72 100644
--- a/thread/framework/java/android/net/thread/IThreadNetworkManager.aidl
+++ b/thread/framework/java/android/net/thread/IThreadNetworkManager.aidl
@@ -19,9 +19,9 @@
import android.net.thread.IThreadNetworkController;
/**
-* Interface for communicating with ThreadNetworkService.
-* @hide
-*/
+ * Interface for communicating with ThreadNetworkService.
+ * @hide
+ */
interface IThreadNetworkManager {
List<IThreadNetworkController> getAllThreadNetworkControllers();
}
diff --git a/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java b/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java
index 520acbd..cecb4e9 100644
--- a/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java
+++ b/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java
@@ -65,11 +65,32 @@
*/
@NonNull
public static OperationalDatasetTimestamp fromInstant(@NonNull Instant instant) {
+ return OperationalDatasetTimestamp.fromInstant(instant, true /* isAuthoritativeSource */);
+ }
+
+ /**
+ * Creates a new {@link OperationalDatasetTimestamp} object from an {@link Instant}.
+ *
+ * <p>The {@code seconds} is set to {@code instant.getEpochSecond()}, {@code ticks} is set to
+ * {@link instant#getNano()} based on frequency of 32768 Hz, and {@code isAuthoritativeSource}
+ * is set to {@code isAuthoritativeSource}.
+ *
+ * <p>Note that this conversion can lose precision and a value returned by {@link #toInstant}
+ * may not equal exactly the {@code instant}.
+ *
+ * @throws IllegalArgumentException if {@code instant.getEpochSecond()} is larger than {@code
+ * 0xffffffffffffL}
+ * @see toInstant
+ * @hide
+ */
+ @NonNull
+ public static OperationalDatasetTimestamp fromInstant(
+ @NonNull Instant instant, boolean isAuthoritativeSource) {
int ticks = getRoundedTicks(instant.getNano());
long seconds = instant.getEpochSecond() + ticks / TICKS_UPPER_BOUND;
// the rounded ticks can be 0x8000 if instant.getNano() >= 999984742
ticks = ticks % TICKS_UPPER_BOUND;
- return new OperationalDatasetTimestamp(seconds, ticks, true /* isAuthoritativeSource */);
+ return new OperationalDatasetTimestamp(seconds, ticks, isAuthoritativeSource);
}
/**
diff --git a/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/NetworkCheckResult.aidl b/thread/framework/java/android/net/thread/ThreadConfiguration.aidl
similarity index 68%
rename from tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/NetworkCheckResult.aidl
rename to thread/framework/java/android/net/thread/ThreadConfiguration.aidl
index 7aac2ab..9473411 100644
--- a/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/NetworkCheckResult.aidl
+++ b/thread/framework/java/android/net/thread/ThreadConfiguration.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright 2024 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.
@@ -14,13 +14,6 @@
* limitations under the License.
*/
-package com.android.cts.netpolicy.hostside;
+package android.net.thread;
-import android.net.NetworkInfo;
-
-@JavaDerive(toString=true)
-parcelable NetworkCheckResult {
- boolean connected;
- String details;
- NetworkInfo networkInfo;
-}
\ No newline at end of file
+parcelable ThreadConfiguration;
diff --git a/thread/framework/java/android/net/thread/ThreadConfiguration.java b/thread/framework/java/android/net/thread/ThreadConfiguration.java
new file mode 100644
index 0000000..be2632c
--- /dev/null
+++ b/thread/framework/java/android/net/thread/ThreadConfiguration.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2024 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.thread;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Data interface for Thread device configuration.
+ *
+ * <p>An example usage of creating a {@link ThreadConfiguration} that turns on NAT64 feature based
+ * on an existing {@link ThreadConfiguration}:
+ *
+ * <pre>{@code
+ * ThreadConfiguration config =
+ * new ThreadConfiguration.Builder(existingConfig).setNat64Enabled(true).build();
+ * }</pre>
+ *
+ * @see ThreadNetworkController#setConfiguration
+ * @see ThreadNetworkController#registerConfigurationCallback
+ * @see ThreadNetworkController#unregisterConfigurationCallback
+ * @hide
+ */
+@FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
+@SystemApi
+public final class ThreadConfiguration implements Parcelable {
+ private final boolean mNat64Enabled;
+ private final boolean mDhcpv6PdEnabled;
+
+ private ThreadConfiguration(Builder builder) {
+ this(builder.mNat64Enabled, builder.mDhcpv6PdEnabled);
+ }
+
+ private ThreadConfiguration(boolean nat64Enabled, boolean dhcpv6PdEnabled) {
+ this.mNat64Enabled = nat64Enabled;
+ this.mDhcpv6PdEnabled = dhcpv6PdEnabled;
+ }
+
+ /** Returns {@code true} if NAT64 is enabled. */
+ public boolean isNat64Enabled() {
+ return mNat64Enabled;
+ }
+
+ /** Returns {@code true} if DHCPv6 Prefix Delegation is enabled. */
+ public boolean isDhcpv6PdEnabled() {
+ return mDhcpv6PdEnabled;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else if (!(other instanceof ThreadConfiguration)) {
+ return false;
+ } else {
+ ThreadConfiguration otherConfig = (ThreadConfiguration) other;
+ return mNat64Enabled == otherConfig.mNat64Enabled
+ && mDhcpv6PdEnabled == otherConfig.mDhcpv6PdEnabled;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mNat64Enabled, mDhcpv6PdEnabled);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append('{');
+ sb.append("Nat64Enabled=").append(mNat64Enabled);
+ sb.append(", Dhcpv6PdEnabled=").append(mDhcpv6PdEnabled);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeBoolean(mNat64Enabled);
+ dest.writeBoolean(mDhcpv6PdEnabled);
+ }
+
+ public static final @NonNull Creator<ThreadConfiguration> CREATOR =
+ new Creator<>() {
+ @Override
+ public ThreadConfiguration createFromParcel(Parcel in) {
+ ThreadConfiguration.Builder builder = new ThreadConfiguration.Builder();
+ builder.setNat64Enabled(in.readBoolean());
+ builder.setDhcpv6PdEnabled(in.readBoolean());
+ return builder.build();
+ }
+
+ @Override
+ public ThreadConfiguration[] newArray(int size) {
+ return new ThreadConfiguration[size];
+ }
+ };
+
+ /**
+ * The builder for creating {@link ThreadConfiguration} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ private boolean mNat64Enabled = false;
+ private boolean mDhcpv6PdEnabled = false;
+
+ /** Creates a new {@link Builder} object with all features disabled. */
+ public Builder() {}
+
+ /**
+ * Creates a new {@link Builder} object from a {@link ThreadConfiguration} object.
+ *
+ * @param config the Border Router configurations to be copied
+ */
+ public Builder(@NonNull ThreadConfiguration config) {
+ Objects.requireNonNull(config);
+
+ mNat64Enabled = config.mNat64Enabled;
+ mDhcpv6PdEnabled = config.mDhcpv6PdEnabled;
+ }
+
+ /**
+ * Enables or disables NAT64 for the device.
+ *
+ * <p>Enabling this feature will allow Thread devices to connect to the internet/cloud over
+ * IPv4.
+ */
+ @NonNull
+ public Builder setNat64Enabled(boolean enabled) {
+ this.mNat64Enabled = enabled;
+ return this;
+ }
+
+ /**
+ * Enables or disables Prefix Delegation for the device.
+ *
+ * <p>Enabling this feature will allow Thread devices to connect to the internet/cloud over
+ * IPv6.
+ */
+ @NonNull
+ public Builder setDhcpv6PdEnabled(boolean enabled) {
+ this.mDhcpv6PdEnabled = enabled;
+ return this;
+ }
+
+ /** Creates a new {@link ThreadConfiguration} object. */
+ @NonNull
+ public ThreadConfiguration build() {
+ return new ThreadConfiguration(this);
+ }
+ }
+}
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkController.java b/thread/framework/java/android/net/thread/ThreadNetworkController.java
index 8d6b40a..b4e581c 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkController.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkController.java
@@ -41,6 +41,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Provides the primary APIs for controlling all aspects of a Thread network.
@@ -124,6 +125,12 @@
private final Map<OperationalDatasetCallback, OperationalDatasetCallbackProxy>
mOpDatasetCallbackMap = new HashMap<>();
+ private final Object mConfigurationCallbackMapLock = new Object();
+
+ @GuardedBy("mConfigurationCallbackMapLock")
+ private final Map<Consumer<ThreadConfiguration>, ConfigurationCallbackProxy>
+ mConfigurationCallbackMap = new HashMap<>();
+
/** @hide */
public ThreadNetworkController(@NonNull IThreadNetworkController controllerService) {
requireNonNull(controllerService, "controllerService cannot be null");
@@ -579,6 +586,95 @@
}
/**
+ * Configures the Thread features for this device.
+ *
+ * <p>This method sets the {@link ThreadConfiguration} for this device. On success, the {@link
+ * OutcomeReceiver#onResult} will be called, and the {@code configuration} will be applied and
+ * persisted to the device; the configuration changes can be observed by {@link
+ * #registerConfigurationCallback}. On failure, {@link OutcomeReceiver#onError} of {@code
+ * receiver} will be invoked with a specific error.
+ *
+ * @param configuration the configuration to set
+ * @param executor the executor to execute {@code receiver}
+ * @param receiver the receiver to receive result of this operation
+ * @hide
+ */
+ // @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
+ // @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED)
+ public void setConfiguration(
+ @NonNull ThreadConfiguration configuration,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) {
+ requireNonNull(configuration, "Configuration cannot be null");
+ requireNonNull(executor, "executor cannot be null");
+ requireNonNull(receiver, "receiver cannot be null");
+ try {
+ mControllerService.setConfiguration(
+ configuration, new OperationReceiverProxy(executor, receiver));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers a callback to be called when the configuration is changed.
+ *
+ * <p>Upon return of this method, {@code callback} will be invoked immediately with the current
+ * {@link ThreadConfiguration}.
+ *
+ * @param executor the executor to execute the {@code callback}
+ * @param callback the callback to receive Thread configuration changes
+ * @throws IllegalArgumentException if {@code callback} has already been registered
+ */
+ @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
+ @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED)
+ public void registerConfigurationCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<ThreadConfiguration> callback) {
+ requireNonNull(executor, "executor cannot be null");
+ requireNonNull(callback, "callback cannot be null");
+ synchronized (mConfigurationCallbackMapLock) {
+ if (mConfigurationCallbackMap.containsKey(callback)) {
+ throw new IllegalArgumentException("callback has already been registered");
+ }
+ ConfigurationCallbackProxy callbackProxy =
+ new ConfigurationCallbackProxy(executor, callback);
+ mConfigurationCallbackMap.put(callback, callbackProxy);
+ try {
+ mControllerService.registerConfigurationCallback(callbackProxy);
+ } catch (RemoteException e) {
+ mConfigurationCallbackMap.remove(callback);
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Unregisters the configuration callback.
+ *
+ * @param callback the callback which has been registered with {@link
+ * #registerConfigurationCallback}
+ * @throws IllegalArgumentException if {@code callback} hasn't been registered
+ */
+ @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
+ @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED)
+ public void unregisterConfigurationCallback(@NonNull Consumer<ThreadConfiguration> callback) {
+ requireNonNull(callback, "callback cannot be null");
+ synchronized (mConfigurationCallbackMapLock) {
+ ConfigurationCallbackProxy callbackProxy = mConfigurationCallbackMap.get(callback);
+ if (callbackProxy == null) {
+ throw new IllegalArgumentException("callback hasn't been registered");
+ }
+ try {
+ mControllerService.unregisterConfigurationCallback(callbackProxy);
+ mConfigurationCallbackMap.remove(callbackProxy.mConfigurationConsumer);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* Sets to use a specified test network as the upstream.
*
* @param testNetworkInterfaceName The name of the test network interface. When it's null,
@@ -764,4 +860,26 @@
propagateError(mExecutor, mResultReceiver, errorCode, errorMessage);
}
}
+
+ private static final class ConfigurationCallbackProxy extends IConfigurationReceiver.Stub {
+ final Executor mExecutor;
+ final Consumer<ThreadConfiguration> mConfigurationConsumer;
+
+ ConfigurationCallbackProxy(
+ @CallbackExecutor Executor executor,
+ Consumer<ThreadConfiguration> ConfigurationConsumer) {
+ this.mExecutor = executor;
+ this.mConfigurationConsumer = ConfigurationConsumer;
+ }
+
+ @Override
+ public void onConfigurationChanged(ThreadConfiguration configuration) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mConfigurationConsumer.accept(configuration));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
}
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkFlags.java b/thread/framework/java/android/net/thread/ThreadNetworkFlags.java
index e6ab988..691bbf5 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkFlags.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkFlags.java
@@ -27,5 +27,9 @@
/** @hide */
public static final String FLAG_THREAD_ENABLED = "com.android.net.thread.flags.thread_enabled";
+ /** @hide */
+ public static final String FLAG_CONFIGURATION_ENABLED =
+ "com.android.net.thread.flags.configuration_enabled";
+
private ThreadNetworkFlags() {}
}
diff --git a/thread/scripts/make-pretty.sh b/thread/scripts/make-pretty.sh
index c176bfa..e012d41 100755
--- a/thread/scripts/make-pretty.sh
+++ b/thread/scripts/make-pretty.sh
@@ -1,9 +1,35 @@
#!/usr/bin/env bash
-SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+ANDROID_ROOT_DIR=$(
+ while [ ! -d ".repo" ] && [ "$PWD" != "/" ]; do cd ..; done
+ pwd
+)
-GOOGLE_JAVA_FORMAT=$SCRIPT_DIR/../../../../../prebuilts/tools/common/google-java-format/google-java-format
-ANDROID_BP_FORMAT=$SCRIPT_DIR/../../../../../prebuilts/build-tools/linux-x86/bin/bpfmt
+if [ ! -d "$ANDROID_ROOT_DIR/.repo" ]; then
+ echo "Error: The script has to run in an Android repo checkout"
+ exit 1
+fi
-$GOOGLE_JAVA_FORMAT --aosp -i $(find $SCRIPT_DIR/../ -name "*.java")
-$ANDROID_BP_FORMAT -w $(find $SCRIPT_DIR/../ -name "*.bp")
+GOOGLE_JAVA_FORMAT=$ANDROID_ROOT_DIR/prebuilts/tools/common/google-java-format/google-java-format
+ANDROID_BP_FORMAT=$ANDROID_ROOT_DIR/prebuilts/build-tools/linux-x86/bin/bpfmt
+AIDL_FORMAT=$ANDROID_ROOT_DIR/system/tools/aidl/aidl-format.sh
+
+CONNECTIVITY_DIR=$ANDROID_ROOT_DIR/packages/modules/Connectivity
+OPENTHREAD_DIR=$ANDROID_ROOT_DIR/external/openthread
+OTBR_POSIX_DIR=$ANDROID_ROOT_DIR/external/ot-br-posix
+
+ALLOWED_CODE_DIRS=($CONNECTIVITY_DIR $OPENTHREAD_DIR $OTBR_POSIX_DIR)
+CODE_DIR=$(git rev-parse --show-toplevel)
+
+if [[ ! " ${ALLOWED_CODE_DIRS[@]} " =~ " ${CODE_DIR} " ]]; then
+ echo "Error: The script has to run in the Git project Connectivity, openthread or ot-br-posix"
+ exit 1
+fi
+
+if [[ $CODE_DIR == $CONNECTIVITY_DIR ]]; then
+ CODE_DIR=$CODE_DIR"/thread"
+fi
+
+$GOOGLE_JAVA_FORMAT --aosp -i $(find $CODE_DIR -name "*.java")
+$ANDROID_BP_FORMAT -w $(find $CODE_DIR -name "*.bp")
+$AIDL_FORMAT -w $(find $CODE_DIR -name "*.aidl")
diff --git a/thread/service/java/com/android/server/thread/InfraInterfaceController.java b/thread/service/java/com/android/server/thread/InfraInterfaceController.java
index be54cbc..e72c9ee 100644
--- a/thread/service/java/com/android/server/thread/InfraInterfaceController.java
+++ b/thread/service/java/com/android/server/thread/InfraInterfaceController.java
@@ -16,14 +16,30 @@
package com.android.server.thread;
-import android.os.ParcelFileDescriptor;
+import static android.system.OsConstants.IPPROTO_IPV6;
+import static android.system.OsConstants.IPPROTO_RAW;
+import static android.system.OsConstants.IPV6_CHECKSUM;
+import static android.system.OsConstants.IPV6_MULTICAST_HOPS;
+import static android.system.OsConstants.IPV6_RECVHOPLIMIT;
+import static android.system.OsConstants.IPV6_RECVPKTINFO;
+import static android.system.OsConstants.IPV6_UNICAST_HOPS;
+import android.net.util.SocketUtils;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import java.io.FileDescriptor;
import java.io.IOException;
/** Controller for the infrastructure network interface. */
public class InfraInterfaceController {
private static final String TAG = "InfraIfController";
+ private static final int ENABLE = 1;
+ private static final int IPV6_CHECKSUM_OFFSET = 2;
+ private static final int HOP_LIMIT = 255;
+
static {
System.loadLibrary("service-thread-jni");
}
@@ -37,8 +53,21 @@
* @throws IOException when fails to create the socket.
*/
public ParcelFileDescriptor createIcmp6Socket(String infraInterfaceName) throws IOException {
- return ParcelFileDescriptor.adoptFd(nativeCreateIcmp6Socket(infraInterfaceName));
+ ParcelFileDescriptor parcelFd =
+ ParcelFileDescriptor.adoptFd(nativeCreateFilteredIcmp6Socket());
+ FileDescriptor fd = parcelFd.getFileDescriptor();
+ try {
+ Os.setsockoptInt(fd, IPPROTO_RAW, IPV6_CHECKSUM, IPV6_CHECKSUM_OFFSET);
+ Os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, ENABLE);
+ Os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, ENABLE);
+ Os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, HOP_LIMIT);
+ Os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, HOP_LIMIT);
+ SocketUtils.bindSocketToInterface(fd, infraInterfaceName);
+ } catch (ErrnoException e) {
+ throw new IOException("Failed to setsockopt for the ICMPv6 socket", e);
+ }
+ return parcelFd;
}
- private static native int nativeCreateIcmp6Socket(String interfaceName) throws IOException;
+ private static native int nativeCreateFilteredIcmp6Socket() throws IOException;
}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 0c77dee..b621a6a 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -91,12 +91,14 @@
import android.net.thread.ActiveOperationalDataset.SecurityPolicy;
import android.net.thread.ChannelMaxPower;
import android.net.thread.IActiveOperationalDatasetReceiver;
+import android.net.thread.IConfigurationReceiver;
import android.net.thread.IOperationReceiver;
import android.net.thread.IOperationalDatasetCallback;
import android.net.thread.IStateCallback;
import android.net.thread.IThreadNetworkController;
import android.net.thread.OperationalDatasetTimestamp;
import android.net.thread.PendingOperationalDataset;
+import android.net.thread.ThreadConfiguration;
import android.net.thread.ThreadNetworkController;
import android.net.thread.ThreadNetworkController.DeviceRole;
import android.net.thread.ThreadNetworkException;
@@ -106,10 +108,10 @@
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserManager;
-import android.provider.Settings;
import android.util.Log;
import android.util.SparseArray;
@@ -118,7 +120,7 @@
import com.android.server.ServiceManagerWrapper;
import com.android.server.connectivity.ConnectivityResources;
import com.android.server.thread.openthread.BackboneRouterState;
-import com.android.server.thread.openthread.BorderRouterConfigurationParcel;
+import com.android.server.thread.openthread.BorderRouterConfiguration;
import com.android.server.thread.openthread.DnsTxtAttribute;
import com.android.server.thread.openthread.IChannelMasksReceiver;
import com.android.server.thread.openthread.IOtDaemon;
@@ -137,6 +139,7 @@
import java.time.Clock;
import java.time.DateTimeException;
import java.time.Instant;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -189,6 +192,8 @@
private final OtDaemonCallbackProxy mOtDaemonCallbackProxy = new OtDaemonCallbackProxy();
private final ConnectivityResources mResources;
private final Supplier<String> mCountryCodeSupplier;
+ private final Map<IConfigurationReceiver, IBinder.DeathRecipient> mConfigurationReceivers =
+ new HashMap<>();
// This should not be directly used for calling IOtDaemon APIs because ot-daemon may die and
// {@code mOtDaemon} will be set to {@code null}. Instead, use {@code getOtDaemon()}
@@ -206,10 +211,9 @@
private final ThreadPersistentSettings mPersistentSettings;
private final UserManager mUserManager;
private boolean mUserRestricted;
- private boolean mAirplaneModeOn;
private boolean mForceStopOtDaemonEnabled;
- private BorderRouterConfigurationParcel mBorderRouterConfig;
+ private BorderRouterConfiguration mBorderRouterConfig;
@VisibleForTesting
ThreadNetworkControllerService(
@@ -234,7 +238,11 @@
mInfraIfController = infraIfController;
mUpstreamNetworkRequest = newUpstreamNetworkRequest();
mNetworkToInterface = new HashMap<Network, String>();
- mBorderRouterConfig = new BorderRouterConfigurationParcel();
+ mBorderRouterConfig =
+ new BorderRouterConfiguration.Builder()
+ .setIsBorderRoutingEnabled(true)
+ .setInfraInterfaceName(null)
+ .build();
mPersistentSettings = persistentSettings;
mNsdPublisher = nsdPublisher;
mUserManager = userManager;
@@ -268,10 +276,12 @@
}
private NetworkRequest newUpstreamNetworkRequest() {
- NetworkRequest.Builder builder = new NetworkRequest.Builder().clearCapabilities();
+ NetworkRequest.Builder builder = new NetworkRequest.Builder();
if (mUpstreamTestNetworkSpecifier != null) {
- return builder.addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ // Test networks don't have NET_CAPABILITY_TRUSTED
+ return builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+ .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
.setNetworkSpecifier(mUpstreamTestNetworkSpecifier)
.build();
}
@@ -340,8 +350,8 @@
final String modelName = resources.getString(R.string.config_thread_model_name);
final String vendorName = resources.getString(R.string.config_thread_vendor_name);
final String vendorOui = resources.getString(R.string.config_thread_vendor_oui);
- final boolean managedByGoogle =
- resources.getBoolean(R.bool.config_thread_managed_by_google_home);
+ final String[] vendorSpecificTxts =
+ resources.getStringArray(R.array.config_thread_mdns_vendor_specific_txts);
if (!modelName.isEmpty()) {
if (modelName.getBytes(UTF_8).length > MAX_MODEL_NAME_UTF8_BYTES) {
@@ -371,19 +381,44 @@
meshcopTxts.modelName = modelName;
meshcopTxts.vendorName = vendorName;
meshcopTxts.vendorOui = HexEncoding.decode(vendorOui.replace("-", "").replace(":", ""));
- meshcopTxts.nonStandardTxtEntries = List.of(makeManagedByGoogleTxtAttr(managedByGoogle));
+ meshcopTxts.nonStandardTxtEntries = makeVendorSpecificTxtAttrs(vendorSpecificTxts);
return meshcopTxts;
}
/**
- * Creates a DNS-SD TXT entry for indicating whether Thread on this device is managed by Google.
+ * Parses vendor-specific TXT entries from "=" separated strings into list of {@link
+ * DnsTxtAttribute}.
*
- * @return TXT entry "vgh=1" if {@code managedByGoogle} is {@code true}; otherwise, "vgh=0"
+ * @throws IllegalArgumentsException if invalid TXT entries are found in {@code vendorTxts}
*/
- private static DnsTxtAttribute makeManagedByGoogleTxtAttr(boolean managedByGoogle) {
- final byte[] value = (managedByGoogle ? "1" : "0").getBytes(UTF_8);
- return new DnsTxtAttribute("vgh", value);
+ @VisibleForTesting
+ static List<DnsTxtAttribute> makeVendorSpecificTxtAttrs(String[] vendorTxts) {
+ List<DnsTxtAttribute> txts = new ArrayList<>();
+ for (String txt : vendorTxts) {
+ String[] kv = txt.split("=", 2 /* limit */); // Split with only the first '='
+ if (kv.length < 1) {
+ throw new IllegalArgumentException(
+ "Invalid vendor-specific TXT is found in resources: " + txt);
+ }
+
+ if (kv[0].length() < 2) {
+ throw new IllegalArgumentException(
+ "Invalid vendor-specific TXT key \""
+ + kv[0]
+ + "\": it must contain at least 2 characters");
+ }
+
+ if (!kv[0].startsWith("v")) {
+ throw new IllegalArgumentException(
+ "Invalid vendor-specific TXT key \""
+ + kv[0]
+ + "\": it doesn't start with \"v\"");
+ }
+
+ txts.add(new DnsTxtAttribute(kv[0], (kv.length >= 2 ? kv[1] : "").getBytes(UTF_8)));
+ }
+ return txts;
}
private void onOtDaemonDied() {
@@ -416,8 +451,6 @@
requestThreadNetwork();
mUserRestricted = isThreadUserRestricted();
registerUserRestrictionsReceiver();
- mAirplaneModeOn = isAirplaneModeOn();
- registerAirplaneModeReceiver();
maybeInitializeOtDaemon();
});
}
@@ -499,15 +532,6 @@
// the otDaemon set enabled state operation succeeded or not, so that it can recover
// to the desired value after reboot.
mPersistentSettings.put(ThreadPersistentSettings.THREAD_ENABLED.key, isEnabled);
-
- // Remember whether the user wanted to keep Thread enabled in airplane mode. If once
- // the user disabled Thread again in airplane mode, the persistent settings state is
- // reset (so that Thread will be auto-disabled again when airplane mode is turned on).
- // This behavior is consistent with Wi-Fi and bluetooth.
- if (mAirplaneModeOn) {
- mPersistentSettings.put(
- ThreadPersistentSettings.THREAD_ENABLED_IN_AIRPLANE_MODE.key, isEnabled);
- }
}
try {
@@ -518,6 +542,77 @@
}
}
+ @Override
+ public void setConfiguration(
+ @NonNull ThreadConfiguration configuration, @NonNull IOperationReceiver receiver) {
+ enforceAllPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
+ mHandler.post(() -> setConfigurationInternal(configuration, receiver));
+ }
+
+ private void setConfigurationInternal(
+ @NonNull ThreadConfiguration configuration,
+ @NonNull IOperationReceiver operationReceiver) {
+ checkOnHandlerThread();
+
+ Log.i(TAG, "Set Thread configuration: " + configuration);
+
+ final boolean changed = mPersistentSettings.putConfiguration(configuration);
+ try {
+ operationReceiver.onSuccess();
+ } catch (RemoteException e) {
+ // do nothing if the client is dead
+ }
+ if (changed) {
+ for (IConfigurationReceiver configReceiver : mConfigurationReceivers.keySet()) {
+ try {
+ configReceiver.onConfigurationChanged(configuration);
+ } catch (RemoteException e) {
+ // do nothing if the client is dead
+ }
+ }
+ }
+ }
+
+ @Override
+ public void registerConfigurationCallback(@NonNull IConfigurationReceiver callback) {
+ enforceAllPermissionsGranted(permission.THREAD_NETWORK_PRIVILEGED);
+ mHandler.post(() -> registerConfigurationCallbackInternal(callback));
+ }
+
+ private void registerConfigurationCallbackInternal(@NonNull IConfigurationReceiver callback) {
+ checkOnHandlerThread();
+ if (mConfigurationReceivers.containsKey(callback)) {
+ throw new IllegalStateException("Registering the same IConfigurationReceiver twice");
+ }
+ IBinder.DeathRecipient deathRecipient =
+ () -> mHandler.post(() -> unregisterConfigurationCallbackInternal(callback));
+ try {
+ callback.asBinder().linkToDeath(deathRecipient, 0);
+ } catch (RemoteException e) {
+ return;
+ }
+ mConfigurationReceivers.put(callback, deathRecipient);
+ try {
+ callback.onConfigurationChanged(mPersistentSettings.getConfiguration());
+ } catch (RemoteException e) {
+ // do nothing if the client is dead
+ }
+ }
+
+ @Override
+ public void unregisterConfigurationCallback(@NonNull IConfigurationReceiver callback) {
+ enforceAllPermissionsGranted(permission.THREAD_NETWORK_PRIVILEGED);
+ mHandler.post(() -> unregisterConfigurationCallbackInternal(callback));
+ }
+
+ private void unregisterConfigurationCallbackInternal(@NonNull IConfigurationReceiver callback) {
+ checkOnHandlerThread();
+ if (!mConfigurationReceivers.containsKey(callback)) {
+ return;
+ }
+ callback.asBinder().unlinkToDeath(mConfigurationReceivers.remove(callback), 0);
+ }
+
private void registerUserRestrictionsReceiver() {
mContext.registerReceiver(
new BroadcastReceiver() {
@@ -575,74 +670,13 @@
return mUserManager.hasUserRestriction(DISALLOW_THREAD_NETWORK);
}
- private void registerAirplaneModeReceiver() {
- mContext.registerReceiver(
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- onAirplaneModeChanged(isAirplaneModeOn());
- }
- },
- new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED),
- null /* broadcastPermission */,
- mHandler);
- }
-
- private void onAirplaneModeChanged(boolean newAirplaneModeOn) {
- checkOnHandlerThread();
- if (mAirplaneModeOn == newAirplaneModeOn) {
- return;
- }
- Log.i(TAG, "Airplane mode changed: " + mAirplaneModeOn + " -> " + newAirplaneModeOn);
- mAirplaneModeOn = newAirplaneModeOn;
-
- final boolean shouldEnableThread = shouldEnableThread();
- final IOperationReceiver receiver =
- new IOperationReceiver.Stub() {
- @Override
- public void onSuccess() {
- Log.d(
- TAG,
- (shouldEnableThread ? "Enabled" : "Disabled")
- + " Thread due to airplane mode change");
- }
-
- @Override
- public void onError(int errorCode, String errorMessage) {
- Log.e(
- TAG,
- "Failed to "
- + (shouldEnableThread ? "enable" : "disable")
- + " Thread for airplane mode change");
- }
- };
- // Do not save the user restriction state to persistent settings so that the user
- // configuration won't be overwritten
- setEnabledInternal(
- shouldEnableThread, false /* persist */, new OperationReceiverWrapper(receiver));
- }
-
- /** Returns {@code true} if Airplane mode has been turned on. */
- private boolean isAirplaneModeOn() {
- return Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0)
- == 1;
- }
-
/**
* Returns {@code true} if Thread should be enabled based on current settings, runtime user
- * restriction and airplane mode state.
+ * restriction state.
*/
private boolean shouldEnableThread() {
- final boolean enabledInAirplaneMode =
- mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED_IN_AIRPLANE_MODE);
-
return !mForceStopOtDaemonEnabled
&& !mUserRestricted
- // FIXME(b/340744397): Note that here we need to call `isAirplaneModeOn()` to get
- // the latest state of airplane mode but can't use `mIsAirplaneMode`. This is for
- // avoiding the race conditions described in b/340744397
- && (!isAirplaneModeOn() || enabledInAirplaneMode)
&& mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED);
}
@@ -884,9 +918,7 @@
final byte[] securityFlags = new byte[] {(byte) 0xff, (byte) 0xf8};
return new ActiveOperationalDataset.Builder()
- .setActiveTimestamp(
- new OperationalDatasetTimestamp(
- now.getEpochSecond() & 0xffffffffffffL, 0, authoritative))
+ .setActiveTimestamp(OperationalDatasetTimestamp.fromInstant(now, authoritative))
.setExtendedPanId(newRandomBytes(random, LENGTH_EXTENDED_PAN_ID))
.setPanId(panId)
.setNetworkName(networkName)
@@ -1200,38 +1232,54 @@
}
}
- private void enableBorderRouting(String infraIfName) {
- if (mBorderRouterConfig.isBorderRoutingEnabled
- && infraIfName.equals(mBorderRouterConfig.infraInterfaceName)) {
+ private void configureBorderRouter(BorderRouterConfiguration borderRouterConfig) {
+ if (mBorderRouterConfig.equals(borderRouterConfig)) {
return;
}
- Log.i(TAG, "Enable border routing on AIL: " + infraIfName);
+ Log.i(
+ TAG,
+ "Configuring Border Router: " + mBorderRouterConfig + " -> " + borderRouterConfig);
+ mBorderRouterConfig = borderRouterConfig;
+ ParcelFileDescriptor infraIcmp6Socket = null;
+ if (mBorderRouterConfig.infraInterfaceName != null) {
+ try {
+ infraIcmp6Socket =
+ mInfraIfController.createIcmp6Socket(
+ mBorderRouterConfig.infraInterfaceName);
+ } catch (IOException e) {
+ Log.i(TAG, "Failed to create ICMPv6 socket on infra network interface", e);
+ }
+ }
try {
- mBorderRouterConfig.infraInterfaceName = infraIfName;
- mBorderRouterConfig.infraInterfaceIcmp6Socket =
- mInfraIfController.createIcmp6Socket(infraIfName);
- mBorderRouterConfig.isBorderRoutingEnabled = true;
-
getOtDaemon()
.configureBorderRouter(
- mBorderRouterConfig, new ConfigureBorderRouterStatusReceiver());
- } catch (RemoteException | IOException | ThreadNetworkException e) {
- Log.w(TAG, "Failed to enable border routing", e);
+ mBorderRouterConfig,
+ infraIcmp6Socket,
+ new ConfigureBorderRouterStatusReceiver());
+ } catch (RemoteException | ThreadNetworkException e) {
+ Log.w(TAG, "Failed to configure border router " + mBorderRouterConfig, e);
}
}
+ private void enableBorderRouting(String infraIfName) {
+ BorderRouterConfiguration borderRouterConfig =
+ newBorderRouterConfigBuilder(mBorderRouterConfig)
+ .setIsBorderRoutingEnabled(true)
+ .setInfraInterfaceName(infraIfName)
+ .build();
+ Log.i(TAG, "Enable border routing on AIL: " + infraIfName);
+ configureBorderRouter(borderRouterConfig);
+ }
+
private void disableBorderRouting() {
mUpstreamNetwork = null;
- mBorderRouterConfig.infraInterfaceName = null;
- mBorderRouterConfig.infraInterfaceIcmp6Socket = null;
- mBorderRouterConfig.isBorderRoutingEnabled = false;
- try {
- getOtDaemon()
- .configureBorderRouter(
- mBorderRouterConfig, new ConfigureBorderRouterStatusReceiver());
- } catch (RemoteException | ThreadNetworkException e) {
- Log.w(TAG, "Failed to disable border routing", e);
- }
+ BorderRouterConfiguration borderRouterConfig =
+ newBorderRouterConfigBuilder(mBorderRouterConfig)
+ .setIsBorderRoutingEnabled(false)
+ .setInfraInterfaceName(null)
+ .build();
+ Log.i(TAG, "Disabling border routing");
+ configureBorderRouter(borderRouterConfig);
}
private void handleThreadInterfaceStateChanged(boolean isUp) {
@@ -1332,6 +1380,13 @@
return builder.build();
}
+ private static BorderRouterConfiguration.Builder newBorderRouterConfigBuilder(
+ BorderRouterConfiguration brConfig) {
+ return new BorderRouterConfiguration.Builder()
+ .setIsBorderRoutingEnabled(brConfig.isBorderRoutingEnabled)
+ .setInfraInterfaceName(brConfig.infraInterfaceName);
+ }
+
private static final class CallbackMetadata {
private static long gId = 0;
diff --git a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
index f18aac9..7c4c72d 100644
--- a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
+++ b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
@@ -18,9 +18,11 @@
import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ApexEnvironment;
import android.content.Context;
+import android.net.thread.ThreadConfiguration;
import android.os.PersistableBundle;
import android.util.AtomicFile;
import android.util.Log;
@@ -74,6 +76,16 @@
/** Stores the Thread country code, null if no country code is stored. */
public static final Key<String> THREAD_COUNTRY_CODE = new Key<>("thread_country_code", null);
+ /** Stores the Thread NAT64 feature toggle state, true for enabled and false for disabled. */
+ private static final Key<Boolean> CONFIG_NAT64_ENABLED =
+ new Key<>("config_nat64_enabled", false);
+
+ /**
+ * Stores the Thread DHCPv6-PD feature toggle state, true for enabled and false for disabled.
+ */
+ private static final Key<Boolean> CONFIG_DHCP6_PD_ENABLED =
+ new Key<>("config_dhcp6_pd_enabled", false);
+
/******** Thread persistent setting keys ***************/
@GuardedBy("mLock")
@@ -175,6 +187,30 @@
}
/**
+ * Store a {@link ThreadConfiguration} to the persistent settings.
+ *
+ * @param configuration {@link ThreadConfiguration} to be stored.
+ * @return {@code true} if the configuration was changed, {@code false} otherwise.
+ */
+ public boolean putConfiguration(@NonNull ThreadConfiguration configuration) {
+ if (getConfiguration().equals(configuration)) {
+ return false;
+ }
+ putObject(CONFIG_NAT64_ENABLED.key, configuration.isNat64Enabled());
+ putObject(CONFIG_DHCP6_PD_ENABLED.key, configuration.isDhcpv6PdEnabled());
+ writeToStoreFile();
+ return true;
+ }
+
+ /** Retrieve the {@link ThreadConfiguration} from the persistent settings. */
+ public ThreadConfiguration getConfiguration() {
+ return new ThreadConfiguration.Builder()
+ .setNat64Enabled(get(CONFIG_NAT64_ENABLED))
+ .setDhcpv6PdEnabled(get(CONFIG_DHCP6_PD_ENABLED))
+ .build();
+ }
+
+ /**
* Base class to store string key and its default value.
*
* @param <T> Type of the value.
diff --git a/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp b/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp
index 5d24eab..1f260f2 100644
--- a/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp
+++ b/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp
@@ -42,15 +42,8 @@
namespace android {
static jint
-com_android_server_thread_InfraInterfaceController_createIcmp6Socket(JNIEnv *env, jobject clazz,
- jstring interfaceName) {
- ScopedUtfChars ifName(env, interfaceName);
-
- struct icmp6_filter filter;
- constexpr int kEnable = 1;
- constexpr int kIpv6ChecksumOffset = 2;
- constexpr int kHopLimit = 255;
-
+com_android_server_thread_InfraInterfaceController_createFilteredIcmp6Socket(JNIEnv *env,
+ jobject clazz) {
// Initializes the ICMPv6 socket.
int sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
if (sock == -1) {
@@ -59,6 +52,7 @@
return -1;
}
+ struct icmp6_filter filter;
// Only accept Router Advertisements, Router Solicitations and Neighbor
// Advertisements.
ICMP6_FILTER_SETBLOCKALL(&filter);
@@ -73,53 +67,6 @@
return -1;
}
- // We want a source address and interface index.
-
- if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &kEnable, sizeof(kEnable)) != 0) {
- jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_RECVPKTINFO (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
- if (setsockopt(sock, IPPROTO_RAW, IPV6_CHECKSUM, &kIpv6ChecksumOffset,
- sizeof(kIpv6ChecksumOffset)) != 0) {
- jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_CHECKSUM (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
- // We need to be able to reject RAs arriving from off-link.
- if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &kEnable, sizeof(kEnable)) != 0) {
- jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_RECVHOPLIMIT (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
- if (setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &kHopLimit, sizeof(kHopLimit)) != 0) {
- jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_UNICAST_HOPS (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
- if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &kHopLimit, sizeof(kHopLimit)) != 0) {
- jniThrowExceptionFmt(env, "java/io/IOException",
- "failed to create the setsockopt IPV6_MULTICAST_HOPS (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
- if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifName.c_str(), strlen(ifName.c_str()))) {
- jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt SO_BINDTODEVICE (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
return sock;
}
@@ -129,8 +76,8 @@
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
- {"nativeCreateIcmp6Socket", "(Ljava/lang/String;)I",
- (void *)com_android_server_thread_InfraInterfaceController_createIcmp6Socket},
+ {"nativeCreateFilteredIcmp6Socket", "()I",
+ (void *)com_android_server_thread_InfraInterfaceController_createFilteredIcmp6Socket},
};
int register_com_android_server_thread_InfraInterfaceController(JNIEnv *env) {
diff --git a/thread/tests/cts/Android.bp b/thread/tests/cts/Android.bp
index c1cf0a0..6db7c9c 100644
--- a/thread/tests/cts/Android.bp
+++ b/thread/tests/cts/Android.bp
@@ -21,9 +21,11 @@
android_test {
name: "CtsThreadNetworkTestCases",
- defaults: ["cts_defaults"],
+ defaults: [
+ "cts_defaults",
+ "framework-connectivity-test-defaults",
+ ],
min_sdk_version: "33",
- sdk_version: "test_current",
manifest: "AndroidManifest.xml",
test_config: "AndroidTest.xml",
srcs: [
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadConfigurationTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadConfigurationTest.java
new file mode 100644
index 0000000..386412e
--- /dev/null
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadConfigurationTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 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.thread.cts;
+
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.thread.ThreadConfiguration;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/** Tests for {@link ThreadConfiguration}. */
+@SmallTest
+@RequiresThreadFeature
+@RunWith(Parameterized.class)
+public final class ThreadConfigurationTest {
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
+ public final boolean mIsNat64Enabled;
+ public final boolean mIsDhcpv6PdEnabled;
+
+ @Parameterized.Parameters
+ public static Collection configArguments() {
+ return Arrays.asList(
+ new Object[][] {
+ {false, false}, // All disabled
+ {true, false}, // NAT64 enabled
+ {false, true}, // DHCP6-PD enabled
+ {true, true}, // All enabled
+ });
+ }
+
+ public ThreadConfigurationTest(boolean isNat64Enabled, boolean isDhcpv6PdEnabled) {
+ mIsNat64Enabled = isNat64Enabled;
+ mIsDhcpv6PdEnabled = isDhcpv6PdEnabled;
+ }
+
+ @Test
+ public void parcelable_parcelingIsLossLess() {
+ ThreadConfiguration config =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(mIsNat64Enabled)
+ .setDhcpv6PdEnabled(mIsDhcpv6PdEnabled)
+ .build();
+ assertParcelingIsLossless(config);
+ }
+
+ @Test
+ public void builder_correctValuesAreSet() {
+ ThreadConfiguration config =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(mIsNat64Enabled)
+ .setDhcpv6PdEnabled(mIsDhcpv6PdEnabled)
+ .build();
+
+ assertThat(config.isNat64Enabled()).isEqualTo(mIsNat64Enabled);
+ assertThat(config.isDhcpv6PdEnabled()).isEqualTo(mIsDhcpv6PdEnabled);
+ }
+
+ @Test
+ public void builderConstructor_configsAreEqual() {
+ ThreadConfiguration config1 =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(mIsNat64Enabled)
+ .setDhcpv6PdEnabled(mIsDhcpv6PdEnabled)
+ .build();
+ ThreadConfiguration config2 = new ThreadConfiguration.Builder(config1).build();
+ assertThat(config1).isEqualTo(config2);
+ }
+}
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
index 0e95703..1a101b6 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -45,6 +45,7 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
+
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
@@ -57,6 +58,7 @@
import android.net.thread.ActiveOperationalDataset;
import android.net.thread.OperationalDatasetTimestamp;
import android.net.thread.PendingOperationalDataset;
+import android.net.thread.ThreadConfiguration;
import android.net.thread.ThreadNetworkController;
import android.net.thread.ThreadNetworkController.OperationalDatasetCallback;
import android.net.thread.ThreadNetworkController.StateCallback;
@@ -94,9 +96,11 @@
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
import java.util.function.Predicate;
/** CTS tests for {@link ThreadNetworkController}. */
@@ -109,11 +113,14 @@
private static final int NETWORK_CALLBACK_TIMEOUT_MILLIS = 10 * 1000;
private static final int CALLBACK_TIMEOUT_MILLIS = 1_000;
private static final int ENABLED_TIMEOUT_MILLIS = 2_000;
+ private static final int SET_CONFIGURATION_TIMEOUT_MILLIS = 1_000;
private static final int SERVICE_DISCOVERY_TIMEOUT_MILLIS = 30_000;
private static final int SERVICE_LOST_TIMEOUT_MILLIS = 20_000;
private static final String MESHCOP_SERVICE_TYPE = "_meshcop._udp";
private static final String THREAD_NETWORK_PRIVILEGED =
"android.permission.THREAD_NETWORK_PRIVILEGED";
+ private static final ThreadConfiguration DEFAULT_CONFIG =
+ new ThreadConfiguration.Builder().build();
@Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
@@ -126,6 +133,9 @@
private HandlerThread mHandlerThread;
private TapTestNetworkTracker mTestNetworkTracker;
+ private final List<Consumer<ThreadConfiguration>> mConfigurationCallbacksToCleanUp =
+ new ArrayList<>();
+
@Before
public void setUp() throws Exception {
mController =
@@ -140,6 +150,7 @@
mHandlerThread.start();
setEnabledAndWait(mController, true);
+ setConfigurationAndWait(mController, DEFAULT_CONFIG);
}
@After
@@ -147,6 +158,18 @@
dropAllPermissions();
leaveAndWait(mController);
tearDownTestNetwork();
+ setConfigurationAndWait(mController, DEFAULT_CONFIG);
+ for (Consumer<ThreadConfiguration> configurationCallback :
+ mConfigurationCallbacksToCleanUp) {
+ try {
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> mController.unregisterConfigurationCallback(configurationCallback));
+ } catch (IllegalArgumentException e) {
+ // Ignore the exception when the callback is not registered.
+ }
+ }
+ mConfigurationCallbacksToCleanUp.clear();
}
@Test
@@ -831,6 +854,152 @@
NET_CAPABILITY_TRUSTED);
}
+ @Test
+ public void setConfiguration_null_throwsNullPointerException() throws Exception {
+ CompletableFuture<Void> setConfigFuture = new CompletableFuture<>();
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mController.setConfiguration(
+ null, mExecutor, newOutcomeReceiver(setConfigFuture)));
+ }
+
+ @Test
+ public void setConfiguration_noPermissions_throwsSecurityException() throws Exception {
+ ThreadConfiguration configuration =
+ new ThreadConfiguration.Builder().setNat64Enabled(true).build();
+ CompletableFuture<Void> setConfigFuture = new CompletableFuture<>();
+ assertThrows(
+ SecurityException.class,
+ () -> {
+ mController.setConfiguration(
+ configuration, mExecutor, newOutcomeReceiver(setConfigFuture));
+ });
+ }
+
+ @Test
+ public void registerConfigurationCallback_permissionsGranted_returnsCurrentStatus()
+ throws Exception {
+ CompletableFuture<ThreadConfiguration> getConfigFuture = new CompletableFuture<>();
+ Consumer<ThreadConfiguration> callback = getConfigFuture::complete;
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> registerConfigurationCallback(mController, mExecutor, callback));
+ assertThat(getConfigFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS))
+ .isEqualTo(DEFAULT_CONFIG);
+ }
+
+ @Test
+ public void registerConfigurationCallback_noPermissions_throwsSecurityException()
+ throws Exception {
+ dropAllPermissions();
+
+ assertThrows(
+ SecurityException.class,
+ () -> registerConfigurationCallback(mController, mExecutor, config -> {}));
+ }
+
+ @Test
+ public void registerConfigurationCallback_returnsUpdatedConfigurations() throws Exception {
+ CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
+ CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
+ ConfigurationListener listener = new ConfigurationListener(mController);
+ ThreadConfiguration config1 =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(true)
+ .setDhcpv6PdEnabled(true)
+ .build();
+ ThreadConfiguration config2 =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(false)
+ .setDhcpv6PdEnabled(true)
+ .build();
+
+ try {
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () ->
+ mController.setConfiguration(
+ config1, mExecutor, newOutcomeReceiver(setFuture1)));
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () ->
+ mController.setConfiguration(
+ config2, mExecutor, newOutcomeReceiver(setFuture2)));
+ setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+ setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+
+ listener.expectConfiguration(DEFAULT_CONFIG);
+ listener.expectConfiguration(config1);
+ listener.expectConfiguration(config2);
+ listener.expectNoMoreConfiguration();
+ } finally {
+ listener.unregisterConfigurationCallback();
+ }
+ }
+
+ @Test
+ public void registerConfigurationCallback_alreadyRegistered_throwsIllegalArgumentException()
+ throws Exception {
+ grantPermissions(THREAD_NETWORK_PRIVILEGED);
+
+ Consumer<ThreadConfiguration> callback = config -> {};
+ registerConfigurationCallback(mController, mExecutor, callback);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> registerConfigurationCallback(mController, mExecutor, callback));
+ }
+
+ @Test
+ public void unregisterConfigurationCallback_noPermissions_throwsSecurityException()
+ throws Exception {
+ Consumer<ThreadConfiguration> callback = config -> {};
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> registerConfigurationCallback(mController, mExecutor, callback));
+
+ assertThrows(
+ SecurityException.class,
+ () -> mController.unregisterConfigurationCallback(callback));
+ }
+
+ @Test
+ public void unregisterConfigurationCallback_callbackRegistered_success() throws Exception {
+ Consumer<ThreadConfiguration> callback = config -> {};
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> {
+ registerConfigurationCallback(mController, mExecutor, callback);
+ mController.unregisterConfigurationCallback(callback);
+ });
+ }
+
+ @Test
+ public void
+ unregisterConfigurationCallback_callbackNotRegistered_throwsIllegalArgumentException()
+ throws Exception {
+ Consumer<ThreadConfiguration> callback = config -> {};
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mController.unregisterConfigurationCallback(callback));
+ }
+
+ @Test
+ public void unregisterConfigurationCallback_alreadyUnregistered_throwsIllegalArgumentException()
+ throws Exception {
+ grantPermissions(THREAD_NETWORK_PRIVILEGED);
+
+ Consumer<ThreadConfiguration> callback = config -> {};
+ registerConfigurationCallback(mController, mExecutor, callback);
+ mController.unregisterConfigurationCallback(callback);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mController.unregisterConfigurationCallback(callback));
+ }
+
private void grantPermissions(String... permissions) {
for (String permission : permissions) {
mGrantedPermissions.add(permission);
@@ -858,7 +1027,6 @@
assertThat(txtMap.get("rv")).isNotNull();
assertThat(txtMap.get("tv")).isNotNull();
assertThat(txtMap.get("sb")).isNotNull();
- assertThat(new String(txtMap.get("vgh"))).isIn(List.of("0", "1"));
}
@Test
@@ -885,7 +1053,6 @@
assertThat(txtMap.get("tv")).isNotNull();
assertThat(txtMap.get("sb")).isNotNull();
assertThat(txtMap.get("id").length).isEqualTo(16);
- assertThat(new String(txtMap.get("vgh"))).isIn(List.of("0", "1"));
}
@Test
@@ -1039,6 +1206,35 @@
}
}
+ private class ConfigurationListener {
+ private ArrayTrackRecord<ThreadConfiguration> mConfigurations = new ArrayTrackRecord<>();
+ private final ArrayTrackRecord<ThreadConfiguration>.ReadHead mReadHead =
+ mConfigurations.newReadHead();
+ ThreadNetworkController mController;
+ Consumer<ThreadConfiguration> mCallback = (config) -> mConfigurations.add(config);
+
+ ConfigurationListener(ThreadNetworkController controller) {
+ this.mController = controller;
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> controller.registerConfigurationCallback(mExecutor, mCallback));
+ }
+
+ public void expectConfiguration(ThreadConfiguration config) {
+ assertThat(mReadHead.poll(CALLBACK_TIMEOUT_MILLIS, c -> c.equals(config))).isNotNull();
+ }
+
+ public void expectNoMoreConfiguration() {
+ assertThat(mReadHead.poll(CALLBACK_TIMEOUT_MILLIS, c -> true)).isNull();
+ }
+
+ public void unregisterConfigurationCallback() {
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> mController.unregisterConfigurationCallback(mCallback));
+ }
+ }
+
private int booleanToEnabledState(boolean enabled) {
return enabled ? STATE_ENABLED : STATE_DISABLED;
}
@@ -1053,6 +1249,18 @@
waitForEnabledState(controller, booleanToEnabledState(enabled));
}
+ private void setConfigurationAndWait(
+ ThreadNetworkController controller, ThreadConfiguration configuration)
+ throws Exception {
+ CompletableFuture<Void> setFuture = new CompletableFuture<>();
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () ->
+ controller.setConfiguration(
+ configuration, mExecutor, newOutcomeReceiver(setFuture)));
+ setFuture.get(SET_CONFIGURATION_TIMEOUT_MILLIS, MILLISECONDS);
+ }
+
private CompletableFuture joinRandomizedDataset(
ThreadNetworkController controller, String networkName) throws Exception {
ActiveOperationalDataset activeDataset = newRandomizedDataset(networkName, controller);
@@ -1119,6 +1327,14 @@
};
}
+ private void registerConfigurationCallback(
+ ThreadNetworkController controller,
+ Executor executor,
+ Consumer<ThreadConfiguration> callback) {
+ controller.registerConfigurationCallback(executor, callback);
+ mConfigurationCallbacksToCleanUp.add(callback);
+ }
+
private static void assertDoesNotThrow(ThrowingRunnable runnable) {
try {
runnable.run();
diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
index 8c63d37..9e8dc3a 100644
--- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
+++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
@@ -17,23 +17,26 @@
package android.net.thread;
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
-import static android.net.thread.utils.IntegrationTestUtils.JOIN_TIMEOUT;
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.thread.utils.IntegrationTestUtils.DEFAULT_DATASET;
import static android.net.thread.utils.IntegrationTestUtils.getIpv6LinkAddresses;
+import static android.net.thread.utils.IntegrationTestUtils.isExpectedIcmpv4Packet;
import static android.net.thread.utils.IntegrationTestUtils.isExpectedIcmpv6Packet;
-import static android.net.thread.utils.IntegrationTestUtils.isFromIpv6Source;
+import static android.net.thread.utils.IntegrationTestUtils.isFrom;
import static android.net.thread.utils.IntegrationTestUtils.isInMulticastGroup;
-import static android.net.thread.utils.IntegrationTestUtils.isToIpv6Destination;
+import static android.net.thread.utils.IntegrationTestUtils.isTo;
+import static android.net.thread.utils.IntegrationTestUtils.joinNetworkAndWaitForOmr;
import static android.net.thread.utils.IntegrationTestUtils.newPacketReader;
import static android.net.thread.utils.IntegrationTestUtils.pollForPacket;
import static android.net.thread.utils.IntegrationTestUtils.sendUdpMessage;
import static android.net.thread.utils.IntegrationTestUtils.waitFor;
+import static android.system.OsConstants.ICMP_ECHO;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
import static com.android.testutils.TestPermissionUtil.runAsShell;
-import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -44,11 +47,11 @@
import static java.util.Objects.requireNonNull;
import android.content.Context;
-import android.net.InetAddresses;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MacAddress;
+import android.net.RouteInfo;
import android.net.thread.utils.FullThreadDevice;
import android.net.thread.utils.InfraNetworkDevice;
import android.net.thread.utils.OtDaemonController;
@@ -74,7 +77,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.net.Inet4Address;
import java.net.Inet6Address;
+import java.net.InetAddress;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
@@ -89,22 +94,14 @@
private static final String TAG = BorderRoutingTest.class.getSimpleName();
private static final int NUM_FTD = 2;
private static final Inet6Address GROUP_ADDR_SCOPE_5 =
- (Inet6Address) InetAddresses.parseNumericAddress("ff05::1234");
+ (Inet6Address) parseNumericAddress("ff05::1234");
private static final Inet6Address GROUP_ADDR_SCOPE_4 =
- (Inet6Address) InetAddresses.parseNumericAddress("ff04::1234");
+ (Inet6Address) parseNumericAddress("ff04::1234");
private static final Inet6Address GROUP_ADDR_SCOPE_3 =
- (Inet6Address) InetAddresses.parseNumericAddress("ff03::1234");
-
- // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
- private static final byte[] DEFAULT_DATASET_TLVS =
- base16().decode(
- "0E080000000000010000000300001335060004001FFFE002"
- + "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
- + "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
- + "642D643961300102D9A00410A245479C836D551B9CA557F7"
- + "B9D351B40C0402A0FFF8");
- private static final ActiveOperationalDataset DEFAULT_DATASET =
- ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS);
+ (Inet6Address) parseNumericAddress("ff03::1234");
+ private static final Inet4Address IPV4_SERVER_ADDR =
+ (Inet4Address) parseNumericAddress("8.8.8.8");
+ private static final String NAT64_CIDR = "192.168.255.0/24";
@Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
@@ -171,12 +168,12 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
mInfraDevice.sendEchoRequest(ftd.getOmrAddress());
// Infra device receives an echo reply sent by FTD.
- assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
+ assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
}
@Test
@@ -193,11 +190,11 @@
startInfraDeviceAndWaitForOnLinkAddr();
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
mInfraDevice.sendEchoRequest(ftd.getOmrAddress());
- assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
+ assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
}
@Test
@@ -213,7 +210,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
Inet6Address ftdOmr = ftd.getOmrAddress();
// Create a new infra network and let Thread prefer it
TestNetworkTracker oldInfraNetworkTracker = mInfraNetworkTracker;
@@ -224,7 +221,7 @@
mInfraDevice.sendEchoRequest(ftd.getOmrAddress());
- assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftdOmr));
+ assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftdOmr));
} finally {
runAsShell(MANAGE_TEST_NETWORKS, () -> oldInfraNetworkTracker.teardown());
}
@@ -243,7 +240,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
Inet6Address ftdOmr = requireNonNull(ftd.getOmrAddress());
Inet6Address ftdMlEid = requireNonNull(ftd.getMlEid());
@@ -285,7 +282,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
ftd.subscribeMulticastAddress(GROUP_ADDR_SCOPE_5);
@@ -307,7 +304,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
ftd.subscribeMulticastAddress(GROUP_ADDR_SCOPE_3);
@@ -328,12 +325,12 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
subscribeMulticastAddressAndWait(ftd, GROUP_ADDR_SCOPE_5);
mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
- assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
+ assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
}
@Test
@@ -360,12 +357,12 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
ftd.subscribeMulticastAddress(GROUP_ADDR_SCOPE_3);
mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_3);
- assertNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
+ assertNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
}
@Test
@@ -382,11 +379,11 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_4);
- assertNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
+ assertNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
}
@Test
@@ -407,22 +404,24 @@
*/
FullThreadDevice ftd1 = mFtds.get(0);
- startFtdChild(ftd1);
+ joinNetworkAndWaitForOmr(ftd1, DEFAULT_DATASET);
subscribeMulticastAddressAndWait(ftd1, GROUP_ADDR_SCOPE_5);
FullThreadDevice ftd2 = mFtds.get(1);
- startFtdChild(ftd2);
+ joinNetworkAndWaitForOmr(ftd2, DEFAULT_DATASET);
subscribeMulticastAddressAndWait(ftd2, GROUP_ADDR_SCOPE_4);
mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
- assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd1.getOmrAddress()));
+ assertNotNull(
+ pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd1.getOmrAddress()));
// Verify ping reply from ftd1 and ftd2 separately as the order of replies can't be
// predicted.
mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_4);
- assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd2.getOmrAddress()));
+ assertNotNull(
+ pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd2.getOmrAddress()));
}
@Test
@@ -443,21 +442,23 @@
*/
FullThreadDevice ftd1 = mFtds.get(0);
- startFtdChild(ftd1);
+ joinNetworkAndWaitForOmr(ftd1, DEFAULT_DATASET);
subscribeMulticastAddressAndWait(ftd1, GROUP_ADDR_SCOPE_5);
FullThreadDevice ftd2 = mFtds.get(1);
- startFtdChild(ftd2);
+ joinNetworkAndWaitForOmr(ftd2, DEFAULT_DATASET);
subscribeMulticastAddressAndWait(ftd2, GROUP_ADDR_SCOPE_5);
mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
- assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd1.getOmrAddress()));
+ assertNotNull(
+ pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd1.getOmrAddress()));
// Send the request twice as the order of replies from ftd1 and ftd2 are not guaranteed
mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
- assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd2.getOmrAddress()));
+ assertNotNull(
+ pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd2.getOmrAddress()));
}
@Test
@@ -473,16 +474,18 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
Inet6Address ftdOmr = ftd.getOmrAddress();
ftd.ping(GROUP_ADDR_SCOPE_5);
ftd.ping(GROUP_ADDR_SCOPE_4);
assertNotNull(
- pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_5));
+ pollForIcmpPacketOnInfraNetwork(
+ ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_5));
assertNotNull(
- pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4));
+ pollForIcmpPacketOnInfraNetwork(
+ ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4));
}
@Test
@@ -499,12 +502,12 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
ftd.ping(GROUP_ADDR_SCOPE_3);
assertNull(
- pollForPacketOnInfraNetwork(
+ pollForIcmpPacketOnInfraNetwork(
ICMPV6_ECHO_REQUEST_TYPE, ftd.getOmrAddress(), GROUP_ADDR_SCOPE_3));
}
@@ -521,14 +524,15 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
Inet6Address ftdLla = ftd.getLinkLocalAddress();
assertNotNull(ftdLla);
ftd.ping(GROUP_ADDR_SCOPE_4, ftdLla);
assertNull(
- pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdLla, GROUP_ADDR_SCOPE_4));
+ pollForIcmpPacketOnInfraNetwork(
+ ICMPV6_ECHO_REQUEST_TYPE, ftdLla, GROUP_ADDR_SCOPE_4));
}
@Test
@@ -544,7 +548,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
List<Inet6Address> ftdMlas = ftd.getMeshLocalAddresses();
assertFalse(ftdMlas.isEmpty());
@@ -552,7 +556,7 @@
ftd.ping(GROUP_ADDR_SCOPE_4, ftdMla);
assertNull(
- pollForPacketOnInfraNetwork(
+ pollForIcmpPacketOnInfraNetwork(
ICMPV6_ECHO_REQUEST_TYPE, ftdMla, GROUP_ADDR_SCOPE_4));
}
}
@@ -571,7 +575,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
subscribeMulticastAddressAndWait(ftd, GROUP_ADDR_SCOPE_5);
Inet6Address ftdOmr = ftd.getOmrAddress();
@@ -583,7 +587,7 @@
mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
- assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftdOmr));
+ assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftdOmr));
}
@Test
@@ -599,7 +603,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
Inet6Address ftdOmr = ftd.getOmrAddress();
// Destroy infra link and re-create
@@ -611,33 +615,46 @@
ftd.ping(GROUP_ADDR_SCOPE_4);
assertNotNull(
- pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4));
+ pollForIcmpPacketOnInfraNetwork(
+ ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4));
+ }
+
+ @Test
+ public void nat64_threadDevicePingIpv4InfraDevice_outboundPacketIsForwarded() throws Exception {
+ FullThreadDevice ftd = mFtds.get(0);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
+ // TODO: enable NAT64 via ThreadNetworkController API instead of ot-ctl
+ mOtCtl.setNat64Cidr(NAT64_CIDR);
+ mOtCtl.setNat64Enabled(true);
+ waitFor(() -> mOtCtl.hasNat64PrefixInNetdata(), Duration.ofSeconds(10));
+
+ ftd.ping(IPV4_SERVER_ADDR);
+
+ assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMP_ECHO, null, IPV4_SERVER_ADDR));
}
private void setUpInfraNetwork() throws Exception {
+ LinkProperties lp = new LinkProperties();
+ // NAT64 feature requires the infra network to have an IPv4 default route.
+ lp.addRoute(
+ new RouteInfo(
+ new IpPrefix("0.0.0.0/0") /* destination */,
+ null /* gateway */,
+ null,
+ RouteInfo.RTN_UNICAST,
+ 1500 /* mtu */));
mInfraNetworkTracker =
runAsShell(
MANAGE_TEST_NETWORKS,
- () ->
- initTestNetwork(
- mContext, new LinkProperties(), 5000 /* timeoutMs */));
- mController.setTestNetworkAsUpstreamAndWait(
- mInfraNetworkTracker.getTestIface().getInterfaceName());
+ () -> initTestNetwork(mContext, lp, 5000 /* timeoutMs */));
+ String infraNetworkName = mInfraNetworkTracker.getTestIface().getInterfaceName();
+ mController.setTestNetworkAsUpstreamAndWait(infraNetworkName);
}
private void tearDownInfraNetwork() {
runAsShell(MANAGE_TEST_NETWORKS, () -> mInfraNetworkTracker.teardown());
}
- private void startFtdChild(FullThreadDevice ftd) throws Exception {
- ftd.factoryReset();
- ftd.joinNetwork(DEFAULT_DATASET);
- ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
- waitFor(() -> ftd.getOmrAddress() != null, Duration.ofSeconds(60));
- Inet6Address ftdOmr = ftd.getOmrAddress();
- assertNotNull(ftdOmr);
- }
-
private void startInfraDeviceAndWaitForOnLinkAddr() throws Exception {
mInfraDevice =
new InfraNetworkDevice(MacAddress.fromString("1:2:3:4:5:6"), mInfraNetworkReader);
@@ -668,20 +685,28 @@
assertInfraLinkMemberOfGroup(address);
}
- private byte[] pollForPacketOnInfraNetwork(int type, Inet6Address srcAddress) {
- return pollForPacketOnInfraNetwork(type, srcAddress, null);
+ private byte[] pollForIcmpPacketOnInfraNetwork(int type, InetAddress srcAddress) {
+ return pollForIcmpPacketOnInfraNetwork(type, srcAddress, null /* destAddress */);
}
- private byte[] pollForPacketOnInfraNetwork(
- int type, Inet6Address srcAddress, Inet6Address destAddress) {
- Predicate<byte[]> filter;
- filter =
+ private byte[] pollForIcmpPacketOnInfraNetwork(
+ int type, InetAddress srcAddress, InetAddress destAddress) {
+ if (srcAddress == null && destAddress == null) {
+ throw new IllegalArgumentException("srcAddress and destAddress cannot be both null");
+ }
+ if (srcAddress != null && destAddress != null) {
+ if ((srcAddress instanceof Inet4Address) != (destAddress instanceof Inet4Address)) {
+ throw new IllegalArgumentException(
+ "srcAddress and destAddress must be both IPv4 or both IPv6");
+ }
+ }
+ boolean isIpv4 =
+ (srcAddress instanceof Inet4Address) || (destAddress instanceof Inet4Address);
+ final Predicate<byte[]> filter =
p ->
- (isExpectedIcmpv6Packet(p, type)
- && (srcAddress == null ? true : isFromIpv6Source(p, srcAddress))
- && (destAddress == null
- ? true
- : isToIpv6Destination(p, destAddress)));
+ (isIpv4 ? isExpectedIcmpv4Packet(p, type) : isExpectedIcmpv6Packet(p, type))
+ && (srcAddress == null || isFrom(p, srcAddress))
+ && (destAddress == null || isTo(p, destAddress));
return pollForPacket(mInfraNetworkReader, filter);
}
}
diff --git a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
index e10f134..2afca5f 100644
--- a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
+++ b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
@@ -18,10 +18,10 @@
import static android.net.InetAddresses.parseNumericAddress;
import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
-import static android.net.thread.utils.IntegrationTestUtils.JOIN_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.SERVICE_DISCOVERY_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.discoverForServiceLost;
import static android.net.thread.utils.IntegrationTestUtils.discoverService;
+import static android.net.thread.utils.IntegrationTestUtils.joinNetworkAndWaitForOmr;
import static android.net.thread.utils.IntegrationTestUtils.resolveService;
import static android.net.thread.utils.IntegrationTestUtils.resolveServiceUntil;
import static android.net.thread.utils.IntegrationTestUtils.waitFor;
@@ -168,8 +168,7 @@
// Creates Full Thread Devices (FTD) and let them join the network.
for (FullThreadDevice ftd : mFtds) {
- ftd.joinNetwork(DEFAULT_DATASET);
- ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
}
int randomId = new Random().nextInt(10_000);
@@ -223,8 +222,7 @@
// Creates a Full Thread Devices (FTD) and let it join the network.
FullThreadDevice ftd = mFtds.get(0);
- ftd.joinNetwork(DEFAULT_DATASET);
- ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
ftd.setSrpHostname("my-host");
ftd.setSrpHostAddresses(List.of((Inet6Address) parseNumericAddress("2001:db8::1")));
ftd.addSrpService(
@@ -279,8 +277,7 @@
// Creates a Full Thread Devices (FTD) and let it join the network.
FullThreadDevice ftd = mFtds.get(0);
- ftd.joinNetwork(DEFAULT_DATASET);
- ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
ftd.setSrpHostname("my-host");
ftd.setSrpHostAddresses(
List.of(
@@ -346,8 +343,7 @@
mRegistrationListeners.add(listener);
for (int i = 0; i < NUM_FTD; ++i) {
FullThreadDevice ftd = mFtds.get(i);
- ftd.joinNetwork(DEFAULT_DATASET);
- ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
ftd.setDnsServerAddress(mOtCtl.getMlEid().getHostAddress());
}
final ArrayList<NsdServiceInfo> browsedServices = new ArrayList<>();
@@ -409,8 +405,7 @@
* </pre>
*/
FullThreadDevice srpClient = mFtds.get(0);
- srpClient.joinNetwork(DEFAULT_DATASET);
- srpClient.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ joinNetworkAndWaitForOmr(srpClient, DEFAULT_DATASET);
srpClient.setSrpHostname("my-host");
srpClient.setSrpHostAddresses(List.of((Inet6Address) parseNumericAddress("2001::1")));
srpClient.addSrpService(
@@ -421,8 +416,7 @@
Map.of("key1", bytes(0x01, 0x02), "key2", bytes(0x03)));
FullThreadDevice dnsClient = mFtds.get(1);
- dnsClient.joinNetwork(DEFAULT_DATASET);
- dnsClient.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ joinNetworkAndWaitForOmr(dnsClient, DEFAULT_DATASET);
dnsClient.setDnsServerAddress(mOtCtl.getMlEid().getHostAddress());
NsdServiceInfo browsedService = dnsClient.browseService("_test._udp.default.service.arpa.");
diff --git a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
index c0a8eea..083a841 100644
--- a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
+++ b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
@@ -282,6 +282,7 @@
for (String subtype : subtypes) {
fullServiceType.append(",").append(subtype);
}
+ waitForSrpServer();
executeCommand(
"srp client service add %s %s %d %d %d %s",
serviceName,
@@ -416,7 +417,7 @@
executeCommand("ipmaddr add " + address.getHostAddress());
}
- public void ping(Inet6Address address, Inet6Address source) {
+ public void ping(InetAddress address, Inet6Address source) {
ping(
address,
source,
@@ -427,7 +428,7 @@
PING_TIMEOUT_0_1_SECOND);
}
- public void ping(Inet6Address address) {
+ public void ping(InetAddress address) {
ping(
address,
null,
@@ -439,7 +440,7 @@
}
/** Returns the number of ping reply packets received. */
- public int ping(Inet6Address address, int count) {
+ public int ping(InetAddress address, int count) {
List<String> output =
ping(
address,
@@ -453,7 +454,7 @@
}
private List<String> ping(
- Inet6Address address,
+ InetAddress address,
Inet6Address source,
int size,
int count,
@@ -492,6 +493,22 @@
return -1;
}
+ /** Waits for an SRP server to be present in Network Data */
+ private void waitForSrpServer() throws TimeoutException {
+ // CLI output:
+ // > srp client server
+ // [fd64:db12:25f4:7e0b:1bfc:6344:25ac:2dd7]:53538
+ // Done
+ waitFor(
+ () -> {
+ final String serverAddr = executeCommand("srp client server").get(0);
+ final int lastColonIndex = serverAddr.lastIndexOf(':');
+ final int port = Integer.parseInt(serverAddr.substring(lastColonIndex + 1));
+ return port > 0;
+ },
+ SERVICE_DISCOVERY_TIMEOUT);
+ }
+
@FormatMethod
private List<String> executeCommand(String commandFormat, Object... args) {
return executeCommand(String.format(commandFormat, args));
diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
index ada46c8..82e9332 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
@@ -16,14 +16,18 @@
package android.net.thread.utils;
import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
+import static android.system.OsConstants.IPPROTO_ICMP;
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+import static org.junit.Assert.assertNotNull;
+
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -36,6 +40,7 @@
import android.net.TestNetworkInterface;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
+import android.net.thread.ActiveOperationalDataset;
import android.net.thread.ThreadNetworkController;
import android.os.Build;
import android.os.Handler;
@@ -45,7 +50,9 @@
import androidx.test.core.app.ApplicationProvider;
import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.Icmpv4Header;
import com.android.net.module.util.structs.Icmpv6Header;
+import com.android.net.module.util.structs.Ipv4Header;
import com.android.net.module.util.structs.Ipv6Header;
import com.android.net.module.util.structs.PrefixInformationOption;
import com.android.net.module.util.structs.RaHeader;
@@ -58,6 +65,7 @@
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
+import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -84,6 +92,17 @@
public static final Duration CALLBACK_TIMEOUT = Duration.ofSeconds(1);
public static final Duration SERVICE_DISCOVERY_TIMEOUT = Duration.ofSeconds(20);
+ // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
+ private static final byte[] DEFAULT_DATASET_TLVS =
+ base16().decode(
+ "0E080000000000010000000300001335060004001FFFE002"
+ + "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
+ + "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
+ + "642D643961300102D9A00410A245479C836D551B9CA557F7"
+ + "B9D351B40C0402A0FFF8");
+ public static final ActiveOperationalDataset DEFAULT_DATASET =
+ ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS);
+
private IntegrationTestUtils() {}
/**
@@ -177,16 +196,36 @@
return null;
}
- /** Returns {@code true} if {@code packet} is an ICMPv6 packet of given {@code type}. */
- public static boolean isExpectedIcmpv6Packet(byte[] packet, int type) {
- if (packet == null) {
+ /** Returns {@code true} if {@code packet} is an ICMPv4 packet of given {@code type}. */
+ public static boolean isExpectedIcmpv4Packet(byte[] packet, int type) {
+ ByteBuffer buf = makeByteBuffer(packet);
+ Ipv4Header header = extractIpv4Header(buf);
+ if (header == null) {
return false;
}
- ByteBuffer buf = ByteBuffer.wrap(packet);
+ if (header.protocol != (byte) IPPROTO_ICMP) {
+ return false;
+ }
try {
- if (Struct.parse(Ipv6Header.class, buf).nextHeader != (byte) IPPROTO_ICMPV6) {
- return false;
- }
+ return Struct.parse(Icmpv4Header.class, buf).type == (short) type;
+ } catch (IllegalArgumentException ignored) {
+ // It's fine that the passed in packet is malformed because it's could be sent
+ // by anybody.
+ }
+ return false;
+ }
+
+ /** Returns {@code true} if {@code packet} is an ICMPv6 packet of given {@code type}. */
+ public static boolean isExpectedIcmpv6Packet(byte[] packet, int type) {
+ ByteBuffer buf = makeByteBuffer(packet);
+ Ipv6Header header = extractIpv6Header(buf);
+ if (header == null) {
+ return false;
+ }
+ if (header.nextHeader != (byte) IPPROTO_ICMPV6) {
+ return false;
+ }
+ try {
return Struct.parse(Icmpv6Header.class, buf).type == (short) type;
} catch (IllegalArgumentException ignored) {
// It's fine that the passed in packet is malformed because it's could be sent
@@ -195,32 +234,66 @@
return false;
}
- public static boolean isFromIpv6Source(byte[] packet, Inet6Address src) {
- if (packet == null) {
- return false;
- }
- ByteBuffer buf = ByteBuffer.wrap(packet);
- try {
- return Struct.parse(Ipv6Header.class, buf).srcIp.equals(src);
- } catch (IllegalArgumentException ignored) {
- // It's fine that the passed in packet is malformed because it's could be sent
- // by anybody.
+ public static boolean isFrom(byte[] packet, InetAddress src) {
+ if (src instanceof Inet4Address) {
+ return isFromIpv4Source(packet, (Inet4Address) src);
+ } else if (src instanceof Inet6Address) {
+ return isFromIpv6Source(packet, (Inet6Address) src);
}
return false;
}
- public static boolean isToIpv6Destination(byte[] packet, Inet6Address dest) {
- if (packet == null) {
- return false;
+ public static boolean isTo(byte[] packet, InetAddress dest) {
+ if (dest instanceof Inet4Address) {
+ return isToIpv4Destination(packet, (Inet4Address) dest);
+ } else if (dest instanceof Inet6Address) {
+ return isToIpv6Destination(packet, (Inet6Address) dest);
}
- ByteBuffer buf = ByteBuffer.wrap(packet);
+ return false;
+ }
+
+ private static boolean isFromIpv4Source(byte[] packet, Inet4Address src) {
+ Ipv4Header header = extractIpv4Header(makeByteBuffer(packet));
+ return header != null && header.srcIp.equals(src);
+ }
+
+ private static boolean isFromIpv6Source(byte[] packet, Inet6Address src) {
+ Ipv6Header header = extractIpv6Header(makeByteBuffer(packet));
+ return header != null && header.srcIp.equals(src);
+ }
+
+ private static boolean isToIpv4Destination(byte[] packet, Inet4Address dest) {
+ Ipv4Header header = extractIpv4Header(makeByteBuffer(packet));
+ return header != null && header.dstIp.equals(dest);
+ }
+
+ private static boolean isToIpv6Destination(byte[] packet, Inet6Address dest) {
+ Ipv6Header header = extractIpv6Header(makeByteBuffer(packet));
+ return header != null && header.dstIp.equals(dest);
+ }
+
+ private static ByteBuffer makeByteBuffer(byte[] packet) {
+ return packet == null ? null : ByteBuffer.wrap(packet);
+ }
+
+ private static Ipv4Header extractIpv4Header(ByteBuffer buf) {
try {
- return Struct.parse(Ipv6Header.class, buf).dstIp.equals(dest);
+ return Struct.parse(Ipv4Header.class, buf);
} catch (IllegalArgumentException ignored) {
// It's fine that the passed in packet is malformed because it's could be sent
// by anybody.
}
- return false;
+ return null;
+ }
+
+ private static Ipv6Header extractIpv6Header(ByteBuffer buf) {
+ try {
+ return Struct.parse(Ipv6Header.class, buf);
+ } catch (IllegalArgumentException ignored) {
+ // It's fine that the passed in packet is malformed because it's could be sent
+ // by anybody.
+ }
+ return null;
}
/** Returns the Prefix Information Options (PIO) extracted from an ICMPv6 RA message. */
@@ -413,6 +486,22 @@
return networkFuture.get(timeout.toSeconds(), SECONDS);
}
+ /**
+ * Let the FTD join the specified Thread network and wait for border routing to be available.
+ *
+ * @return the OMR address
+ */
+ public static Inet6Address joinNetworkAndWaitForOmr(
+ FullThreadDevice ftd, ActiveOperationalDataset dataset) throws Exception {
+ ftd.factoryReset();
+ ftd.joinNetwork(dataset);
+ ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ waitFor(() -> ftd.getOmrAddress() != null, Duration.ofSeconds(60));
+ Inet6Address ftdOmr = ftd.getOmrAddress();
+ assertNotNull(ftdOmr);
+ return ftdOmr;
+ }
+
private static class DefaultDiscoveryListener implements NsdManager.DiscoveryListener {
@Override
public void onStartDiscoveryFailed(String serviceType, int errorCode) {}
diff --git a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
index b3175fd..15a3f5c 100644
--- a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
+++ b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
@@ -105,6 +105,29 @@
return prefixes.isEmpty() ? null : prefixes.get(0);
}
+ /** Enables/Disables NAT64 feature. */
+ public void setNat64Enabled(boolean enabled) {
+ executeCommand("nat64 " + (enabled ? "enable" : "disable"));
+ }
+
+ /** Sets the NAT64 CIDR. */
+ public void setNat64Cidr(String cidr) {
+ executeCommand("nat64 cidr " + cidr);
+ }
+
+ /** Returns whether there's a NAT64 prefix in network data */
+ public boolean hasNat64PrefixInNetdata() {
+ // Example (in the 'Routes' section):
+ // fdb2:bae3:5b59:2:0:0::/96 sn low c000
+ List<String> outputLines = executeCommandAndParse("netdata show");
+ for (String line : outputLines) {
+ if (line.contains(" sn")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public String executeCommand(String cmd) {
return SystemUtil.runShellCommand(OT_CTL + " " + cmd);
}
diff --git a/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java b/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java
index 2244a89..11c78e3 100644
--- a/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java
+++ b/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java
@@ -41,6 +41,19 @@
}
@Test
+ public void fromInstant_authoritativeIsSetAsSpecified() {
+ Instant instant = Instant.now();
+
+ OperationalDatasetTimestamp timestampAuthoritativeFalse =
+ OperationalDatasetTimestamp.fromInstant(instant, false);
+ OperationalDatasetTimestamp timestampAuthoritativeTrue =
+ OperationalDatasetTimestamp.fromInstant(instant, true);
+
+ assertThat(timestampAuthoritativeFalse.isAuthoritativeSource()).isEqualTo(false);
+ assertThat(timestampAuthoritativeTrue.isAuthoritativeSource()).isEqualTo(true);
+ }
+
+ @Test
public void fromTlvValue_goodValue_success() {
OperationalDatasetTimestamp timestamp =
OperationalDatasetTimestamp.fromTlvValue(base16().decode("FFEEDDCCBBAA9989"));
diff --git a/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
index ae0bc80..b32986d 100644
--- a/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
@@ -42,6 +42,9 @@
import android.os.Handler;
import android.os.test.TestLooper;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import com.android.server.thread.openthread.DnsTxtAttribute;
import com.android.server.thread.openthread.INsdDiscoverServiceCallback;
import com.android.server.thread.openthread.INsdResolveHostCallback;
@@ -50,6 +53,7 @@
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -62,6 +66,8 @@
import java.util.concurrent.Executor;
/** Unit tests for {@link NsdPublisher}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public final class NsdPublisherTest {
private static final DnsTxtAttribute TEST_TXT_ENTRY_1 =
new DnsTxtAttribute("key1", new byte[] {0x01, 0x02});
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index 2f58943..be32764 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -16,6 +16,12 @@
package com.android.server.thread;
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_THREAD;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.thread.ActiveOperationalDataset.CHANNEL_PAGE_24_GHZ;
import static android.net.thread.ThreadNetworkController.STATE_DISABLED;
import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
@@ -26,7 +32,6 @@
import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
import static com.android.server.thread.ThreadNetworkCountryCode.DEFAULT_COUNTRY_CODE;
-import static com.android.server.thread.ThreadPersistentSettings.THREAD_ENABLED_IN_AIRPLANE_MODE;
import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_INVALID_STATE;
import static com.google.common.io.BaseEncoding.base16;
@@ -39,6 +44,8 @@
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
@@ -49,6 +56,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -56,9 +65,11 @@
import android.net.ConnectivityManager;
import android.net.NetworkAgent;
import android.net.NetworkProvider;
+import android.net.NetworkRequest;
import android.net.thread.ActiveOperationalDataset;
import android.net.thread.IActiveOperationalDatasetReceiver;
import android.net.thread.IOperationReceiver;
+import android.net.thread.ThreadConfiguration;
import android.net.thread.ThreadNetworkException;
import android.os.Handler;
import android.os.IBinder;
@@ -67,7 +78,6 @@
import android.os.SystemClock;
import android.os.UserManager;
import android.os.test.TestLooper;
-import android.provider.Settings;
import android.util.AtomicFile;
import androidx.test.annotation.UiThreadTest;
@@ -95,9 +105,11 @@
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
-import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
@@ -147,7 +159,6 @@
private static final byte[] TEST_VENDOR_OUI_BYTES = new byte[] {(byte) 0xAC, (byte) 0xDE, 0x48};
private static final String TEST_VENDOR_NAME = "test vendor";
private static final String TEST_MODEL_NAME = "test model";
- private static final boolean TEST_VGH_VALUE = false;
@Mock private ConnectivityManager mMockConnectivityManager;
@Mock private NetworkAgent mMockNetworkAgent;
@@ -179,6 +190,9 @@
.when(mContext)
.enforceCallingOrSelfPermission(
eq(PERMISSION_THREAD_NETWORK_PRIVILEGED), anyString());
+ doNothing()
+ .when(mContext)
+ .enforceCallingOrSelfPermission(eq(NETWORK_SETTINGS), anyString());
mTestLooper = new TestLooper();
final Handler handler = new Handler(mTestLooper.getLooper());
@@ -190,8 +204,6 @@
when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(false);
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
-
when(mConnectivityResources.get()).thenReturn(mResources);
when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(true);
when(mResources.getString(eq(R.string.config_thread_vendor_name)))
@@ -200,8 +212,8 @@
.thenReturn(TEST_VENDOR_OUI);
when(mResources.getString(eq(R.string.config_thread_model_name)))
.thenReturn(TEST_MODEL_NAME);
- when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
- .thenReturn(TEST_VGH_VALUE);
+ when(mResources.getStringArray(eq(R.array.config_thread_mdns_vendor_specific_txts)))
+ .thenReturn(new String[] {});
final AtomicFile storageFile = new AtomicFile(tempFolder.newFile("thread_settings.xml"));
mPersistentSettings = new ThreadPersistentSettings(storageFile, mConnectivityResources);
@@ -244,8 +256,8 @@
.thenReturn(TEST_VENDOR_OUI);
when(mResources.getString(eq(R.string.config_thread_model_name)))
.thenReturn(TEST_MODEL_NAME);
- when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
- .thenReturn(true);
+ when(mResources.getStringArray(eq(R.array.config_thread_mdns_vendor_specific_txts)))
+ .thenReturn(new String[] {"vt=test"});
mService.initialize();
mTestLooper.dispatchAll();
@@ -255,19 +267,7 @@
assertThat(meshcopTxts.vendorOui).isEqualTo(TEST_VENDOR_OUI_BYTES);
assertThat(meshcopTxts.modelName).isEqualTo(TEST_MODEL_NAME);
assertThat(meshcopTxts.nonStandardTxtEntries)
- .containsExactly(new DnsTxtAttribute("vgh", "1".getBytes(StandardCharsets.UTF_8)));
- }
-
- @Test
- public void getMeshcopTxtAttributes_managedByGoogleIsFalse_vghIsZero() {
- when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
- .thenReturn(false);
-
- MeshcopTxtAttributes meshcopTxts =
- ThreadNetworkControllerService.getMeshcopTxtAttributes(mResources);
-
- assertThat(meshcopTxts.nonStandardTxtEntries)
- .containsExactly(new DnsTxtAttribute("vgh", "0".getBytes(StandardCharsets.UTF_8)));
+ .containsExactly(new DnsTxtAttribute("vt", "test".getBytes(UTF_8)));
}
@Test
@@ -340,6 +340,61 @@
}
@Test
+ public void makeVendorSpecificTxtAttrs_validTxts_returnsParsedTxtAttrs() {
+ String[] txts = new String[] {"va=123", "vb=", "vc"};
+
+ List<DnsTxtAttribute> attrs = mService.makeVendorSpecificTxtAttrs(txts);
+
+ assertThat(attrs)
+ .containsExactly(
+ new DnsTxtAttribute("va", "123".getBytes(UTF_8)),
+ new DnsTxtAttribute("vb", new byte[] {}),
+ new DnsTxtAttribute("vc", new byte[] {}));
+ }
+
+ @Test
+ public void makeVendorSpecificTxtAttrs_txtKeyNotStartWithV_throwsIllegalArgument() {
+ String[] txts = new String[] {"abc=123"};
+
+ assertThrows(
+ IllegalArgumentException.class, () -> mService.makeVendorSpecificTxtAttrs(txts));
+ }
+
+ @Test
+ public void makeVendorSpecificTxtAttrs_txtIsTooShort_throwsIllegalArgument() {
+ String[] txtEmptyKey = new String[] {"=123"};
+ String[] txtSingleCharKey = new String[] {"v=456"};
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mService.makeVendorSpecificTxtAttrs(txtEmptyKey));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mService.makeVendorSpecificTxtAttrs(txtSingleCharKey));
+ }
+
+ @Test
+ public void makeVendorSpecificTxtAttrs_txtValueIsEmpty_parseSuccess() {
+ String[] txts = new String[] {"va=", "vb"};
+
+ List<DnsTxtAttribute> attrs = mService.makeVendorSpecificTxtAttrs(txts);
+
+ assertThat(attrs)
+ .containsExactly(
+ new DnsTxtAttribute("va", new byte[] {}),
+ new DnsTxtAttribute("vb", new byte[] {}));
+ }
+
+ @Test
+ public void makeVendorSpecificTxtAttrs_multipleEquals_splittedByTheFirstEqual() {
+ String[] txts = new String[] {"va=abc=def=123"};
+
+ List<DnsTxtAttribute> attrs = mService.makeVendorSpecificTxtAttrs(txts);
+
+ assertThat(attrs).containsExactly(new DnsTxtAttribute("va", "abc=def=123".getBytes(UTF_8)));
+ }
+
+ @Test
public void join_otDaemonRemoteFailure_returnsInternalError() throws Exception {
mService.initialize();
final IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
@@ -434,100 +489,6 @@
assertThat(failure.getErrorCode()).isEqualTo(ERROR_FAILED_PRECONDITION);
}
- @Test
- public void airplaneMode_initWithAirplaneModeOn_otDaemonNotStarted() {
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
-
- mService.initialize();
- mTestLooper.dispatchAll();
-
- assertThat(mFakeOtDaemon.isInitialized()).isFalse();
- }
-
- @Test
- public void airplaneMode_initWithAirplaneModeOff_threadIsEnabled() {
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
-
- mService.initialize();
- mTestLooper.dispatchAll();
-
- assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_ENABLED);
- }
-
- @Test
- public void airplaneMode_changesFromOffToOn_stateIsDisabled() {
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
- AtomicReference<BroadcastReceiver> receiverRef =
- captureBroadcastReceiver(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- mService.initialize();
- mTestLooper.dispatchAll();
-
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
- receiverRef.get().onReceive(mContext, new Intent());
- mTestLooper.dispatchAll();
-
- assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_DISABLED);
- }
-
- @Test
- public void airplaneMode_changesFromOnToOff_stateIsEnabled() {
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
- AtomicReference<BroadcastReceiver> receiverRef =
- captureBroadcastReceiver(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- mService.initialize();
- mTestLooper.dispatchAll();
-
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
- receiverRef.get().onReceive(mContext, new Intent());
- mTestLooper.dispatchAll();
-
- assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_ENABLED);
- }
-
- @Test
- public void airplaneMode_setEnabledWhenAirplaneModeIsOn_WillNotAutoDisableSecondTime() {
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
- AtomicReference<BroadcastReceiver> receiverRef =
- captureBroadcastReceiver(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- CompletableFuture<Void> setEnabledFuture = new CompletableFuture<>();
- mService.initialize();
-
- mService.setEnabled(true, newOperationReceiver(setEnabledFuture));
- mTestLooper.dispatchAll();
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
- receiverRef.get().onReceive(mContext, new Intent());
- mTestLooper.dispatchAll();
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
- receiverRef.get().onReceive(mContext, new Intent());
- mTestLooper.dispatchAll();
-
- assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_ENABLED);
- assertThat(mPersistentSettings.get(THREAD_ENABLED_IN_AIRPLANE_MODE)).isTrue();
- }
-
- @Test
- public void airplaneMode_setDisabledWhenAirplaneModeIsOn_WillAutoDisableSecondTime() {
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
- AtomicReference<BroadcastReceiver> receiverRef =
- captureBroadcastReceiver(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- CompletableFuture<Void> setEnabledFuture = new CompletableFuture<>();
- mService.initialize();
- mService.setEnabled(true, newOperationReceiver(setEnabledFuture));
- mTestLooper.dispatchAll();
-
- mService.setEnabled(false, newOperationReceiver(setEnabledFuture));
- mTestLooper.dispatchAll();
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
- receiverRef.get().onReceive(mContext, new Intent());
- mTestLooper.dispatchAll();
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
- receiverRef.get().onReceive(mContext, new Intent());
- mTestLooper.dispatchAll();
-
- assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_DISABLED);
- assertThat(mPersistentSettings.get(THREAD_ENABLED_IN_AIRPLANE_MODE)).isFalse();
- }
-
private AtomicReference<BroadcastReceiver> captureBroadcastReceiver(String action) {
AtomicReference<BroadcastReceiver> receiverRef = new AtomicReference<>();
@@ -585,6 +546,55 @@
}
@Test
+ public void createRandomizedDataset_zeroNanoseconds_returnsZeroTicks() throws Exception {
+ Instant now = Instant.ofEpochSecond(0, 0);
+ Clock clock = Clock.fixed(now, ZoneId.systemDefault());
+ MockitoSession session =
+ ExtendedMockito.mockitoSession().mockStatic(SystemClock.class).startMocking();
+ final IActiveOperationalDatasetReceiver mockReceiver =
+ ExtendedMockito.mock(IActiveOperationalDatasetReceiver.class);
+
+ try {
+ ExtendedMockito.when(SystemClock.currentNetworkTimeClock()).thenReturn(clock);
+ mService.createRandomizedDataset(DEFAULT_NETWORK_NAME, mockReceiver);
+ mTestLooper.dispatchAll();
+ } finally {
+ session.finishMocking();
+ }
+
+ verify(mockReceiver, never()).onError(anyInt(), anyString());
+ verify(mockReceiver, times(1)).onSuccess(mActiveDatasetCaptor.capture());
+ ActiveOperationalDataset activeDataset = mActiveDatasetCaptor.getValue();
+ assertThat(activeDataset.getActiveTimestamp().getTicks()).isEqualTo(0);
+ }
+
+ @Test
+ public void createRandomizedDataset_maxNanoseconds_returnsMaxTicks() throws Exception {
+ // The nanoseconds to ticks conversion is rounded in the current implementation.
+ // 32767.5 / 32768 * 1000000000 = 999984741.2109375, using 999984741 to
+ // produce the maximum ticks.
+ Instant now = Instant.ofEpochSecond(0, 999984741);
+ Clock clock = Clock.fixed(now, ZoneId.systemDefault());
+ MockitoSession session =
+ ExtendedMockito.mockitoSession().mockStatic(SystemClock.class).startMocking();
+ final IActiveOperationalDatasetReceiver mockReceiver =
+ ExtendedMockito.mock(IActiveOperationalDatasetReceiver.class);
+
+ try {
+ ExtendedMockito.when(SystemClock.currentNetworkTimeClock()).thenReturn(clock);
+ mService.createRandomizedDataset(DEFAULT_NETWORK_NAME, mockReceiver);
+ mTestLooper.dispatchAll();
+ } finally {
+ session.finishMocking();
+ }
+
+ verify(mockReceiver, never()).onError(anyInt(), anyString());
+ verify(mockReceiver, times(1)).onSuccess(mActiveDatasetCaptor.capture());
+ ActiveOperationalDataset activeDataset = mActiveDatasetCaptor.getValue();
+ assertThat(activeDataset.getActiveTimestamp().getTicks()).isEqualTo(32767);
+ }
+
+ @Test
public void createRandomizedDataset_hasNetworkTimeClock_datasetActiveTimestampIsAuthoritative()
throws Exception {
MockitoSession session =
@@ -708,4 +718,87 @@
inOrder.verify(mMockTunIfController, times(1)).setInterfaceUp(false);
inOrder.verify(mMockTunIfController, times(1)).setInterfaceUp(true);
}
+
+ @Test
+ public void setConfiguration_configurationUpdated() throws Exception {
+ mService.initialize();
+ final IOperationReceiver mockReceiver1 = mock(IOperationReceiver.class);
+ final IOperationReceiver mockReceiver2 = mock(IOperationReceiver.class);
+ final IOperationReceiver mockReceiver3 = mock(IOperationReceiver.class);
+ ThreadConfiguration config1 =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(false)
+ .setDhcpv6PdEnabled(false)
+ .build();
+ ThreadConfiguration config2 =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(true)
+ .setDhcpv6PdEnabled(true)
+ .build();
+ ThreadConfiguration config3 =
+ new ThreadConfiguration.Builder(config2).build(); // Same as config2
+
+ mService.setConfiguration(config1, mockReceiver1);
+ mService.setConfiguration(config2, mockReceiver2);
+ mService.setConfiguration(config3, mockReceiver3);
+ mTestLooper.dispatchAll();
+
+ assertThat(mPersistentSettings.getConfiguration()).isEqualTo(config3);
+ InOrder inOrder = Mockito.inOrder(mockReceiver1, mockReceiver2, mockReceiver3);
+ inOrder.verify(mockReceiver1).onSuccess();
+ inOrder.verify(mockReceiver2).onSuccess();
+ inOrder.verify(mockReceiver3).onSuccess();
+ }
+
+ @Test
+ public void initialize_upstreamNetworkRequestHasCertainTransportTypesAndCapabilities() {
+ mService.initialize();
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<NetworkRequest> networkRequestCaptor =
+ ArgumentCaptor.forClass(NetworkRequest.class);
+ verify(mMockConnectivityManager, atLeastOnce())
+ .registerNetworkCallback(
+ networkRequestCaptor.capture(),
+ any(ConnectivityManager.NetworkCallback.class),
+ any(Handler.class));
+ List<NetworkRequest> upstreamNetworkRequests =
+ networkRequestCaptor.getAllValues().stream()
+ .filter(nr -> !nr.hasTransport(TRANSPORT_THREAD))
+ .toList();
+ assertThat(upstreamNetworkRequests.size()).isEqualTo(1);
+ NetworkRequest upstreamNetworkRequest = upstreamNetworkRequests.get(0);
+ assertThat(upstreamNetworkRequest.hasTransport(TRANSPORT_WIFI)).isTrue();
+ assertThat(upstreamNetworkRequest.hasTransport(TRANSPORT_ETHERNET)).isTrue();
+ assertThat(upstreamNetworkRequest.hasCapability(NET_CAPABILITY_NOT_VPN)).isTrue();
+ assertThat(upstreamNetworkRequest.hasCapability(NET_CAPABILITY_INTERNET)).isTrue();
+ }
+
+ @Test
+ public void setTestNetworkAsUpstream_upstreamNetworkRequestAlwaysDisallowsVpn() {
+ mService.initialize();
+ mTestLooper.dispatchAll();
+ clearInvocations(mMockConnectivityManager);
+
+ final IOperationReceiver mockReceiver1 = mock(IOperationReceiver.class);
+ final IOperationReceiver mockReceiver2 = mock(IOperationReceiver.class);
+ mService.setTestNetworkAsUpstream("test-network", mockReceiver1);
+ mService.setTestNetworkAsUpstream(null, mockReceiver2);
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<NetworkRequest> networkRequestCaptor =
+ ArgumentCaptor.forClass(NetworkRequest.class);
+ verify(mMockConnectivityManager, times(2))
+ .registerNetworkCallback(
+ networkRequestCaptor.capture(),
+ any(ConnectivityManager.NetworkCallback.class),
+ any(Handler.class));
+ assertThat(networkRequestCaptor.getAllValues().size()).isEqualTo(2);
+ NetworkRequest networkRequest1 = networkRequestCaptor.getAllValues().get(0);
+ NetworkRequest networkRequest2 = networkRequestCaptor.getAllValues().get(1);
+ assertThat(networkRequest1.getNetworkSpecifier()).isNotNull();
+ assertThat(networkRequest1.hasCapability(NET_CAPABILITY_NOT_VPN)).isTrue();
+ assertThat(networkRequest2.getNetworkSpecifier()).isNull();
+ assertThat(networkRequest2.hasCapability(NET_CAPABILITY_NOT_VPN)).isTrue();
+ }
}
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
index 7d2fe91..ba489d9 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
@@ -21,16 +21,12 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.validateMockitoUsage;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.res.Resources;
+import android.net.thread.ThreadConfiguration;
import android.os.PersistableBundle;
import android.util.AtomicFile;
@@ -42,13 +38,14 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.ByteArrayOutputStream;
-import java.io.FileInputStream;
import java.io.FileOutputStream;
/** Unit tests for {@link ThreadPersistentSettings}. */
@@ -57,12 +54,15 @@
public class ThreadPersistentSettingsTest {
private static final String TEST_COUNTRY_CODE = "CN";
- @Mock private AtomicFile mAtomicFile;
@Mock Resources mResources;
@Mock ConnectivityResources mConnectivityResources;
+ private AtomicFile mAtomicFile;
private ThreadPersistentSettings mThreadPersistentSettings;
+ @Rule(order = 0)
+ public final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -70,8 +70,7 @@
when(mConnectivityResources.get()).thenReturn(mResources);
when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(true);
- FileOutputStream fos = mock(FileOutputStream.class);
- when(mAtomicFile.startWrite()).thenReturn(fos);
+ mAtomicFile = createAtomicFile();
mThreadPersistentSettings =
new ThreadPersistentSettings(mAtomicFile, mConnectivityResources);
}
@@ -85,7 +84,7 @@
@Test
public void initialize_readsFromFile() throws Exception {
byte[] data = createXmlForParsing(THREAD_ENABLED.key, false);
- setupAtomicFileMockForRead(data);
+ setupAtomicFileForRead(data);
mThreadPersistentSettings.initialize();
@@ -95,7 +94,7 @@
@Test
public void initialize_ThreadDisabledInResources_returnsThreadDisabled() throws Exception {
when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(false);
- setupAtomicFileMockForRead(new byte[0]);
+ setupAtomicFileForRead(new byte[0]);
mThreadPersistentSettings.initialize();
@@ -107,7 +106,7 @@
throws Exception {
when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(false);
byte[] data = createXmlForParsing(THREAD_ENABLED.key, true);
- setupAtomicFileMockForRead(data);
+ setupAtomicFileForRead(data);
mThreadPersistentSettings.initialize();
@@ -119,9 +118,6 @@
mThreadPersistentSettings.put(THREAD_ENABLED.key, true);
assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isTrue();
- // Confirm that file writes have been triggered.
- verify(mAtomicFile).startWrite();
- verify(mAtomicFile).finishWrite(any());
}
@Test
@@ -129,9 +125,8 @@
mThreadPersistentSettings.put(THREAD_ENABLED.key, false);
assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse();
- // Confirm that file writes have been triggered.
- verify(mAtomicFile).startWrite();
- verify(mAtomicFile).finishWrite(any());
+ mThreadPersistentSettings.initialize();
+ assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse();
}
@Test
@@ -139,10 +134,8 @@
mThreadPersistentSettings.put(THREAD_COUNTRY_CODE.key, TEST_COUNTRY_CODE);
assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isEqualTo(TEST_COUNTRY_CODE);
-
- // Confirm that file writes have been triggered.
- verify(mAtomicFile).startWrite();
- verify(mAtomicFile).finishWrite(any());
+ mThreadPersistentSettings.initialize();
+ assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isEqualTo(TEST_COUNTRY_CODE);
}
@Test
@@ -150,10 +143,63 @@
mThreadPersistentSettings.put(THREAD_COUNTRY_CODE.key, null);
assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isNull();
+ mThreadPersistentSettings.initialize();
+ assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isNull();
+ }
- // Confirm that file writes have been triggered.
- verify(mAtomicFile).startWrite();
- verify(mAtomicFile).finishWrite(any());
+ @Test
+ public void putConfiguration_sameValues_returnsFalse() {
+ ThreadConfiguration configuration =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(true)
+ .setDhcpv6PdEnabled(true)
+ .build();
+ mThreadPersistentSettings.putConfiguration(configuration);
+
+ assertThat(mThreadPersistentSettings.putConfiguration(configuration)).isFalse();
+ }
+
+ @Test
+ public void putConfiguration_differentValues_returnsTrue() {
+ ThreadConfiguration configuration1 =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(false)
+ .setDhcpv6PdEnabled(false)
+ .build();
+ mThreadPersistentSettings.putConfiguration(configuration1);
+ ThreadConfiguration configuration2 =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(true)
+ .setDhcpv6PdEnabled(true)
+ .build();
+
+ assertThat(mThreadPersistentSettings.putConfiguration(configuration2)).isTrue();
+ }
+
+ @Test
+ public void putConfiguration_nat64Enabled_valuesUpdatedAndPersisted() throws Exception {
+ ThreadConfiguration configuration =
+ new ThreadConfiguration.Builder().setNat64Enabled(true).build();
+ mThreadPersistentSettings.putConfiguration(configuration);
+
+ assertThat(mThreadPersistentSettings.getConfiguration()).isEqualTo(configuration);
+ mThreadPersistentSettings.initialize();
+ assertThat(mThreadPersistentSettings.getConfiguration()).isEqualTo(configuration);
+ }
+
+ @Test
+ public void putConfiguration_dhcpv6PdEnabled_valuesUpdatedAndPersisted() throws Exception {
+ ThreadConfiguration configuration =
+ new ThreadConfiguration.Builder().setDhcpv6PdEnabled(true).build();
+ mThreadPersistentSettings.putConfiguration(configuration);
+
+ assertThat(mThreadPersistentSettings.getConfiguration()).isEqualTo(configuration);
+ mThreadPersistentSettings.initialize();
+ assertThat(mThreadPersistentSettings.getConfiguration()).isEqualTo(configuration);
+ }
+
+ private AtomicFile createAtomicFile() throws Exception {
+ return new AtomicFile(mTemporaryFolder.newFile());
}
private byte[] createXmlForParsing(String key, Boolean value) throws Exception {
@@ -164,19 +210,9 @@
return outputStream.toByteArray();
}
- private void setupAtomicFileMockForRead(byte[] dataToRead) throws Exception {
- FileInputStream is = mock(FileInputStream.class);
- when(mAtomicFile.openRead()).thenReturn(is);
- when(is.available()).thenReturn(dataToRead.length).thenReturn(0);
- doAnswer(
- invocation -> {
- byte[] data = invocation.getArgument(0);
- int pos = invocation.getArgument(1);
- if (pos == dataToRead.length) return 0; // read complete.
- System.arraycopy(dataToRead, 0, data, 0, dataToRead.length);
- return dataToRead.length;
- })
- .when(is)
- .read(any(), anyInt(), anyInt());
+ private void setupAtomicFileForRead(byte[] dataToRead) throws Exception {
+ try (FileOutputStream outputStream = new FileOutputStream(mAtomicFile.getBaseFile())) {
+ outputStream.write(dataToRead);
+ }
}
}