Merge "Make BpfRingbufBase destructor virtual" into main
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 47e5d5e..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",
@@ -275,7 +261,7 @@
],
"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"
@@ -333,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"
@@ -357,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"
@@ -408,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"]
},
{
@@ -422,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..b4426a6 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-tethering",
"netd-client",
"tetheringstatsprotos",
],
diff --git a/Tethering/common/TetheringLib/lint-baseline.xml b/Tethering/common/TetheringLib/lint-baseline.xml
index ed5fbb0..5171efb 100644
--- a/Tethering/common/TetheringLib/lint-baseline.xml
+++ b/Tethering/common/TetheringLib/lint-baseline.xml
@@ -12,4 +12,15 @@
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 8b3102a..0f5a014 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -198,6 +198,10 @@
/**
* 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)
@@ -1379,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);
@@ -1533,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 a213ac4..b807544 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -44,7 +44,6 @@
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.TetheringManager.TetheringRequest;
@@ -72,7 +71,7 @@
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;
@@ -239,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,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());
@@ -309,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;
@@ -825,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);
}
}
@@ -1370,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;
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index c310f16..d62f18f 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -91,7 +91,6 @@
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;
@@ -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;
@@ -246,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;
@@ -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
@@ -2397,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();
@@ -2437,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 a147a4a..454cbf1 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -164,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);
@@ -176,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/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 5c258b2..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,36 +27,51 @@
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;
@@ -73,7 +89,9 @@
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.TimeoutException;
@@ -89,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[] {
@@ -802,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 00eb3b1..177296a 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -51,6 +51,7 @@
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.doThrow;
@@ -73,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;
@@ -91,6 +91,7 @@
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.networkstack.tethering.BpfCoordinator;
@@ -174,8 +175,7 @@
@Mock private RouterAdvertisementDaemon mRaDaemon;
@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;
@@ -280,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();
}
@@ -455,105 +437,114 @@
// 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(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
@@ -572,7 +563,8 @@
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
@@ -624,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)));
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 e9cde28..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;
@@ -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;
}
@@ -1082,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 */);
@@ -1111,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.
@@ -1127,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());
@@ -1145,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 */);
@@ -1172,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();
@@ -1187,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());
@@ -3435,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(
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/framework/Android.bp b/framework/Android.bp
index deb1c5a..4eda0aa 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -95,8 +95,7 @@
"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",
@@ -123,8 +122,7 @@
// 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
@@ -332,7 +330,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/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index ffaf41f..a6a967b 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -4489,6 +4489,7 @@
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 */
@@ -6737,21 +6738,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/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/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 64624ae..419ec3a 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());
}
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 b870477..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);
}
@@ -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/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..0e84764 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;
@@ -494,8 +495,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);
}
}
@@ -821,7 +822,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 +1233,7 @@
return RecordConflictType.NO_CONFLICT;
}
- if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase(record.getName(), fullServiceName)) {
+ if (!DnsUtils.equalsDnsLabelIgnoreDnsCase(record.getName(), fullServiceName)) {
return RecordConflictType.NO_CONFLICT;
}
@@ -1270,7 +1271,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 +1352,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);
}
}
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..7eea93a 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);
}
}
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/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/Android.bp b/service/Android.bp
index 1dd09a9..1a0e045 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",
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/src/com/android/server/CallbackQueue.java b/service/src/com/android/server/CallbackQueue.java
new file mode 100644
index 0000000..060a984
--- /dev/null
+++ b/service/src/com/android/server/CallbackQueue.java
@@ -0,0 +1,126 @@
+/*
+ * 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);
+ * [...]
+ * queue.shrinkToLength();
+ * storedCallbacks = queue.getBackingArray();
+ * </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 310db9a..953fd76 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -38,14 +38,25 @@
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.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;
@@ -66,7 +77,6 @@
import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.ConnectivityManager.isNetworkTypeValid;
-import static android.net.ConnectivityManager.NetworkCallback.DECLARED_METHODS_ALL;
import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.INetd.PERMISSION_INTERNET;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
@@ -135,8 +145,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 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;
@@ -321,6 +332,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;
@@ -358,7 +370,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;
@@ -500,6 +511,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
@@ -1863,6 +1877,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);
@@ -2018,7 +2035,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
@@ -3454,6 +3471,14 @@
private void handleFrozenUids(int[] uids, int[] frozenStates) {
ensureRunningOnConnectivityServiceThread();
+ handleDestroyFrozenSockets(uids, frozenStates);
+ // TODO: handle freezing NetworkCallbacks
+ }
+
+ 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;
@@ -5364,7 +5389,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);
@@ -5486,8 +5511,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);
}
}
@@ -5862,8 +5886,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,
@@ -5879,7 +5902,7 @@
}
handleRemoveNetworkRequest(nri);
if (callOnUnavailable) {
- callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
+ callCallbackForRequest(nri, null, CALLBACK_UNAVAIL, 0);
}
}
@@ -7035,7 +7058,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);
}
}
@@ -7756,6 +7779,11 @@
}
}
+ boolean isCallbackOverridden(int callbackId) {
+ return !mUseDeclaredMethodsForCallbacksEnabled
+ || (mDeclaredMethodsFlags & (1 << callbackId)) != 0;
+ }
+
boolean hasHigherOrderThan(@NonNull final NetworkRequestInfo target) {
// Compare two preference orders.
return mPreferenceOrder < target.mPreferenceOrder;
@@ -7792,10 +7820,52 @@
+ " callback flags: " + mCallbackFlags
+ " order: " + mPreferenceOrder
+ " isUidTracked: " + mUidTrackedForBlockedStatus
- + " declaredMethods: 0x" + Integer.toHexString(mDeclaredMethodsFlags);
+ + " 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 {
@@ -9082,7 +9152,7 @@
}
networkAgent.networkMonitor().notifyLinkPropertiesChanged(
new LinkProperties(newLp, true /* parcelSensitiveFields */));
- notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
+ notifyNetworkCallbacks(networkAgent, CALLBACK_IP_CHANGED);
}
mKeepaliveTracker.handleCheckKeepalivesStillValid(networkAgent);
@@ -9578,8 +9648,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
@@ -9666,7 +9735,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);
@@ -9808,7 +9877,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;
}
}
@@ -10114,7 +10183,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
@@ -10184,6 +10253,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.
@@ -10196,24 +10277,19 @@
// are Type.LISTEN, but should not have NetworkCallbacks invoked.
return;
}
- if (mUseDeclaredMethodsForCallbacksEnabled
- && (nri.mDeclaredMethodsFlags & (1 << notificationType)) == 0) {
+ if (!nri.isCallbackOverridden(notificationType)) {
// No need to send the notification as the recipient method is not overridden
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);
- }
+ 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(
@@ -10228,15 +10304,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(
@@ -10249,17 +10319,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;
@@ -10268,17 +10337,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());
}
}
@@ -10528,7 +10606,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);
}
@@ -10541,7 +10619,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);
}
}
}
@@ -10873,7 +10951,7 @@
notifyNetworkAvailable(event.mNewNetwork, event.mNetworkRequestInfo);
} else {
callCallbackForRequest(event.mNetworkRequestInfo, event.mOldNetwork,
- ConnectivityManager.CALLBACK_LOST, 0);
+ CALLBACK_LOST, 0);
}
}
@@ -10917,7 +10995,7 @@
if (null != localInfoChangedAgents) {
for (final NetworkAgentInfo nai : localInfoChangedAgents) {
notifyNetworkCallbacks(nai,
- ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
+ CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
}
}
@@ -10960,7 +11038,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(
@@ -11329,7 +11407,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()) {
@@ -11362,22 +11440,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) {
@@ -11401,6 +11475,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);
@@ -11440,7 +11521,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);
}
}
@@ -11467,7 +11548,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);
}
}
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/staticlibs/Android.bp b/staticlibs/Android.bp
index 34ea9ab..5d6169c 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -58,13 +58,8 @@
"//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",
],
@@ -345,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: [
@@ -445,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",
],
@@ -456,6 +450,126 @@
visibility: ["//packages/modules/Connectivity/service-t"],
}
+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:__subpackages__",
+ "//packages/modules/NetworkStack:__subpackages__",
+ ],
+ 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",
+ jarjar_rules: "jarjar-rules-shared.txt",
+ 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",
+ ],
+ visibility: [
+ "//packages/modules/Connectivity:__subpackages__",
+ ],
+ defaults_visibility: [
+ "//visibility:private",
+ ],
+ lint: {
+ strict_updatability_linting: true,
+ error_checks: ["NewApi"],
+ },
+}
+
+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"],
+}
+
+java_library {
+ name: "net-utils-tethering",
+ srcs: [
+ ":net-utils-all-srcs",
+ ":framework-connectivity-shared-srcs",
+ ],
+ defaults: ["net-utils-non-bootclasspath-defaults"],
+}
+
+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",
+ ],
+ },
+ 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 {
@@ -507,3 +621,41 @@
"//packages/modules/Wifi/service",
],
}
+
+// 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"],
+}
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..4a81c10
--- /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[] getShrinkedBackingArray() {
+ 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/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/SingleWriterBpfMap.java b/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
index cd6bfec..a638cc4 100644
--- a/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
+++ b/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
@@ -19,6 +19,7 @@
import android.system.ErrnoException;
import android.util.Pair;
+import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@@ -60,6 +61,7 @@
public class SingleWriterBpfMap<K extends Struct, V extends Struct> extends BpfMap<K, V> {
// HashMap instead of ArrayMap because it performs better on larger maps, and many maps used in
// our code can contain hundreds of items.
+ @GuardedBy("this")
private final HashMap<K, V> mCache = new HashMap<>();
// This should only ever be called (hence private) once for a given 'path'.
@@ -72,10 +74,12 @@
super(path, BPF_F_RDWR_EXCLUSIVE, key, value);
// Populate cache with the current map contents.
- K currentKey = super.getFirstKey();
- while (currentKey != null) {
- mCache.put(currentKey, super.getValue(currentKey));
- currentKey = super.getNextKey(currentKey);
+ synchronized (this) {
+ K currentKey = super.getFirstKey();
+ while (currentKey != null) {
+ mCache.put(currentKey, super.getValue(currentKey));
+ currentKey = super.getNextKey(currentKey);
+ }
}
}
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 a8c50d8..f1ff2e4 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -260,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/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 e9f8858..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",
@@ -76,7 +77,14 @@
test_suites: [
"general-tests",
],
+ // MoblyBinaryHostTest doesn't support unit_test.
test_options: {
- unit_test: true,
+ 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
index b75d464..8fcca37 100644
--- a/staticlibs/tests/unit/host/python/adb_utils_test.py
+++ b/staticlibs/tests/unit/host/python/adb_utils_test.py
@@ -12,15 +12,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import unittest
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(unittest.TestCase):
+class TestAdbUtils(base_test.BaseTestClass, parameterized.TestCase):
- def setUp(self):
+ 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
@@ -49,25 +55,21 @@
@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 self.assertRaises(UnexpectedBehaviorError):
+ 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, mock_get_value):
- # Test cases for different return values of get_value_of_key_from_dumpsys
- # TODO: Make it parameterized.
- test_cases = [
- ("Awake", True),
- ("Asleep", False),
- ("Dozing", False),
- ("SomeOtherState", False),
- ]
-
- for state_str, expected_result in test_cases:
- mock_get_value.return_value = state_str
- self.assertEqual(
- adb_utils._get_screen_state(self.mock_ad), expected_result
- )
+ 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 = (
@@ -76,36 +78,34 @@
result = adb_utils.get_value_of_key_from_dumpsys(
self.mock_ad, "power", "mWakefulness"
)
- self.assertEqual(result, "Awake")
+ 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, mock_get_value):
- # Test cases for different combinations of expected_state and get_value return value
- # TODO: Make it parameterized.
- test_cases = [
- (True, ["true"]), # Expect True, get True
- (False, ["false"]), # Expect False, get 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
- ]
-
- for expected_state, returned_value in test_cases:
- 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
- )
+ 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 self.assertRaises(UnexpectedBehaviorError):
+ with asserts.assert_raises(UnexpectedBehaviorError):
adb_utils.expect_dumpsys_state_with_retry(
self.mock_ad, "service", "key", True, 0
)
@@ -116,11 +116,7 @@
mock_get_value.return_value = None
# Expect the function to raise UnexpectedBehaviorError due to the exception
- with self.assertRaises(UnexpectedBehaviorError):
+ with asserts.assert_raises(UnexpectedBehaviorError):
adb_utils.expect_dumpsys_state_with_retry(
self.mock_ad, "service", "key", True
)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/staticlibs/tests/unit/host/python/apf_utils_test.py b/staticlibs/tests/unit/host/python/apf_utils_test.py
index f7fb93b..caaf959 100644
--- a/staticlibs/tests/unit/host/python/apf_utils_test.py
+++ b/staticlibs/tests/unit/host/python/apf_utils_test.py
@@ -12,18 +12,32 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import unittest
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,
+ send_broadcast_empty_ethercat_packet,
+ send_raw_packet_downstream,
)
+from net_tests_utils.host.python.assert_utils import UnexpectedBehaviorError
-class TestApfUtils(unittest.TestCase):
+class TestApfUtils(base_test.BaseTestClass, parameterized.TestCase):
- def setUp(self):
+ 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")
@@ -36,9 +50,8 @@
COUNTER_NAME1: 123
COUNTER_NAME2: 456
"""
- iface_name = "wlan0"
- counters = get_apf_counters_from_dumpsys(self.mock_ad, iface_name)
- self.assertEqual(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(
@@ -58,7 +71,7 @@
for dumpsys_output in test_cases:
mock_get_dumpsys.return_value = dumpsys_output
- with self.assertRaises(PatternNotFoundException):
+ 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")
@@ -68,10 +81,95 @@
"COUNTER_NAME1": 123,
"COUNTER_NAME2": 456,
}
- self.assertEqual(get_apf_counter(self.mock_ad, iface, "COUNTER_NAME1"), 123)
+ asserts.assert_equal(
+ get_apf_counter(self.mock_ad, iface, "COUNTER_NAME1"), 123
+ )
# Not found
- self.assertEqual(get_apf_counter(self.mock_ad, iface, "COUNTER_NAME3"), 0)
+ 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")
-if __name__ == "__main__":
- unittest.main()
+ @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.apf_utils.get_hardware_address")
+ @patch("net_tests_utils.host.python.apf_utils.send_raw_packet_downstream")
+ def test_send_broadcast_empty_ethercat_packet(
+ self,
+ mock_send_raw_packet_downstream: MagicMock,
+ mock_get_hardware_address: MagicMock,
+ ) -> None:
+ mock_get_hardware_address.return_value = "12:34:56:78:90:AB"
+ send_broadcast_empty_ethercat_packet(self.mock_ad, "eth0")
+ # Assuming you'll mock the packet construction part, verify calls to send_raw_packet_downstream.
+ mock_send_raw_packet_downstream.assert_called_once()
+
+ @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
+ iface_name = "eth0"
+ packet_in_hex = "AABBCCDDEEFF"
+ send_raw_packet_downstream(self.mock_ad, iface_name, packet_in_hex)
+ mock_adb_shell.assert_called_once_with(
+ self.mock_ad,
+ "cmd network_stack send-raw-packet-downstream"
+ f" {iface_name} {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, "eth0", "AABBCCDDEEFF")
+
+ @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, "eth0", "AABBCCDDEEFF")
+
+ @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
index b970d14..7a33373 100644
--- a/staticlibs/tests/unit/host/python/assert_utils_test.py
+++ b/staticlibs/tests/unit/host/python/assert_utils_test.py
@@ -12,11 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import unittest
+from mobly import asserts
+from mobly import base_test
from net_tests_utils.host.python.assert_utils import UnexpectedBehaviorError, expect_with_retry
-class TestAssertUtils(unittest.TestCase):
+class TestAssertUtils(base_test.BaseTestClass):
def test_predicate_succeed(self):
"""Test when the predicate becomes True within retries."""
@@ -28,12 +29,12 @@
return call_count > 2 # True on the third call
expect_with_retry(predicate, max_retries=5, retry_interval_sec=0)
- self.assertEqual(call_count, 3) # Ensure it was called exactly 3 times
+ 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 self.assertRaises(UnexpectedBehaviorError):
+ with asserts.assert_raises(UnexpectedBehaviorError):
expect_with_retry(
predicate=lambda: False, max_retries=3, retry_interval_sec=0
)
@@ -52,7 +53,9 @@
max_retries=5,
retry_interval_sec=0,
)
- self.assertFalse(retry_action_called) # Assert retry_action was NOT called
+ 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."""
@@ -62,14 +65,16 @@
nonlocal retry_action_called
retry_action_called = True
- with self.assertRaises(UnexpectedBehaviorError):
+ with asserts.assert_raises(UnexpectedBehaviorError):
expect_with_retry(
predicate=lambda: False,
retry_action=retry_action,
max_retries=1,
retry_interval_sec=0,
)
- self.assertFalse(retry_action_called) # Assert retry_action was NOT called
+ 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."""
@@ -79,11 +84,11 @@
nonlocal retry_action_called
retry_action_called = True
- with self.assertRaises(UnexpectedBehaviorError):
+ with asserts.assert_raises(UnexpectedBehaviorError):
expect_with_retry(
predicate=lambda: False,
retry_action=retry_action,
max_retries=2,
retry_interval_sec=0,
)
- self.assertTrue(retry_action_called)
+ 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
index c44f1a8..fa6a310 100644
--- a/staticlibs/tests/unit/host/python/run_tests.py
+++ b/staticlibs/tests/unit/host/python/run_tests.py
@@ -14,14 +14,22 @@
"""Main entrypoint for all of unittest."""
-import unittest
-
-# Import all unittest classes here, so it can be discovered by unittest module.
-# TODO: make the tests can be executed without manually import classes.
+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__":
- unittest.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
index 26ee9e2..d3b200a 100644
--- a/staticlibs/tests/unit/host/python/test_config.xml
+++ b/staticlibs/tests/unit/host/python/test_config.xml
@@ -14,9 +14,11 @@
limitations under the License.
-->
<configuration description="Config for NetworkStaticLibHostPythonTests">
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
- <test class="com.android.tradefed.testtype.python.PythonBinaryHostTest">
- <option name="par-file-name" value="NetworkStaticLibHostPythonTests" />
- <option name="test-timeout" value="3m" />
+ <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..bdcb8c0
--- /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 testGetShrinkedBackingArray() {
+ val array = GrowingIntArray(10)
+ array.add(-1)
+ array.add(2)
+
+ assertContentEquals(intArrayOf(-1, 2), array.shrinkedBackingArray)
+ }
+
+ @Test
+ fun testToString() {
+ assertEquals("[]", GrowingIntArray(10).toString())
+ assertEquals("[1,2,3]", GrowingIntArray(3).apply {
+ add(1)
+ add(2)
+ add(3)
+ }.toString())
+ }
+}
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/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/testutils/Android.bp b/staticlibs/testutils/Android.bp
index 8c71a91..4749e75 100644
--- a/staticlibs/testutils/Android.bp
+++ b/staticlibs/testutils/Android.bp
@@ -42,6 +42,7 @@
"net-utils-device-common-struct",
"net-utils-device-common-struct-base",
"net-utils-device-common-wear",
+ "net-utils-framework-connectivity",
"modules-utils-build_system",
],
lint: {
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..4a594e6
--- /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(100, 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 8687ac7..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)
@@ -125,6 +135,14 @@
)
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()
@@ -137,6 +155,7 @@
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()
@@ -163,6 +182,9 @@
if (shouldThreadLeakFailTest) {
it.addChild(leakMonitorDesc)
}
+ if (restoreDefaultNetwork) {
+ it.addChild(restoreDefaultNetworkDesc)
+ }
}
}
@@ -173,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/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/python/apf_utils.py b/staticlibs/testutils/host/python/apf_utils.py
index 331e970..415799c 100644
--- a/staticlibs/testutils/host/python/apf_utils.py
+++ b/staticlibs/testutils/host/python/apf_utils.py
@@ -12,15 +12,27 @@
# 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 net_tests_utils.host.python import adb_utils
+from mobly.controllers.android_device_lib.adb import AdbError
+from net_tests_utils.host.python import adb_utils, assert_utils
+
+
+# Constants.
+ETHER_BROADCAST = "FFFFFFFFFFFF"
+ETH_P_ETHERCAT = "88A4"
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:
@@ -75,3 +87,170 @@
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 send_broadcast_empty_ethercat_packet(
+ ad: android_device.AndroidDevice, iface_name: str
+) -> None:
+ """Transmits a broadcast empty EtherCat packet on the specified interface."""
+
+ # Get the interface's MAC address.
+ mac_address = get_hardware_address(ad, iface_name)
+
+ # TODO: Build packet by using scapy library.
+ # Ethernet header (14 bytes).
+ packet = ETHER_BROADCAST # Destination MAC (broadcast)
+ packet += mac_address.replace(":", "") # Source MAC
+ packet += ETH_P_ETHERCAT # EtherType (EtherCAT)
+
+ # EtherCAT header (2 bytes) + 44 bytes of zero padding.
+ packet += "00" * 46
+
+ # Send the packet using a raw socket.
+ send_raw_packet_downstream(ad, iface_name, packet)
+
+
+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/mdns_utils.py b/staticlibs/testutils/host/python/mdns_utils.py
index ec1fea0..1234e54 100644
--- a/staticlibs/testutils/host/python/mdns_utils.py
+++ b/staticlibs/testutils/host/python/mdns_utils.py
@@ -12,9 +12,24 @@
# 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:
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/cts/multidevices/connectivity_multi_devices_test.py b/tests/cts/multidevices/connectivity_multi_devices_test.py
index 840831b..7e7bbf5 100644
--- a/tests/cts/multidevices/connectivity_multi_devices_test.py
+++ b/tests/cts/multidevices/connectivity_multi_devices_test.py
@@ -1,14 +1,17 @@
# Lint as: python3
"""Connectivity multi devices tests."""
import sys
+
+from mobly import asserts
from mobly import base_test
from mobly import test_runner
from mobly import utils
from mobly.controllers import android_device
-from net_tests_utils.host.python import mdns_utils, tether_utils
+from net_tests_utils.host.python import adb_utils, apf_utils, assert_utils, mdns_utils, tether_utils
from net_tests_utils.host.python.tether_utils import UpstreamType
CONNECTIVITY_MULTI_DEVICES_SNIPPET_PACKAGE = "com.google.snippet.connectivity"
+COUNTER_DROPPED_ETHERTYPE_NOT_ALLOWED = "DROPPED_ETHERTYPE_NOT_ALLOWED"
class ConnectivityMultiDevicesTest(base_test.BaseTestClass):
@@ -65,22 +68,67 @@
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
+ self.serverDevice, self.clientDevice, UpstreamType.NONE
)
mdns_utils.register_mdns_service_and_discover_resolve(
- self.clientDevice, self.serverDevice
+ self.clientDevice, self.serverDevice
)
finally:
- mdns_utils.cleanup_mdns_service(
- self.clientDevice, self.serverDevice
- )
+ mdns_utils.cleanup_mdns_service(self.clientDevice, self.serverDevice)
tether_utils.cleanup_tethering_for_upstream_type(
- self.serverDevice, UpstreamType.NONE
+ self.serverDevice, UpstreamType.NONE
)
+ def test_apf_drop_ethercat(self):
+ tether_utils.assume_hotspot_test_preconditions(
+ self.serverDevice, self.clientDevice, UpstreamType.NONE
+ )
+ client = self.clientDevice.connectivity_multi_devices_snippet
+ try:
+ server_iface_name, client_network = (
+ tether_utils.setup_hotspot_and_client_for_upstream_type(
+ self.serverDevice, self.clientDevice, UpstreamType.NONE
+ )
+ )
+ client_iface_name = client.getInterfaceNameFromNetworkHandle(client_network)
+
+ adb_utils.set_doze_mode(self.clientDevice, True)
+
+ count_before_test = apf_utils.get_apf_counter(
+ self.clientDevice,
+ client_iface_name,
+ COUNTER_DROPPED_ETHERTYPE_NOT_ALLOWED,
+ )
+ try:
+ apf_utils.send_broadcast_empty_ethercat_packet(
+ self.serverDevice, server_iface_name
+ )
+ except apf_utils.UnsupportedOperationException:
+ asserts.skip(
+ "NetworkStack is too old to support send raw packet, skip test."
+ )
+
+ assert_utils.expect_with_retry(
+ lambda: apf_utils.get_apf_counter(
+ self.clientDevice,
+ client_iface_name,
+ COUNTER_DROPPED_ETHERTYPE_NOT_ALLOWED,
+ )
+ > count_before_test
+ )
+ finally:
+ adb_utils.set_doze_mode(self.clientDevice, False)
+ tether_utils.cleanup_tethering_for_upstream_type(
+ self.serverDevice, UpstreamType.NONE
+ )
+
+
if __name__ == "__main__":
# Take test args
if "--" in sys.argv:
diff --git a/tests/cts/multidevices/snippet/Android.bp b/tests/cts/multidevices/snippet/Android.bp
index b0b32c2..c94087e 100644
--- a/tests/cts/multidevices/snippet/Android.bp
+++ b/tests/cts/multidevices/snippet/Android.bp
@@ -26,6 +26,7 @@
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 967e581..4637497 100644
--- a/tests/cts/multidevices/snippet/AndroidManifest.xml
+++ b/tests/cts/multidevices/snippet/AndroidManifest.xml
@@ -21,6 +21,8 @@
<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
@@ -28,7 +30,8 @@
<meta-data
android:name="mobly-snippets"
android:value="com.google.snippet.connectivity.ConnectivityMultiDevicesSnippet,
- com.google.snippet.connectivity.MdnsMultiDevicesSnippet" />
+ 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/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..024d3bf 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -48,8 +48,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 5662fca..9ac2c67 100644
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -410,6 +410,12 @@
}
}
+ 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)
@@ -436,12 +442,7 @@
assumeApfVersionSupportAtLeast(4)
// clear any active APF filter
- var gen = ApfV4Generator(
- caps.apfVersionSupported,
- caps.maximumApfProgramSize,
- caps.maximumApfProgramSize
- ).addPass()
- installProgram(gen.generate())
+ clearApfMemory()
readProgram() // wait for install completion
// Assert that initial ping does not get filtered.
@@ -455,7 +456,7 @@
assertThat(packetReader.expectPingReply()).isEqualTo(data)
// Generate an APF program that drops the next ping
- gen = ApfV4Generator(
+ val gen = ApfV4Generator(
caps.apfVersionSupported,
caps.maximumApfProgramSize,
caps.maximumApfProgramSize
@@ -472,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()
@@ -518,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) {
@@ -567,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) }
@@ -608,8 +606,7 @@
gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_16384THS)
gen.addStoreCounter(FILTER_AGE_16384THS, R0)
- installProgram(gen.generate())
- readProgram() // wait for install completion
+ installAndVerifyProgram(gen.generate())
val payloadSize = 56
val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
@@ -717,8 +714,7 @@
.addPass()
.generate()
- installProgram(program)
- readProgram() // Ensure installation is complete
+ installAndVerifyProgram(program)
packetReader.sendPing(payload, payloadSize)
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 21eb90f..9458460 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;
@@ -211,6 +210,7 @@
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;
@@ -274,7 +274,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();
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/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/NsdManagerDownstreamTetheringTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
index 284fcae..f45f881 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
@@ -59,7 +59,6 @@
@After
override fun tearDown() {
super.tearDown()
- setIncludeTestInterfaces(false)
}
@Test
@@ -107,7 +106,6 @@
@Test
fun testMdnsDiscoveryWorkOnTetheringInterface() {
assumeFalse(isInterfaceForTetheringAvailable())
- setIncludeTestInterfaces(true)
var downstreamIface: TestNetworkInterface? = null
var tetheringEventCallback: MyTetheringEventCallback? = null
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/native/connectivity_native_test/Android.bp b/tests/native/connectivity_native_test/Android.bp
index c5088c6..ab2f28c 100644
--- a/tests/native/connectivity_native_test/Android.bp
+++ b/tests/native/connectivity_native_test/Android.bp
@@ -32,7 +32,7 @@
"libmodules-utils-build",
"libutils",
],
- compile_multilib: "first",
+ compile_multilib: "both",
}
filegroup {
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/com/android/server/CallbackQueueTest.kt b/tests/unit/java/com/android/server/CallbackQueueTest.kt
new file mode 100644
index 0000000..a6dd5c3
--- /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.shrinkedBackingArray)
+ 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 be7f2a3..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:
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/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/CSDeclaredMethodsForCallbacksTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt
index cf990b1..a7083dc 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt
@@ -17,8 +17,11 @@
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
@@ -35,7 +38,9 @@
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
@@ -129,6 +134,32 @@
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) {
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..de56ae5 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)
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 2f60d9a..e6f272b 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -111,7 +111,6 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserManager;
-import android.provider.Settings;
import android.util.Log;
import android.util.SparseArray;
@@ -139,6 +138,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;
@@ -210,7 +210,6 @@
private final ThreadPersistentSettings mPersistentSettings;
private final UserManager mUserManager;
private boolean mUserRestricted;
- private boolean mAirplaneModeOn;
private boolean mForceStopOtDaemonEnabled;
private BorderRouterConfigurationParcel mBorderRouterConfig;
@@ -344,8 +343,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) {
@@ -375,19 +374,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() {
@@ -420,8 +444,6 @@
requestThreadNetwork();
mUserRestricted = isThreadUserRestricted();
registerUserRestrictionsReceiver();
- mAirplaneModeOn = isAirplaneModeOn();
- registerAirplaneModeReceiver();
maybeInitializeOtDaemon();
});
}
@@ -503,15 +525,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 {
@@ -598,10 +611,12 @@
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- mHandler.post(() -> onUserRestrictionsChanged(isThreadUserRestricted()));
+ onUserRestrictionsChanged(isThreadUserRestricted());
}
},
- new IntentFilter(UserManager.ACTION_USER_RESTRICTIONS_CHANGED));
+ new IntentFilter(UserManager.ACTION_USER_RESTRICTIONS_CHANGED),
+ null /* broadcastPermission */,
+ mHandler);
}
private void onUserRestrictionsChanged(boolean newUserRestrictedState) {
@@ -648,72 +663,13 @@
return mUserManager.hasUserRestriction(DISALLOW_THREAD_NETWORK);
}
- private void registerAirplaneModeReceiver() {
- mContext.registerReceiver(
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mHandler.post(() -> onAirplaneModeChanged(isAirplaneModeOn()));
- }
- },
- new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
- }
-
- 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);
}
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 41f34ff..11c4819 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -859,7 +859,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
@@ -886,7 +885,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
diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
index 8c63d37..b6d9aa3 100644
--- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
+++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
@@ -17,12 +17,13 @@
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.thread.utils.IntegrationTestUtils.DEFAULT_DATASET;
import static android.net.thread.utils.IntegrationTestUtils.getIpv6LinkAddresses;
import static android.net.thread.utils.IntegrationTestUtils.isExpectedIcmpv6Packet;
import static android.net.thread.utils.IntegrationTestUtils.isFromIpv6Source;
import static android.net.thread.utils.IntegrationTestUtils.isInMulticastGroup;
import static android.net.thread.utils.IntegrationTestUtils.isToIpv6Destination;
+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;
@@ -33,7 +34,6 @@
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;
@@ -95,17 +95,6 @@
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);
-
@Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
private final Context mContext = ApplicationProvider.getApplicationContext();
@@ -171,7 +160,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
mInfraDevice.sendEchoRequest(ftd.getOmrAddress());
@@ -193,7 +182,7 @@
startInfraDeviceAndWaitForOnLinkAddr();
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
mInfraDevice.sendEchoRequest(ftd.getOmrAddress());
@@ -213,7 +202,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;
@@ -243,7 +232,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
Inet6Address ftdOmr = requireNonNull(ftd.getOmrAddress());
Inet6Address ftdMlEid = requireNonNull(ftd.getMlEid());
@@ -285,7 +274,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
ftd.subscribeMulticastAddress(GROUP_ADDR_SCOPE_5);
@@ -307,7 +296,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
ftd.subscribeMulticastAddress(GROUP_ADDR_SCOPE_3);
@@ -328,7 +317,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
subscribeMulticastAddressAndWait(ftd, GROUP_ADDR_SCOPE_5);
mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
@@ -360,7 +349,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
ftd.subscribeMulticastAddress(GROUP_ADDR_SCOPE_3);
mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_3);
@@ -382,7 +371,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_4);
@@ -407,11 +396,11 @@
*/
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);
@@ -443,11 +432,11 @@
*/
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);
@@ -473,7 +462,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
Inet6Address ftdOmr = ftd.getOmrAddress();
ftd.ping(GROUP_ADDR_SCOPE_5);
@@ -499,7 +488,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
ftd.ping(GROUP_ADDR_SCOPE_3);
@@ -521,7 +510,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
Inet6Address ftdLla = ftd.getLinkLocalAddress();
assertNotNull(ftdLla);
@@ -544,7 +533,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
List<Inet6Address> ftdMlas = ftd.getMeshLocalAddresses();
assertFalse(ftdMlas.isEmpty());
@@ -571,7 +560,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
subscribeMulticastAddressAndWait(ftd, GROUP_ADDR_SCOPE_5);
Inet6Address ftdOmr = ftd.getOmrAddress();
@@ -599,7 +588,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
Inet6Address ftdOmr = ftd.getOmrAddress();
// Destroy infra link and re-create
@@ -629,15 +618,6 @@
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);
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..8440bbc 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,
@@ -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..7b0c415 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
@@ -22,8 +22,11 @@
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 +39,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;
@@ -84,6 +88,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() {}
/**
@@ -413,6 +428,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/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index 6e2369f..eaf11b1 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -26,7 +26,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;
@@ -49,6 +48,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;
@@ -68,7 +69,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;
@@ -96,11 +96,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;
@@ -150,7 +150,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;
@@ -193,8 +192,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)))
@@ -203,8 +200,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);
@@ -247,8 +244,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();
@@ -258,19 +255,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
@@ -343,6 +328,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);
@@ -437,100 +477,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<>();
@@ -542,7 +488,9 @@
.when(mContext)
.registerReceiver(
any(BroadcastReceiver.class),
- argThat(actualIntentFilter -> actualIntentFilter.hasAction(action)));
+ argThat(actualIntentFilter -> actualIntentFilter.hasAction(action)),
+ any(),
+ any());
return receiverRef;
}