Merge "Invoke setDataEnabled instead of setDataEnabledForReason on R device" into main
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index dacdaf2..5ae1ef9 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -19,6 +19,14 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+framework_remoteauth_srcs = [":framework-remoteauth-java-sources"]
+framework_remoteauth_api_srcs = []
+
+java_defaults {
+ name: "enable-remoteauth-targets",
+ enabled: true,
+}
+
// Include build rules from Sources.bp
build = ["Sources.bp"]
@@ -43,8 +51,7 @@
":framework-connectivity-tiramisu-updatable-sources",
":framework-nearby-java-sources",
":framework-thread-sources",
- ":framework-remoteauth-java-sources",
- ],
+ ] + framework_remoteauth_srcs,
libs: [
"unsupportedappusage",
"app-compat-annotations",
@@ -115,6 +122,7 @@
"framework-connectivity-t-defaults",
"enable-framework-connectivity-t-targets",
],
+ api_srcs: framework_remoteauth_api_srcs,
// Do not add static_libs to this library: put them in framework-connectivity instead.
// The jarjar rules are only so that references to jarjared utils in
// framework-connectivity-pre-jarjar match at runtime.
diff --git a/framework-t/src/android/net/nsd/OffloadServiceInfo.java b/framework-t/src/android/net/nsd/OffloadServiceInfo.java
index 7bd5a7d..2c839bc 100644
--- a/framework-t/src/android/net/nsd/OffloadServiceInfo.java
+++ b/framework-t/src/android/net/nsd/OffloadServiceInfo.java
@@ -161,6 +161,23 @@
}
/**
+ * Create a new OffloadServiceInfo with payload updated.
+ *
+ * @hide
+ */
+ @NonNull
+ public OffloadServiceInfo withOffloadPayload(@NonNull byte[] offloadPayload) {
+ return new OffloadServiceInfo(
+ this.getKey(),
+ this.getSubtypes(),
+ this.getHostname(),
+ offloadPayload,
+ this.getPriority(),
+ this.getOffloadType()
+ );
+ }
+
+ /**
* Get the offloadType.
* <p>
* For example, if the {@link com.android.server.NsdService} requests the OffloadEngine to both
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index 73feee4..fc680d9 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -76,6 +76,19 @@
}
static Status initPrograms(const char* cg2_path) {
+ // This code was mainlined in T, so this should be trivially satisfied.
+ if (!modules::sdklevel::IsAtLeastT()) abort();
+
+ // S requires eBPF support which was only added in 4.9, so this should be satisfied.
+ if (!bpf::isAtLeastKernelVersion(4, 9, 0)) abort();
+
+ // U bumps the kernel requirement up to 4.14
+ if (modules::sdklevel::IsAtLeastU() && !bpf::isAtLeastKernelVersion(4, 14, 0)) abort();
+
+ // V bumps the kernel requirement up to 4.19
+ if (modules::sdklevel::IsAtLeastV() && !bpf::isAtLeastKernelVersion(4, 19, 0)) abort();
+
+ // U mandates this mount point (though it should also be the case on T)
if (modules::sdklevel::IsAtLeastU() && !!strcmp(cg2_path, "/sys/fs/cgroup")) abort();
unique_fd cg_fd(open(cg2_path, O_DIRECTORY | O_RDONLY | O_CLOEXEC));
diff --git a/remoteauth/service/Android.bp b/remoteauth/service/Android.bp
index dba8b75..dfaf8cf 100644
--- a/remoteauth/service/Android.bp
+++ b/remoteauth/service/Android.bp
@@ -27,6 +27,7 @@
srcs: [":remoteauth-service-srcs"],
required: ["libremoteauth_jni_rust_defaults"],
defaults: [
+ "enable-remoteauth-targets",
"framework-system-server-module-defaults",
],
libs: [
diff --git a/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthService.java b/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthService.java
index 41ce89a..9374ace 100644
--- a/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthService.java
+++ b/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthService.java
@@ -27,6 +27,7 @@
/** Service implementing remoteauth functionality. */
public class RemoteAuthService extends IRemoteAuthService.Stub {
public static final String TAG = "RemoteAuthService";
+ public static final String SERVICE_NAME = Context.REMOTE_AUTH_SERVICE;
public RemoteAuthService(Context context) {
Preconditions.checkNotNull(context);
diff --git a/remoteauth/tests/unit/Android.bp b/remoteauth/tests/unit/Android.bp
index 629d360..37c78c7 100644
--- a/remoteauth/tests/unit/Android.bp
+++ b/remoteauth/tests/unit/Android.bp
@@ -18,7 +18,10 @@
android_test {
name: "RemoteAuthUnitTests",
- defaults: ["mts-target-sdk-version-current"],
+ defaults: [
+ "enable-remoteauth-targets",
+ "mts-target-sdk-version-current"
+ ],
sdk_version: "test_current",
min_sdk_version: "31",
diff --git a/service-t/Android.bp b/service-t/Android.bp
index 83caf35..08527a3 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -19,6 +19,8 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+service_remoteauth_pre_jarjar_lib = "service-remoteauth-pre-jarjar"
+
// Include build rules from Sources.bp
build = ["Sources.bp"]
@@ -56,7 +58,7 @@
"service-connectivity-pre-jarjar",
"service-nearby-pre-jarjar",
"service-thread-pre-jarjar",
- "service-remoteauth-pre-jarjar",
+ service_remoteauth_pre_jarjar_lib,
"ServiceConnectivityResources",
"unsupportedappusage",
],
diff --git a/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java b/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
index 597c06f..6b03daa 100644
--- a/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
+++ b/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
@@ -24,16 +24,22 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.ConnectivityStatsLog;
+import java.util.Random;
+
/**
* Class to record the NetworkNsdReported into statsd. Each client should create this class to
* report its data.
*/
public class NetworkNsdReportedMetrics {
+ // The upper bound for the random number used in metrics data sampling determines the possible
+ // sample rate.
+ private static final int RANDOM_NUMBER_UPPER_BOUND = 1000;
// Whether this client is using legacy backend.
private final boolean mIsLegacy;
// The client id.
private final int mClientId;
private final Dependencies mDependencies;
+ private final Random mRandom;
public NetworkNsdReportedMetrics(boolean isLegacy, int clientId) {
this(isLegacy, clientId, new Dependencies());
@@ -44,6 +50,7 @@
mIsLegacy = isLegacy;
mClientId = clientId;
mDependencies = dependencies;
+ mRandom = dependencies.makeRandomGenerator();
}
/**
@@ -67,7 +74,18 @@
event.getFoundCallbackCount(),
event.getLostCallbackCount(),
event.getRepliedRequestsCount(),
- event.getSentQueryCount());
+ event.getSentQueryCount(),
+ event.getSentPacketCount(),
+ event.getConflictDuringProbingCount(),
+ event.getConflictAfterProbingCount(),
+ event.getRandomNumber());
+ }
+
+ /**
+ * @see Random
+ */
+ public Random makeRandomGenerator() {
+ return new Random();
}
}
@@ -75,6 +93,7 @@
final Builder builder = NetworkNsdReported.newBuilder();
builder.setIsLegacy(mIsLegacy);
builder.setClientId(mClientId);
+ builder.setRandomNumber(mRandom.nextInt(RANDOM_NUMBER_UPPER_BOUND));
return builder;
}
@@ -113,14 +132,23 @@
*
* @param transactionId The transaction id of service registration.
* @param durationMs The duration of service stayed registered.
+ * @param repliedRequestsCount The replied request count of this service before unregistered it.
+ * @param sentPacketCount The total sent packet count of this service before unregistered it.
+ * @param conflictDuringProbingCount The number of conflict during probing.
+ * @param conflictAfterProbingCount The number of conflict after probing.
*/
- public void reportServiceUnregistration(int transactionId, long durationMs) {
+ public void reportServiceUnregistration(int transactionId, long durationMs,
+ int repliedRequestsCount, int sentPacketCount, int conflictDuringProbingCount,
+ int conflictAfterProbingCount) {
final Builder builder = makeReportedBuilder();
builder.setTransactionId(transactionId);
builder.setType(NsdEventType.NET_REGISTER);
builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_UNREGISTERED);
builder.setEventDurationMillisec(durationMs);
- // TODO: Report repliedRequestsCount
+ builder.setRepliedRequestsCount(repliedRequestsCount);
+ builder.setSentPacketCount(sentPacketCount);
+ builder.setConflictDuringProbingCount(conflictDuringProbingCount);
+ builder.setConflictAfterProbingCount(conflictAfterProbingCount);
mDependencies.statsWrite(builder.build());
}
diff --git a/service-t/src/com/android/server/ConnectivityServiceInitializer.java b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
index 2da067a..624c5df 100644
--- a/service-t/src/com/android/server/ConnectivityServiceInitializer.java
+++ b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
@@ -17,7 +17,6 @@
package com.android.server;
import android.content.Context;
-import android.remoteauth.RemoteAuthManager;
import android.util.Log;
import com.android.modules.utils.build.SdkLevel;
@@ -90,8 +89,8 @@
}
if (mRemoteAuthService != null) {
- Log.i(TAG, "Registering " + RemoteAuthManager.REMOTE_AUTH_SERVICE);
- publishBinderService(RemoteAuthManager.REMOTE_AUTH_SERVICE, mRemoteAuthService,
+ Log.i(TAG, "Registering " + RemoteAuthService.SERVICE_NAME);
+ publishBinderService(RemoteAuthService.SERVICE_NAME, mRemoteAuthService,
/* allowIsolated= */ false);
}
}
@@ -157,8 +156,7 @@
} catch (UnsupportedOperationException e) {
// RemoteAuth is not yet supported in all branches
// TODO: remove catch clause when it is available.
- Log.i(TAG, "Skipping unsupported service "
- + RemoteAuthManager.REMOTE_AUTH_SERVICE);
+ Log.i(TAG, "Skipping unsupported service " + RemoteAuthService.SERVICE_NAME);
return null;
}
}
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 6485e99..b9acc48 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -17,15 +17,20 @@
package com.android.server;
import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.NETWORK_STACK;
import static android.net.ConnectivityManager.NETID_UNSET;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.net.nsd.NsdManager.MDNS_DISCOVERY_MANAGER_EVENT;
import static android.net.nsd.NsdManager.MDNS_SERVICE_EVENT;
import static android.net.nsd.NsdManager.RESOLVE_SERVICE_SUCCEEDED;
import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
import static com.android.modules.utils.build.SdkLevel.isAtLeastU;
+import static com.android.networkstack.apishim.ConstantsShim.REGISTER_NSD_OFFLOAD_ENGINE;
+import static com.android.server.connectivity.mdns.MdnsAdvertiser.AdvertiserMetrics;
+import static com.android.server.connectivity.mdns.MdnsConstants.NO_PACKET;
import static com.android.server.connectivity.mdns.MdnsRecord.MAX_LABEL_LENGTH;
import static com.android.server.connectivity.mdns.util.MdnsUtils.Clock;
@@ -75,6 +80,7 @@
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.metrics.NetworkNsdReportedMetrics;
+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.InetAddressUtils;
@@ -988,14 +994,20 @@
// instead of looking at the flag value.
final long stopTimeMs = mClock.elapsedRealtime();
if (request instanceof AdvertiserClientRequest) {
+ final AdvertiserMetrics metrics =
+ mAdvertiser.getAdvertiserMetrics(transactionId);
mAdvertiser.removeService(transactionId);
clientInfo.onUnregisterServiceSucceeded(clientRequestId, transactionId,
- request.calculateRequestDurationMs(stopTimeMs));
+ request.calculateRequestDurationMs(stopTimeMs), metrics);
} else {
if (unregisterService(transactionId)) {
clientInfo.onUnregisterServiceSucceeded(clientRequestId,
transactionId,
- request.calculateRequestDurationMs(stopTimeMs));
+ request.calculateRequestDurationMs(stopTimeMs),
+ new AdvertiserMetrics(NO_PACKET /* repliedRequestsCount */,
+ NO_PACKET /* sentPacketCount */,
+ 0 /* conflictDuringProbingCount */,
+ 0 /* conflictAfterProbingCount */));
} else {
clientInfo.onUnregisterServiceFailed(
clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
@@ -1191,7 +1203,7 @@
// TODO: Limits the number of registrations created by a given class.
mOffloadEngines.register(offloadEngineInfo.mOffloadEngine,
offloadEngineInfo);
- // TODO: Sends all the existing OffloadServiceInfos back.
+ sendAllOffloadServiceInfos(offloadEngineInfo);
break;
case NsdManager.UNREGISTER_OFFLOAD_ENGINE:
mOffloadEngines.unregister((IOffloadEngine) msg.obj);
@@ -1877,6 +1889,21 @@
}
}
+ private void sendAllOffloadServiceInfos(@NonNull OffloadEngineInfo offloadEngineInfo) {
+ final String targetInterface = offloadEngineInfo.mInterfaceName;
+ final IOffloadEngine offloadEngine = offloadEngineInfo.mOffloadEngine;
+ final List<MdnsAdvertiser.OffloadServiceInfoWrapper> offloadWrappers =
+ mAdvertiser.getAllInterfaceOffloadServiceInfos(targetInterface);
+ for (MdnsAdvertiser.OffloadServiceInfoWrapper wrapper : offloadWrappers) {
+ try {
+ offloadEngine.onOffloadServiceUpdated(wrapper.mOffloadServiceInfo);
+ } catch (RemoteException e) {
+ // Can happen in regular cases, do not log a stacktrace
+ Log.i(TAG, "Failed to send offload callback, remote died: " + e.getMessage());
+ }
+ }
+ }
+
private void sendOffloadServiceInfosUpdate(@NonNull String targetInterfaceName,
@NonNull OffloadServiceInfo offloadServiceInfo, boolean isRemove) {
final int count = mOffloadEngines.beginBroadcast();
@@ -1900,7 +1927,7 @@
}
} catch (RemoteException e) {
// Can happen in regular cases, do not log a stacktrace
- Log.i(TAG, "Failed to send offload callback, remote died", e);
+ Log.i(TAG, "Failed to send offload callback, remote died: " + e.getMessage());
}
}
} finally {
@@ -2083,9 +2110,7 @@
public void registerOffloadEngine(String ifaceName, IOffloadEngine cb,
@OffloadEngine.OffloadCapability long offloadCapabilities,
@OffloadEngine.OffloadType long offloadTypes) {
- // TODO: Relax the permission because NETWORK_SETTINGS is a signature permission, and
- // it may not be possible for all the callers of this API to have it.
- PermissionUtils.enforceNetworkStackPermissionOr(mContext, NETWORK_SETTINGS);
+ checkOffloadEnginePermission(mContext);
Objects.requireNonNull(ifaceName);
Objects.requireNonNull(cb);
mNsdStateMachine.sendMessage(
@@ -2096,13 +2121,31 @@
@Override
public void unregisterOffloadEngine(IOffloadEngine cb) {
- // TODO: Relax the permission because NETWORK_SETTINGS is a signature permission, and
- // it may not be possible for all the callers of this API to have it.
- PermissionUtils.enforceNetworkStackPermissionOr(mContext, NETWORK_SETTINGS);
+ checkOffloadEnginePermission(mContext);
Objects.requireNonNull(cb);
mNsdStateMachine.sendMessage(
mNsdStateMachine.obtainMessage(NsdManager.UNREGISTER_OFFLOAD_ENGINE, cb));
}
+
+ private static void checkOffloadEnginePermission(Context context) {
+ if (!SdkLevel.isAtLeastT()) {
+ throw new SecurityException("API is not available in before API level 33");
+ }
+ // REGISTER_NSD_OFFLOAD_ENGINE was only added to the SDK in V, but may
+ // be back ported to older builds: accept it as long as it's signature-protected
+ if (PermissionUtils.checkAnyPermissionOf(context, REGISTER_NSD_OFFLOAD_ENGINE)
+ && (SdkLevel.isAtLeastV() || PermissionUtils.isSystemSignaturePermission(
+ context, REGISTER_NSD_OFFLOAD_ENGINE))) {
+ return;
+ }
+ if (PermissionUtils.checkAnyPermissionOf(context, NETWORK_STACK,
+ PERMISSION_MAINLINE_NETWORK_STACK, NETWORK_SETTINGS)) {
+ return;
+ }
+ throw new SecurityException("Requires one of the following permissions: "
+ + String.join(", ", List.of(REGISTER_NSD_OFFLOAD_ENGINE, NETWORK_STACK,
+ PERMISSION_MAINLINE_NETWORK_STACK, NETWORK_SETTINGS)) + ".");
+ }
}
private void sendNsdStateChangeBroadcast(boolean isEnabled) {
@@ -2461,9 +2504,14 @@
}
if (request instanceof AdvertiserClientRequest) {
+ final AdvertiserMetrics metrics =
+ mAdvertiser.getAdvertiserMetrics(transactionId);
mAdvertiser.removeService(transactionId);
mMetrics.reportServiceUnregistration(transactionId,
- request.calculateRequestDurationMs(mClock.elapsedRealtime()));
+ request.calculateRequestDurationMs(mClock.elapsedRealtime()),
+ metrics.mRepliedRequestsCount, metrics.mSentPacketCount,
+ metrics.mConflictDuringProbingCount,
+ metrics.mConflictAfterProbingCount);
continue;
}
@@ -2489,7 +2537,11 @@
case NsdManager.REGISTER_SERVICE:
unregisterService(transactionId);
mMetrics.reportServiceUnregistration(transactionId,
- request.calculateRequestDurationMs(mClock.elapsedRealtime()));
+ request.calculateRequestDurationMs(mClock.elapsedRealtime()),
+ NO_PACKET /* repliedRequestsCount */,
+ NO_PACKET /* sentPacketCount */,
+ 0 /* conflictDuringProbingCount */,
+ 0 /* conflictAfterProbingCount */);
break;
default:
break;
@@ -2628,8 +2680,11 @@
}
}
- void onUnregisterServiceSucceeded(int listenerKey, int transactionId, long durationMs) {
- mMetrics.reportServiceUnregistration(transactionId, durationMs);
+ void onUnregisterServiceSucceeded(int listenerKey, int transactionId, long durationMs,
+ AdvertiserMetrics metrics) {
+ mMetrics.reportServiceUnregistration(transactionId, durationMs,
+ metrics.mRepliedRequestsCount, metrics.mSentPacketCount,
+ metrics.mConflictDuringProbingCount, metrics.mConflictAfterProbingCount);
try {
mCb.onUnregisterServiceSucceeded(listenerKey);
} catch (RemoteException e) {
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 dd72d11..913d233 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -16,6 +16,7 @@
package com.android.server.connectivity.mdns;
+import static com.android.server.connectivity.mdns.MdnsConstants.NO_PACKET;
import static com.android.server.connectivity.mdns.MdnsRecord.MAX_LABEL_LENGTH;
import android.annotation.NonNull;
@@ -37,6 +38,7 @@
import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -117,6 +119,17 @@
}
}
+ /**
+ * Gets the current status of the OffloadServiceInfos per interface.
+ * @param interfaceName the target interfaceName
+ * @return the list of current offloaded services.
+ */
+ @NonNull
+ public List<OffloadServiceInfoWrapper> getAllInterfaceOffloadServiceInfos(
+ @NonNull String interfaceName) {
+ return mInterfaceOffloadServices.getOrDefault(interfaceName, Collections.emptyList());
+ }
+
private final MdnsInterfaceAdvertiser.Callback mInterfaceAdvertiserCb =
new MdnsInterfaceAdvertiser.Callback() {
@Override
@@ -134,9 +147,12 @@
interfaceName, k -> new ArrayList<>());
// Remove existing offload services from cache for update.
existingOffloadServiceInfoWrappers.removeIf(item -> item.mServiceId == serviceId);
+
+ byte[] rawOffloadPacket = advertiser.getRawOffloadPayload(serviceId);
final OffloadServiceInfoWrapper newOffloadServiceInfoWrapper = createOffloadService(
serviceId,
- registration);
+ registration,
+ rawOffloadPacket);
existingOffloadServiceInfoWrappers.add(newOffloadServiceInfoWrapper);
mCb.onOffloadStartOrUpdate(interfaceName,
newOffloadServiceInfoWrapper.mOffloadServiceInfo);
@@ -165,6 +181,7 @@
// (with the old, conflicting, actually not used name as argument... The new
// implementation will send callbacks with the new name).
registration.mNotifiedRegistrationSuccess = false;
+ registration.mConflictAfterProbingCount++;
// The service was done probing, just reset it to probing state (RFC6762 9.)
forAllAdvertisers(a -> {
@@ -180,6 +197,7 @@
registration.updateForConflict(
registration.makeNewServiceInfoForConflict(1 /* renameCount */),
1 /* renameCount */);
+ registration.mConflictDuringProbingCount++;
// Keep renaming if the new name conflicts in local registrations
updateRegistrationUntilNoConflict((net, adv) -> adv.hasRegistration(registration),
@@ -345,6 +363,22 @@
}
}
+ int getServiceRepliedRequestsCount(int id) {
+ int repliedRequestsCount = NO_PACKET;
+ for (int i = 0; i < mAdvertisers.size(); i++) {
+ repliedRequestsCount += mAdvertisers.valueAt(i).getServiceRepliedRequestsCount(id);
+ }
+ return repliedRequestsCount;
+ }
+
+ int getSentPacketCount(int id) {
+ int sentPacketCount = NO_PACKET;
+ for (int i = 0; i < mAdvertisers.size(); i++) {
+ sentPacketCount += mAdvertisers.valueAt(i).getSentPacketCount(id);
+ }
+ return sentPacketCount;
+ }
+
@Override
public void onSocketCreated(@NonNull SocketKey socketKey,
@NonNull MdnsInterfaceSocket socket,
@@ -381,13 +415,38 @@
public void onAddressesChanged(@NonNull SocketKey socketKey,
@NonNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> addresses) {
final MdnsInterfaceAdvertiser advertiser = mAdvertisers.get(socket);
- if (advertiser != null) advertiser.updateAddresses(addresses);
+ if (advertiser == null) {
+ return;
+ }
+ advertiser.updateAddresses(addresses);
+ // Update address should trigger offload packet update.
+ final String interfaceName = advertiser.getSocketInterfaceName();
+ final List<OffloadServiceInfoWrapper> existingOffloadServiceInfoWrappers =
+ mInterfaceOffloadServices.get(interfaceName);
+ if (existingOffloadServiceInfoWrappers == null) {
+ return;
+ }
+ final List<OffloadServiceInfoWrapper> updatedOffloadServiceInfoWrappers =
+ new ArrayList<>(existingOffloadServiceInfoWrappers.size());
+ for (OffloadServiceInfoWrapper oldWrapper : existingOffloadServiceInfoWrappers) {
+ OffloadServiceInfoWrapper newWrapper = new OffloadServiceInfoWrapper(
+ oldWrapper.mServiceId,
+ oldWrapper.mOffloadServiceInfo.withOffloadPayload(
+ advertiser.getRawOffloadPayload(oldWrapper.mServiceId))
+ );
+ updatedOffloadServiceInfoWrappers.add(newWrapper);
+ mCb.onOffloadStartOrUpdate(interfaceName, newWrapper.mOffloadServiceInfo);
+ }
+ mInterfaceOffloadServices.put(interfaceName, updatedOffloadServiceInfoWrappers);
}
}
- private static class OffloadServiceInfoWrapper {
- private final @NonNull OffloadServiceInfo mOffloadServiceInfo;
- private final int mServiceId;
+ /**
+ * The wrapper class for OffloadServiceInfo including the serviceId.
+ */
+ public static class OffloadServiceInfoWrapper {
+ public final @NonNull OffloadServiceInfo mOffloadServiceInfo;
+ public final int mServiceId;
OffloadServiceInfoWrapper(int serviceId, OffloadServiceInfo offloadServiceInfo) {
mOffloadServiceInfo = offloadServiceInfo;
@@ -404,6 +463,8 @@
private NsdServiceInfo mServiceInfo;
@Nullable
private final String mSubtype;
+ int mConflictDuringProbingCount;
+ int mConflictAfterProbingCount;
private Registration(@NonNull NsdServiceInfo serviceInfo, @Nullable String subtype) {
this.mOriginalName = serviceInfo.getServiceName();
@@ -515,6 +576,24 @@
@NonNull OffloadServiceInfo offloadServiceInfo);
}
+ /**
+ * Data class of avdverting metrics.
+ */
+ public static class AdvertiserMetrics {
+ public final int mRepliedRequestsCount;
+ public final int mSentPacketCount;
+ public final int mConflictDuringProbingCount;
+ public final int mConflictAfterProbingCount;
+
+ public AdvertiserMetrics(int repliedRequestsCount, int sentPacketCount,
+ int conflictDuringProbingCount, int conflictAfterProbingCount) {
+ mRepliedRequestsCount = repliedRequestsCount;
+ mSentPacketCount = sentPacketCount;
+ mConflictDuringProbingCount = conflictDuringProbingCount;
+ mConflictAfterProbingCount = conflictAfterProbingCount;
+ }
+ }
+
public MdnsAdvertiser(@NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
@NonNull AdvertiserCallback cb, @NonNull SharedLog sharedLog) {
this(looper, socketProvider, cb, new Dependencies(), sharedLog);
@@ -597,6 +676,34 @@
}
}
+ /**
+ * Get advertising metrics.
+ *
+ * @param id ID used when registering.
+ * @return The advertising metrics includes replied requests count, send packet count, conflict
+ * count during/after probing.
+ */
+ public AdvertiserMetrics getAdvertiserMetrics(int id) {
+ checkThread();
+ final Registration registration = mRegistrations.get(id);
+ if (registration == null) {
+ return new AdvertiserMetrics(
+ NO_PACKET /* repliedRequestsCount */,
+ NO_PACKET /* sentPacketCount */,
+ 0 /* conflictDuringProbingCount */,
+ 0 /* conflictAfterProbingCount */);
+ }
+ int repliedRequestsCount = NO_PACKET;
+ int sentPacketCount = NO_PACKET;
+ for (int i = 0; i < mAdvertiserRequests.size(); i++) {
+ repliedRequestsCount +=
+ mAdvertiserRequests.valueAt(i).getServiceRepliedRequestsCount(id);
+ sentPacketCount += mAdvertiserRequests.valueAt(i).getSentPacketCount(id);
+ }
+ return new AdvertiserMetrics(repliedRequestsCount, sentPacketCount,
+ registration.mConflictDuringProbingCount, registration.mConflictAfterProbingCount);
+ }
+
private static <K, V> boolean any(@NonNull ArrayMap<K, V> map,
@NonNull BiPredicate<K, V> predicate) {
for (int i = 0; i < map.size(); i++) {
@@ -615,9 +722,9 @@
}
private OffloadServiceInfoWrapper createOffloadService(int serviceId,
- @NonNull Registration registration) {
+ @NonNull Registration registration, byte[] rawOffloadPacket) {
final NsdServiceInfo nsdServiceInfo = registration.getServiceInfo();
- List<String> subTypes = new ArrayList<>();
+ final List<String> subTypes = new ArrayList<>();
String subType = registration.getSubtype();
if (subType != null) {
subTypes.add(subType);
@@ -627,7 +734,7 @@
nsdServiceInfo.getServiceType()),
subTypes,
String.join(".", mDeviceHostName),
- null /* rawOffloadPacket */,
+ rawOffloadPacket,
// TODO: define overlayable resources in
// ServiceConnectivityResources that set the priority based on
// service type.
@@ -636,5 +743,4 @@
OffloadEngine.OFFLOAD_TYPE_REPLY);
return new OffloadServiceInfoWrapper(serviceId, offloadServiceInfo);
}
-
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsConstants.java b/service-t/src/com/android/server/connectivity/mdns/MdnsConstants.java
index 0c32cf1..1251170 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsConstants.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsConstants.java
@@ -37,6 +37,7 @@
public static final int FLAG_TRUNCATED = 0x0200;
public static final int QCLASS_INTERNET = 0x0001;
public static final int QCLASS_UNICAST = 0x8000;
+ public static final int NO_PACKET = 0;
public static final String SUBTYPE_LABEL = "_sub";
public static final String SUBTYPE_PREFIX = "_";
private static final String MDNS_IPV4_HOST_ADDRESS = "224.0.0.251";
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
index a83b852..6454959 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -16,6 +16,8 @@
package com.android.server.connectivity.mdns;
+import static com.android.server.connectivity.mdns.MdnsConstants.NO_PACKET;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.LinkAddress;
@@ -28,6 +30,7 @@
import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.MdnsAnnouncer.BaseAnnouncementInfo;
import com.android.server.connectivity.mdns.MdnsPacketRepeater.PacketRepeaterCallback;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.IOException;
import java.net.InetSocketAddress;
@@ -92,8 +95,11 @@
/**
* Callbacks from {@link MdnsProber}.
*/
- private class ProbingCallback implements
- PacketRepeaterCallback<MdnsProber.ProbingInfo> {
+ private class ProbingCallback implements PacketRepeaterCallback<MdnsProber.ProbingInfo> {
+ @Override
+ public void onSent(int index, @NonNull MdnsProber.ProbingInfo info, int sentPacketCount) {
+ mRecordRepository.onProbingSent(info.getServiceId(), sentPacketCount);
+ }
@Override
public void onFinished(MdnsProber.ProbingInfo info) {
final MdnsAnnouncer.AnnouncementInfo announcementInfo;
@@ -117,8 +123,8 @@
*/
private class AnnouncingCallback implements PacketRepeaterCallback<BaseAnnouncementInfo> {
@Override
- public void onSent(int index, @NonNull BaseAnnouncementInfo info) {
- mRecordRepository.onAdvertisementSent(info.getServiceId());
+ public void onSent(int index, @NonNull BaseAnnouncementInfo info, int sentPacketCount) {
+ mRecordRepository.onAdvertisementSent(info.getServiceId(), sentPacketCount);
}
@Override
@@ -259,6 +265,22 @@
}
/**
+ * Get the replied request count from given service id.
+ */
+ public int getServiceRepliedRequestsCount(int id) {
+ if (!mRecordRepository.hasActiveService(id)) return NO_PACKET;
+ return mRecordRepository.getServiceRepliedRequestsCount(id);
+ }
+
+ /**
+ * Get the total sent packet count from given service id.
+ */
+ public int getSentPacketCount(int id) {
+ if (!mRecordRepository.hasActiveService(id)) return NO_PACKET;
+ return mRecordRepository.getSentPacketCount(id);
+ }
+
+ /**
* Update interface addresses used to advertise.
*
* This causes new address records to be announced.
@@ -351,7 +373,25 @@
mReplySender.queueReply(answers);
}
+ /**
+ * Get the socket interface name.
+ */
public String getSocketInterfaceName() {
return mSocket.getInterface().getName();
}
+
+ /**
+ * Gets the offload MdnsPacket.
+ * @param serviceId The serviceId.
+ * @return the raw offload payload
+ */
+ public byte[] getRawOffloadPayload(int serviceId) {
+ try {
+ return MdnsUtils.createRawDnsPacket(mReplySender.getPacketCreationBuffer(),
+ mRecordRepository.getOffloadPacket(serviceId));
+ } catch (IOException | IllegalArgumentException e) {
+ mSharedLog.wtf("Cannot create rawOffloadPacket: " + e.getMessage());
+ return new byte[0];
+ }
+ }
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
index 644560c..12ed139 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
@@ -59,7 +59,7 @@
/**
* Called when a packet was sent.
*/
- default void onSent(int index, @NonNull T info) {}
+ default void onSent(int index, @NonNull T info, int sentPacketCount) {}
/**
* Called when the {@link MdnsPacketRepeater} is done sending packets.
@@ -114,9 +114,10 @@
}
// Send to both v4 and v6 addresses; the reply sender will take care of ignoring the
// send when the socket has not joined the relevant group.
+ int sentPacketCount = 0;
for (InetSocketAddress destination : ALL_ADDRS) {
try {
- mReplySender.sendNow(packet, destination);
+ sentPacketCount += mReplySender.sendNow(packet, destination);
} catch (IOException e) {
mSharedLog.e("Error sending packet to " + destination, e);
}
@@ -135,7 +136,7 @@
// Call onSent after scheduling the next run, to allow the callback to cancel it
if (mCb != null) {
- mCb.onSent(index, request);
+ mCb.onSent(index, request, sentPacketCount);
}
}
}
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 1375279..1fb4d90 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -16,6 +16,8 @@
package com.android.server.connectivity.mdns;
+import static com.android.server.connectivity.mdns.MdnsConstants.NO_PACKET;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TargetApi;
@@ -175,13 +177,23 @@
public boolean exiting = false;
/**
+ * The replied query packet count of this service.
+ */
+ public int repliedServiceCount = NO_PACKET;
+
+ /**
+ * The sent packet count of this service (including announcements and probes).
+ */
+ public int sentPacketCount = NO_PACKET;
+
+ /**
* Create a ServiceRegistration for dns-sd service registration (RFC6763).
*
* @param deviceHostname Hostname of the device (for the interface used)
* @param serviceInfo Service to advertise
*/
ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo,
- @Nullable String subtype) {
+ @Nullable String subtype, int repliedServiceCount, int sentPacketCount) {
this.serviceInfo = serviceInfo;
this.subtype = subtype;
@@ -254,6 +266,8 @@
true /* sharedName */, true /* probing */));
this.allRecords = Collections.unmodifiableList(allRecords);
+ this.repliedServiceCount = repliedServiceCount;
+ this.sentPacketCount = sentPacketCount;
}
void setProbing(boolean probing) {
@@ -316,7 +330,8 @@
}
final ServiceRegistration registration = new ServiceRegistration(
- mDeviceHostname, serviceInfo, subtype);
+ mDeviceHostname, serviceInfo, subtype, NO_PACKET /* repliedServiceCount */,
+ NO_PACKET /* sentPacketCount */);
mServices.put(serviceId, registration);
// Remove existing exiting service
@@ -406,6 +421,24 @@
}
/**
+ * @return The replied request count of the service.
+ */
+ public int getServiceRepliedRequestsCount(int id) {
+ final ServiceRegistration service = mServices.get(id);
+ if (service == null) return NO_PACKET;
+ return service.repliedServiceCount;
+ }
+
+ /**
+ * @return The total sent packet count of the service.
+ */
+ public int getSentPacketCount(int id) {
+ final ServiceRegistration service = mServices.get(id);
+ if (service == null) return NO_PACKET;
+ return service.sentPacketCount;
+ }
+
+ /**
* Remove all services from the repository
* @return IDs of the removed services
*/
@@ -472,9 +505,12 @@
for (int i = 0; i < mServices.size(); i++) {
final ServiceRegistration registration = mServices.valueAt(i);
if (registration.exiting) continue;
- addReplyFromService(question, registration.allRecords, registration.ptrRecords,
+ if (addReplyFromService(question, registration.allRecords, registration.ptrRecords,
registration.srvRecord, registration.txtRecord, replyUnicast, now,
- answerInfo, additionalAnswerRecords);
+ answerInfo, additionalAnswerRecords)) {
+ registration.repliedServiceCount++;
+ registration.sentPacketCount++;
+ }
}
}
@@ -527,7 +563,7 @@
/**
* Add answers and additional answers for a question, from a ServiceRegistration.
*/
- private void addReplyFromService(@NonNull MdnsRecord question,
+ private boolean addReplyFromService(@NonNull MdnsRecord question,
@NonNull List<RecordInfo<?>> serviceRecords,
@Nullable List<RecordInfo<MdnsPointerRecord>> servicePtrRecords,
@Nullable RecordInfo<MdnsServiceRecord> serviceSrvRecord,
@@ -596,7 +632,7 @@
}
// No more records to add if no answer
- if (answerInfo.size() == answersStartIndex) return;
+ if (answerInfo.size() == answersStartIndex) return false;
final List<RecordInfo<?>> additionalAnswerInfo = new ArrayList<>();
// RFC6763 12.1: if including PTR record, include the SRV and TXT records it names
@@ -626,6 +662,7 @@
addNsecRecordsForUniqueNames(additionalAnswerRecords,
answerInfo.listIterator(answersStartIndex),
additionalAnswerInfo.listIterator());
+ return true;
}
/**
@@ -736,6 +773,38 @@
}
/**
+ * Gets the offload MdnsPacket.
+ * @param serviceId The serviceId.
+ * @return The offload {@link MdnsPacket} that contains PTR/SRV/TXT/A/AAAA records.
+ */
+ public MdnsPacket getOffloadPacket(int serviceId) throws IllegalArgumentException {
+ final ServiceRegistration registration = mServices.get(serviceId);
+ if (registration == null) throw new IllegalArgumentException(
+ "Service is not registered: " + serviceId);
+
+ final ArrayList<MdnsRecord> answers = new ArrayList<>();
+
+ // Adds all PTR, SRV, TXT, A/AAAA records.
+ for (RecordInfo<MdnsPointerRecord> ptrRecord : registration.ptrRecords) {
+ answers.add(ptrRecord.record);
+ }
+ answers.add(registration.srvRecord.record);
+ answers.add(registration.txtRecord.record);
+ for (RecordInfo<?> record : mGeneralRecords) {
+ if (record.record instanceof MdnsInetAddressRecord) {
+ answers.add(record.record);
+ }
+ }
+
+ final int flags = 0x8400; // Response, authoritative (rfc6762 18.4)
+ return new MdnsPacket(flags,
+ Collections.emptyList() /* questions */,
+ answers,
+ Collections.emptyList() /* authorityRecords */,
+ Collections.emptyList() /* additionalRecords */);
+ }
+
+ /**
* Get the service IDs of services conflicting with a received packet.
*/
public Set<Integer> getConflictingServices(MdnsPacket packet) {
@@ -830,8 +899,8 @@
final ServiceRegistration existing = mServices.get(serviceId);
if (existing == null) return null;
- final ServiceRegistration newService = new ServiceRegistration(
- mDeviceHostname, newInfo, existing.subtype);
+ final ServiceRegistration newService = new ServiceRegistration(mDeviceHostname, newInfo,
+ existing.subtype, existing.repliedServiceCount, existing.sentPacketCount);
mServices.put(serviceId, newService);
return makeProbingInfo(serviceId, newService.srvRecord.record);
}
@@ -839,7 +908,7 @@
/**
* Called when {@link MdnsAdvertiser} sent an advertisement for the given service.
*/
- public void onAdvertisementSent(int serviceId) {
+ public void onAdvertisementSent(int serviceId, int sentPacketCount) {
final ServiceRegistration registration = mServices.get(serviceId);
if (registration == null) return;
@@ -848,9 +917,20 @@
record.lastSentTimeMs = now;
record.lastAdvertisedTimeMs = now;
}
+ registration.sentPacketCount += sentPacketCount;
}
/**
+ * Called when {@link MdnsAdvertiser} sent a probing for the given service.
+ */
+ public void onProbingSent(int serviceId, int sentPacketCount) {
+ final ServiceRegistration registration = mServices.get(serviceId);
+ if (registration == null) return;
+ registration.sentPacketCount += sentPacketCount;
+ }
+
+
+ /**
* Compute:
* 2001:db8::1 --> 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.B.D.0.1.0.0.2.ip6.arpa
*
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
index 16c7d27..71057fb 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
@@ -25,6 +25,7 @@
import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.MdnsRecordRepository.ReplyInfo;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.IOException;
import java.net.DatagramPacket;
@@ -43,6 +44,9 @@
public class MdnsReplySender {
private static final boolean DBG = MdnsAdvertiser.DBG;
private static final int MSG_SEND = 1;
+ private static final int PACKET_NOT_SENT = 0;
+ private static final int PACKET_SENT = 1;
+
@NonNull
private final MdnsInterfaceSocket mSocket;
@NonNull
@@ -78,44 +82,22 @@
*
* Must be called on the looper thread used by the {@link MdnsReplySender}.
*/
- public void sendNow(@NonNull MdnsPacket packet, @NonNull InetSocketAddress destination)
+ public int sendNow(@NonNull MdnsPacket packet, @NonNull InetSocketAddress destination)
throws IOException {
ensureRunningOnHandlerThread(mHandler);
if (!((destination.getAddress() instanceof Inet6Address && mSocket.hasJoinedIpv6())
|| (destination.getAddress() instanceof Inet4Address && mSocket.hasJoinedIpv4()))) {
// Skip sending if the socket has not joined the v4/v6 group (there was no address)
- return;
+ return PACKET_NOT_SENT;
}
+ final byte[] outBuffer = MdnsUtils.createRawDnsPacket(mPacketCreationBuffer, packet);
+ mSocket.send(new DatagramPacket(outBuffer, 0, outBuffer.length, destination));
+ return PACKET_SENT;
+ }
- // TODO: support packets over size (send in multiple packets with TC bit set)
- final MdnsPacketWriter writer = new MdnsPacketWriter(mPacketCreationBuffer);
-
- writer.writeUInt16(0); // Transaction ID (advertisement: 0)
- writer.writeUInt16(packet.flags); // Response, authoritative (rfc6762 18.4)
- writer.writeUInt16(packet.questions.size()); // questions count
- writer.writeUInt16(packet.answers.size()); // answers count
- writer.writeUInt16(packet.authorityRecords.size()); // authority entries count
- writer.writeUInt16(packet.additionalRecords.size()); // additional records count
-
- for (MdnsRecord record : packet.questions) {
- // Questions do not have TTL or data
- record.writeHeaderFields(writer);
- }
- for (MdnsRecord record : packet.answers) {
- record.write(writer, 0L);
- }
- for (MdnsRecord record : packet.authorityRecords) {
- record.write(writer, 0L);
- }
- for (MdnsRecord record : packet.additionalRecords) {
- record.write(writer, 0L);
- }
-
- final int len = writer.getWritePosition();
- final byte[] outBuffer = new byte[len];
- System.arraycopy(mPacketCreationBuffer, 0, outBuffer, 0, len);
-
- mSocket.send(new DatagramPacket(outBuffer, 0, len, destination));
+ /** Get the packetCreationBuffer */
+ public byte[] getPacketCreationBuffer() {
+ return mPacketCreationBuffer;
}
/**
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 df3bde8..c1c9c42 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
@@ -24,8 +24,11 @@
import android.util.ArraySet;
import com.android.server.connectivity.mdns.MdnsConstants;
+import com.android.server.connectivity.mdns.MdnsPacket;
+import com.android.server.connectivity.mdns.MdnsPacketWriter;
import com.android.server.connectivity.mdns.MdnsRecord;
+import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
@@ -165,6 +168,41 @@
}
/**
+ * Create a raw DNS packet.
+ */
+ public static byte[] createRawDnsPacket(@NonNull byte[] packetCreationBuffer,
+ @NonNull MdnsPacket packet) throws IOException {
+ // TODO: support packets over size (send in multiple packets with TC bit set)
+ final MdnsPacketWriter writer = new MdnsPacketWriter(packetCreationBuffer);
+
+ writer.writeUInt16(0); // Transaction ID (advertisement: 0)
+ writer.writeUInt16(packet.flags); // Response, authoritative (rfc6762 18.4)
+ writer.writeUInt16(packet.questions.size()); // questions count
+ writer.writeUInt16(packet.answers.size()); // answers count
+ writer.writeUInt16(packet.authorityRecords.size()); // authority entries count
+ writer.writeUInt16(packet.additionalRecords.size()); // additional records count
+
+ for (MdnsRecord record : packet.questions) {
+ // Questions do not have TTL or data
+ record.writeHeaderFields(writer);
+ }
+ for (MdnsRecord record : packet.answers) {
+ record.write(writer, 0L);
+ }
+ for (MdnsRecord record : packet.authorityRecords) {
+ record.write(writer, 0L);
+ }
+ for (MdnsRecord record : packet.additionalRecords) {
+ record.write(writer, 0L);
+ }
+
+ final int len = writer.getWritePosition();
+ final byte[] outBuffer = new byte[len];
+ System.arraycopy(packetCreationBuffer, 0, outBuffer, 0, len);
+ return outBuffer;
+ }
+
+ /**
* Checks if the MdnsRecord needs to be renewed or not.
*
* <p>As per RFC6762 7.1 no need to query if remaining TTL is more than half the original one,
diff --git a/service/Android.bp b/service/Android.bp
index 9ae3d6c..8e59e86 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -19,6 +19,12 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+service_remoteauth_pre_jarjar_lib = "service-remoteauth-pre-jarjar"
+
+// The above variables may have different values
+// depending on the branch, and this comment helps
+// separate them from the rest of the file to avoid merge conflicts
+
aidl_interface {
name: "connectivity_native_aidl_interface",
local_include_dir: "binder",
@@ -236,7 +242,7 @@
"service-connectivity-pre-jarjar",
"service-connectivity-tiramisu-pre-jarjar",
"service-nearby-pre-jarjar",
- "service-remoteauth-pre-jarjar",
+ service_remoteauth_pre_jarjar_lib,
"service-thread-pre-jarjar",
],
// The below libraries are not actually needed to build since no source is compiled
@@ -361,7 +367,7 @@
java_genrule {
name: "service-remoteauth-jarjar-gen",
tool_files: [
- ":service-remoteauth-pre-jarjar{.jar}",
+ ":" + service_remoteauth_pre_jarjar_lib + "{.jar}",
"jarjar-excludes.txt",
],
tools: [
@@ -369,7 +375,7 @@
],
out: ["service_remoteauth_jarjar_rules.txt"],
cmd: "$(location jarjar-rules-generator) " +
- "$(location :service-remoteauth-pre-jarjar{.jar}) " +
+ "$(location :" + service_remoteauth_pre_jarjar_lib + "{.jar}) " +
"--prefix com.android.server.remoteauth " +
"--excludes $(location jarjar-excludes.txt) " +
"--output $(out)",
diff --git a/service/jni/com_android_server_BpfNetMaps.cpp b/service/jni/com_android_server_BpfNetMaps.cpp
index 9ced44e..50a0635 100644
--- a/service/jni/com_android_server_BpfNetMaps.cpp
+++ b/service/jni/com_android_server_BpfNetMaps.cpp
@@ -187,15 +187,6 @@
mTc.setPermissionForUids(permission, data);
}
-static void native_dump(JNIEnv* env, jobject self, jobject javaFd, jboolean verbose) {
- int fd = netjniutils::GetNativeFileDescriptor(env, javaFd);
- if (fd < 0) {
- jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
- return;
- }
- mTc.dump(fd, verbose);
-}
-
static jint native_synchronizeKernelRCU(JNIEnv* env, jobject self) {
return -bpf::synchronizeKernelRCU();
}
@@ -232,8 +223,6 @@
(void*)native_swapActiveStatsMap},
{"native_setPermissionForUids", "(I[I)V",
(void*)native_setPermissionForUids},
- {"native_dump", "(Ljava/io/FileDescriptor;Z)V",
- (void*)native_dump},
{"native_synchronizeKernelRCU", "()I",
(void*)native_synchronizeKernelRCU},
};
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 3828389..543bdc8 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -576,13 +576,5 @@
}
}
-void TrafficController::dump(int fd, bool verbose __unused) {
- std::lock_guard guard(mMutex);
- DumpWriter dw(fd);
-
- ScopedIndent indentTop(dw);
- dw.println("TrafficController");
-}
-
} // namespace net
} // namespace android
diff --git a/service/native/include/TrafficController.h b/service/native/include/TrafficController.h
index d610d25..86cf50a 100644
--- a/service/native/include/TrafficController.h
+++ b/service/native/include/TrafficController.h
@@ -22,7 +22,6 @@
#include "android-base/thread_annotations.h"
#include "bpf/BpfMap.h"
#include "netd.h"
-#include "netdutils/DumpWriter.h"
#include "netdutils/NetlinkListener.h"
#include "netdutils/StatusOr.h"
@@ -55,8 +54,6 @@
netdutils::Status updateOwnerMapEntry(UidOwnerMatchType match, uid_t uid, FirewallRule rule,
FirewallType type) EXCLUDES(mMutex);
- void dump(int fd, bool verbose) EXCLUDES(mMutex);
-
netdutils::Status replaceRulesInMap(UidOwnerMatchType match, const std::vector<int32_t>& uids)
EXCLUDES(mMutex);
diff --git a/service/src/com/android/metrics/stats.proto b/service/src/com/android/metrics/stats.proto
index 99afb90..ecc0377 100644
--- a/service/src/com/android/metrics/stats.proto
+++ b/service/src/com/android/metrics/stats.proto
@@ -64,6 +64,18 @@
// Record sent query count before stopped discovery
optional int32 sent_query_count = 12;
+
+ // Record sent packet count before unregistered service
+ optional int32 sent_packet_count = 13;
+
+ // Record number of conflict during probing
+ optional int32 conflict_during_probing_count = 14;
+
+ // Record number of conflict after probing
+ optional int32 conflict_after_probing_count = 15;
+
+ // The random number between 0 ~ 999 for sampling
+ optional int32 random_number = 16;
}
/**
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 62520dc..4b24aaf 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -295,13 +295,6 @@
return ConnectivityStatsLog.buildStatsEvent(NETWORK_BPF_MAP_INFO, cookieTagMapSize,
uidOwnerMapSize, uidPermissionMapSize);
}
-
- /**
- * Call native_dump
- */
- public void nativeDump(final FileDescriptor fd, final boolean verbose) {
- native_dump(fd, verbose);
- }
}
/** Constructor used after T that doesn't need to use netd anymore. */
@@ -1030,7 +1023,8 @@
EOPNOTSUPP, "dumpsys connectivity trafficcontroller dump not available on pre-T"
+ " devices, use dumpsys netd trafficcontroller instead.");
}
- mDeps.nativeDump(fd, verbose);
+
+ pw.println("TrafficController"); // required by CTS testDumpBpfNetMaps
pw.println();
pw.println("sEnableJavaBpfMap: " + sEnableJavaBpfMap);
@@ -1106,8 +1100,5 @@
private native void native_setPermissionForUids(int permissions, int[] uids);
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
- private static native void native_dump(FileDescriptor fd, boolean verbose);
-
- @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static native int native_synchronizeKernelRCU();
}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 04d0b93..60523dd 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -934,6 +934,15 @@
private final Map<String, ApplicationSelfCertifiedNetworkCapabilities>
mSelfCertifiedCapabilityCache = new HashMap<>();
+ // Flag to enable the feature of closing frozen app sockets.
+ private final boolean mDestroyFrozenSockets;
+
+ // Flag to optimize closing frozen app sockets by waiting for the cellular modem to wake up.
+ private final boolean mDelayDestroyFrozenSockets;
+
+ // Uids that ConnectivityService is pending to close sockets of.
+ private final Set<Integer> mPendingFrozenUids = new ArraySet<>();
+
/**
* Implements support for the legacy "one network per network type" model.
*
@@ -1772,8 +1781,11 @@
mCdmps = null;
}
- if (mDeps.isAtLeastU()
- && mDeps.isFeatureEnabled(context, KEY_DESTROY_FROZEN_SOCKETS_VERSION)) {
+ mDestroyFrozenSockets = mDeps.isAtLeastU()
+ && mDeps.isFeatureEnabled(context, KEY_DESTROY_FROZEN_SOCKETS_VERSION);
+ mDelayDestroyFrozenSockets = mDeps.isAtLeastU()
+ && mDeps.isFeatureEnabled(context, DELAY_DESTROY_FROZEN_SOCKETS_VERSION);
+ if (mDestroyFrozenSockets) {
final UidFrozenStateChangedCallback frozenStateChangedCallback =
new UidFrozenStateChangedCallback() {
@Override
@@ -2983,26 +2995,109 @@
}
}
+ /**
+ * Check if the cell network is idle.
+ * @return true if the cell network state is idle
+ * false if the cell network state is active or unknown
+ */
+ private boolean isCellNetworkIdle() {
+ final NetworkAgentInfo defaultNai = getDefaultNetwork();
+ if (defaultNai == null
+ || !defaultNai.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+ // mNetworkActivityTracker only tracks the activity of the default network. So if the
+ // cell network is not the default network, cell network state is unknown.
+ // TODO(b/279380356): Track cell network state when the cell is not the default network
+ return false;
+ }
+
+ return !mNetworkActivityTracker.isDefaultNetworkActive();
+ }
+
private void handleFrozenUids(int[] uids, int[] frozenStates) {
final ArraySet<Integer> ownerUids = new ArraySet<>();
for (int i = 0; i < uids.length; i++) {
if (frozenStates[i] == UID_FROZEN_STATE_FROZEN) {
ownerUids.add(uids[i]);
+ } else {
+ mPendingFrozenUids.remove(uids[i]);
}
}
- if (!ownerUids.isEmpty()) {
+ if (ownerUids.isEmpty()) {
+ return;
+ }
+
+ if (mDelayDestroyFrozenSockets && isCellNetworkIdle()) {
+ // Delay closing sockets to avoid waking the cell modem up.
+ // Wi-Fi network state is not considered since waking Wi-Fi modem up is much cheaper
+ // than waking cell modem up.
+ mPendingFrozenUids.addAll(ownerUids);
+ } else {
try {
mDeps.destroyLiveTcpSocketsByOwnerUids(ownerUids);
- } catch (Exception e) {
+ } catch (SocketException | InterruptedIOException | ErrnoException e) {
loge("Exception in socket destroy: " + e);
}
}
}
+ private void closePendingFrozenSockets() {
+ ensureRunningOnConnectivityServiceThread();
+
+ try {
+ mDeps.destroyLiveTcpSocketsByOwnerUids(mPendingFrozenUids);
+ } catch (SocketException | InterruptedIOException | ErrnoException e) {
+ loge("Failed to close pending frozen app sockets: " + e);
+ }
+ mPendingFrozenUids.clear();
+ }
+
+ private void handleReportNetworkActivity(final NetworkActivityParams params) {
+ mNetworkActivityTracker.handleReportNetworkActivity(params);
+
+ if (mDelayDestroyFrozenSockets
+ && params.isActive
+ && params.label == TRANSPORT_CELLULAR
+ && !mPendingFrozenUids.isEmpty()) {
+ closePendingFrozenSockets();
+ }
+ }
+
+ /**
+ * If the cellular network is no longer the default network, close pending frozen sockets.
+ *
+ * @param newNetwork new default network
+ * @param oldNetwork old default network
+ */
+ private void maybeClosePendingFrozenSockets(NetworkAgentInfo newNetwork,
+ NetworkAgentInfo oldNetwork) {
+ final boolean isOldNetworkCellular = oldNetwork != null
+ && oldNetwork.networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
+ final boolean isNewNetworkCellular = newNetwork != null
+ && newNetwork.networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
+
+ if (isOldNetworkCellular
+ && !isNewNetworkCellular
+ && !mPendingFrozenUids.isEmpty()) {
+ closePendingFrozenSockets();
+ }
+ }
+
+ private void dumpCloseFrozenAppSockets(IndentingPrintWriter pw) {
+ pw.println("CloseFrozenAppSockets:");
+ pw.increaseIndent();
+ pw.print("mDestroyFrozenSockets="); pw.println(mDestroyFrozenSockets);
+ pw.print("mDelayDestroyFrozenSockets="); pw.println(mDelayDestroyFrozenSockets);
+ pw.print("mPendingFrozenUids="); pw.println(mPendingFrozenUids);
+ pw.decreaseIndent();
+ }
+
@VisibleForTesting
static final String KEY_DESTROY_FROZEN_SOCKETS_VERSION = "destroy_frozen_sockets_version";
+ @VisibleForTesting
+ static final String DELAY_DESTROY_FROZEN_SOCKETS_VERSION =
+ "delay_destroy_frozen_sockets_version";
private void enforceInternetPermission() {
mContext.enforceCallingOrSelfPermission(
@@ -3605,6 +3700,9 @@
dumpAvoidBadWifiSettings(pw);
pw.println();
+ dumpCloseFrozenAppSockets(pw);
+
+ pw.println();
if (!CollectionUtils.contains(args, SHORT_ARG)) {
pw.println();
@@ -4671,6 +4769,7 @@
// incorrect) behavior.
mNetworkActivityTracker.updateDataActivityTracking(
null /* newNetwork */, nai);
+ maybeClosePendingFrozenSockets(null /* newNetwork */, nai);
ensureNetworkTransitionWakelock(nai.toShortString());
}
}
@@ -5877,7 +5976,7 @@
}
case EVENT_REPORT_NETWORK_ACTIVITY:
final NetworkActivityParams arg = (NetworkActivityParams) msg.obj;
- mNetworkActivityTracker.handleReportNetworkActivity(arg);
+ handleReportNetworkActivity(arg);
break;
case EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED:
handleMobileDataPreferredUidsChanged();
@@ -9117,6 +9216,7 @@
mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork);
}
mNetworkActivityTracker.updateDataActivityTracking(newDefaultNetwork, oldDefaultNetwork);
+ maybeClosePendingFrozenSockets(newDefaultNetwork, oldDefaultNetwork);
mProxyTracker.setDefaultProxy(null != newDefaultNetwork
? newDefaultNetwork.linkProperties.getHttpProxy() : null);
resetHttpProxyForNonDefaultNetwork(oldDefaultNetwork);
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
index b89ab1f..3358fd7 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -48,7 +48,7 @@
@BeforeClassWithInfo
public static void setUpOnceBase(TestInformation testInfo) throws Exception {
DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(testInfo.getDevice());
- String testApk = deviceSdkLevel.isDeviceAtLeastT() ? TEST_APK_NEXT : TEST_APK;
+ String testApk = deviceSdkLevel.isDeviceAtLeastV() ? TEST_APK_NEXT : TEST_APK;
uninstallPackage(testInfo, TEST_PKG, false);
installPackage(testInfo, testApk);
diff --git a/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java b/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
index ff06a90..aa90f5f 100644
--- a/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
+++ b/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
@@ -16,24 +16,33 @@
package android.security.cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.modules.utils.build.testing.DeviceSdkLevel;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.IBuildReceiver;
import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-import java.lang.Integer;
-import java.lang.String;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.ArrayList;
/**
* Host-side tests for values in /proc/net.
*
* These tests analyze /proc/net to verify that certain networking properties are correct.
*/
-public class ProcNetTest extends DeviceTestCase implements IBuildReceiver, IDeviceTest {
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class ProcNetTest extends BaseHostJUnit4Test implements IBuildReceiver, IDeviceTest {
private static final String SPI_TIMEOUT_SYSCTL = "/proc/sys/net/core/xfrm_acq_expires";
private static final int MIN_ACQ_EXPIRES = 3600;
// Global sysctls. Must be present and set to 1.
@@ -72,13 +81,12 @@
*/
@Override
public void setDevice(ITestDevice device) {
- super.setDevice(device);
mDevice = device;
}
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ /** Run before each test case. */
+ @Before
+ public void setUp() throws Exception {
mSysctlDirs = getSysctlDirs();
}
@@ -113,6 +121,7 @@
/**
* Checks that SPI default timeouts are overridden, and set to a reasonable length of time
*/
+ @Test
public void testMinAcqExpires() throws Exception {
int value = readIntFromPath(SPI_TIMEOUT_SYSCTL);
assertAtLeast(SPI_TIMEOUT_SYSCTL, value, MIN_ACQ_EXPIRES);
@@ -122,6 +131,7 @@
* Checks that the sysctls for multinetwork kernel features are present and
* enabled.
*/
+ @Test
public void testProcSysctls() throws Exception {
for (String sysctl : GLOBAL_SYSCTLS) {
int value = readIntFromPath(sysctl);
@@ -138,6 +148,7 @@
/**
* Verify that accept_ra_rt_info_{min,max}_plen exists and is set to the expected value
*/
+ @Test
public void testAcceptRaRtInfoMinMaxPlen() throws Exception {
for (String interfaceDir : mSysctlDirs) {
String path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "accept_ra_rt_info_min_plen";
@@ -153,6 +164,7 @@
* Verify that router_solicitations exists and is set to the expected value
* and verify that router_solicitation_max_interval exists and is in an acceptable interval.
*/
+ @Test
public void testRouterSolicitations() throws Exception {
for (String interfaceDir : mSysctlDirs) {
String path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "router_solicitations";
@@ -172,7 +184,11 @@
* (This repeats the VTS test, and is here for good performance of the internet as a whole.)
* TODO: revisit this once a better CC algorithm like BBR2 is available.
*/
+ @Test
public void testCongestionControl() throws Exception {
+ final DeviceSdkLevel dsl = new DeviceSdkLevel(mDevice);
+ assumeTrue(dsl.isDeviceAtLeastV());
+
String path = "/proc/sys/net/ipv4/tcp_congestion_control";
String value = mDevice.executeAdbCommand("shell", "cat", path).trim();
assertEquals(value, "cubic");
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 49a6ef1..17a135a 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -163,6 +163,7 @@
private val cm by lazy { context.getSystemService(ConnectivityManager::class.java)!! }
private val serviceName = "NsdTest%09d".format(Random().nextInt(1_000_000_000))
+ private val serviceName2 = "NsdTest%09d".format(Random().nextInt(1_000_000_000))
private val serviceType = "_nmt%09d._tcp".format(Random().nextInt(1_000_000_000))
private val handlerThread = HandlerThread(NsdManagerTest::class.java.simpleName)
private val ctsNetUtils by lazy{ CtsNetUtils(context) }
@@ -890,14 +891,48 @@
}
}
- fun checkOffloadServiceInfo(serviceInfo: OffloadServiceInfo) {
- assertEquals(serviceName, serviceInfo.key.serviceName)
- assertEquals(serviceType, serviceInfo.key.serviceType)
- assertEquals(listOf<String>("_subtype"), serviceInfo.subtypes)
+ fun checkOffloadServiceInfo(serviceInfo: OffloadServiceInfo, si: NsdServiceInfo) {
+ val expectedServiceType = si.serviceType.split(",")[0]
+ assertEquals(si.serviceName, serviceInfo.key.serviceName)
+ assertEquals(expectedServiceType, serviceInfo.key.serviceType)
+ assertEquals(listOf("_subtype"), serviceInfo.subtypes)
assertTrue(serviceInfo.hostname.startsWith("Android_"))
assertTrue(serviceInfo.hostname.endsWith("local"))
assertEquals(0, serviceInfo.priority)
assertEquals(OffloadEngine.OFFLOAD_TYPE_REPLY.toLong(), serviceInfo.offloadType)
+ val offloadPayload = serviceInfo.offloadPayload
+ assertNotNull(offloadPayload)
+ val dnsPacket = TestDnsPacket(offloadPayload)
+ assertEquals(0x8400, dnsPacket.header.flags)
+ assertEquals(0, dnsPacket.records[DnsPacket.QDSECTION].size)
+ assertTrue(dnsPacket.records[DnsPacket.ANSECTION].size >= 5)
+ assertEquals(0, dnsPacket.records[DnsPacket.NSSECTION].size)
+ assertEquals(0, dnsPacket.records[DnsPacket.ARSECTION].size)
+
+ val ptrRecord = dnsPacket.records[DnsPacket.ANSECTION][0]
+ assertEquals("$expectedServiceType.local", ptrRecord.dName)
+ assertEquals(0x0C /* PTR */, ptrRecord.nsType)
+ val ptrSubRecord = dnsPacket.records[DnsPacket.ANSECTION][1]
+ assertEquals("_subtype._sub.$expectedServiceType.local", ptrSubRecord.dName)
+ assertEquals(0x0C /* PTR */, ptrSubRecord.nsType)
+ val srvRecord = dnsPacket.records[DnsPacket.ANSECTION][2]
+ assertEquals("${si.serviceName}.$expectedServiceType.local", srvRecord.dName)
+ assertEquals(0x21 /* SRV */, srvRecord.nsType)
+ val txtRecord = dnsPacket.records[DnsPacket.ANSECTION][3]
+ assertEquals("${si.serviceName}.$expectedServiceType.local", txtRecord.dName)
+ assertEquals(0x10 /* TXT */, txtRecord.nsType)
+ val iface = NetworkInterface.getByName(testNetwork1.iface.interfaceName)
+ val allAddress = iface.inetAddresses.toList()
+ for (i in 4 until dnsPacket.records[DnsPacket.ANSECTION].size) {
+ val addressRecord = dnsPacket.records[DnsPacket.ANSECTION][i]
+ assertTrue(addressRecord.dName.startsWith("Android_"))
+ assertTrue(addressRecord.dName.endsWith("local"))
+ assertTrue(addressRecord.nsType in arrayOf(0x1C /* AAAA */, 0x01 /* A */))
+ val rData = addressRecord.rr
+ assertNotNull(rData)
+ val addr = InetAddress.getByAddress(rData)
+ assertTrue(addr in allAddress)
+ }
}
@Test
@@ -907,36 +942,63 @@
// The offload callbacks are only supported with the new backend,
// enabled with target SDK U+.
assumeTrue(isAtLeastU() || targetSdkVersion > Build.VERSION_CODES.TIRAMISU)
+
+ // TODO: also have a test that use an executor that runs in a different thread, and pass
+ // in the thread ID NsdServiceInfo to check it
+ val si1 = NsdServiceInfo()
+ si1.serviceType = "$serviceType,_subtype"
+ si1.serviceName = serviceName
+ si1.network = testNetwork1.network
+ si1.port = 23456
+ val record1 = NsdRegistrationRecord()
+
+ val si2 = NsdServiceInfo()
+ si2.serviceType = "$serviceType,_subtype"
+ si2.serviceName = serviceName2
+ si2.network = testNetwork1.network
+ si2.port = 12345
+ val record2 = NsdRegistrationRecord()
val offloadEngine = TestNsdOffloadEngine()
- runAsShell(NETWORK_SETTINGS) {
- nsdManager.registerOffloadEngine(testNetwork1.iface.interfaceName,
- OffloadEngine.OFFLOAD_TYPE_REPLY.toLong(),
- OffloadEngine.OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK.toLong(),
- { it.run() }, offloadEngine)
- }
- val si = NsdServiceInfo()
- si.serviceType = "$serviceType,_subtype"
- si.serviceName = serviceName
- si.network = testNetwork1.network
- si.port = 12345
- val record = NsdRegistrationRecord()
- nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, record)
- val addOrUpdateEvent = offloadEngine
- .expectCallbackEventually<TestNsdOffloadEngine.OffloadEvent.AddOrUpdateEvent> {
- it.info.key.serviceName == serviceName
+ tryTest {
+ // Register service before the OffloadEngine is registered.
+ nsdManager.registerService(si1, NsdManager.PROTOCOL_DNS_SD, record1)
+ record1.expectCallback<ServiceRegistered>()
+ runAsShell(NETWORK_SETTINGS) {
+ nsdManager.registerOffloadEngine(testNetwork1.iface.interfaceName,
+ OffloadEngine.OFFLOAD_TYPE_REPLY.toLong(),
+ OffloadEngine.OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK.toLong(),
+ { it.run() }, offloadEngine)
}
- checkOffloadServiceInfo(addOrUpdateEvent.info)
+ val addOrUpdateEvent1 = offloadEngine
+ .expectCallbackEventually<TestNsdOffloadEngine.OffloadEvent.AddOrUpdateEvent> {
+ it.info.key.serviceName == si1.serviceName
+ }
+ checkOffloadServiceInfo(addOrUpdateEvent1.info, si1)
- nsdManager.unregisterService(record)
- val unregisterEvent = offloadEngine
- .expectCallbackEventually<TestNsdOffloadEngine.OffloadEvent.RemoveEvent> {
- it.info.key.serviceName == serviceName
+ // Register service after OffloadEngine is registered.
+ nsdManager.registerService(si2, NsdManager.PROTOCOL_DNS_SD, record2)
+ record2.expectCallback<ServiceRegistered>()
+ val addOrUpdateEvent2 = offloadEngine
+ .expectCallbackEventually<TestNsdOffloadEngine.OffloadEvent.AddOrUpdateEvent> {
+ it.info.key.serviceName == si2.serviceName
+ }
+ checkOffloadServiceInfo(addOrUpdateEvent2.info, si2)
+
+ nsdManager.unregisterService(record2)
+ record2.expectCallback<ServiceUnregistered>()
+ val unregisterEvent = offloadEngine
+ .expectCallbackEventually<TestNsdOffloadEngine.OffloadEvent.RemoveEvent> {
+ it.info.key.serviceName == si2.serviceName
+ }
+ checkOffloadServiceInfo(unregisterEvent.info, si2)
+ } cleanupStep {
+ runAsShell(NETWORK_SETTINGS) {
+ nsdManager.unregisterOffloadEngine(offloadEngine)
}
- checkOffloadServiceInfo(unregisterEvent.info)
-
- runAsShell(NETWORK_SETTINGS) {
- nsdManager.unregisterOffloadEngine(offloadEngine)
+ } cleanup {
+ nsdManager.unregisterService(record1)
+ record1.expectCallback<ServiceUnregistered>()
}
}
@@ -1381,6 +1443,11 @@
): ByteArray? = pollForMdnsPacket(timeoutMs) { it.isReplyFor("$serviceName.$serviceType.local") }
private class TestDnsPacket(data: ByteArray) : DnsPacket(data) {
+ val header: DnsHeader
+ get() = mHeader
+ val records: Array<List<DnsRecord>>
+ get() = mRecords
+
fun isProbeFor(name: String): Boolean = mRecords[QDSECTION].any {
it.dName == name && it.nsType == 0xff /* ANY */
}
diff --git a/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt b/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
index 7f893df..a82e29b 100644
--- a/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
+++ b/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
@@ -21,12 +21,15 @@
import android.stats.connectivity.NsdEventType
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
+import java.util.Random
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
@@ -34,6 +37,12 @@
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
class NetworkNsdReportedMetricsTest {
private val deps = mock(NetworkNsdReportedMetrics.Dependencies::class.java)
+ private val random = mock(Random::class.java)
+
+ @Before
+ fun setUp() {
+ doReturn(random).`when`(deps).makeRandomGenerator()
+ }
@Test
fun testReportServiceRegistrationSucceeded() {
@@ -80,8 +89,13 @@
val clientId = 99
val transactionId = 100
val durationMs = 10L
+ val repliedRequestsCount = 25
+ val sentPacketCount = 50
+ val conflictDuringProbingCount = 2
+ val conflictAfterProbingCount = 1
val metrics = NetworkNsdReportedMetrics(true /* isLegacy */, clientId, deps)
- metrics.reportServiceUnregistration(transactionId, durationMs)
+ metrics.reportServiceUnregistration(transactionId, durationMs, repliedRequestsCount,
+ sentPacketCount, conflictDuringProbingCount, conflictAfterProbingCount)
val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
verify(deps).statsWrite(eventCaptor.capture())
@@ -92,6 +106,10 @@
assertEquals(NsdEventType.NET_REGISTER, it.type)
assertEquals(MdnsQueryResult.MQR_SERVICE_UNREGISTERED, it.queryResult)
assertEquals(durationMs, it.eventDurationMillisec)
+ assertEquals(repliedRequestsCount, it.repliedRequestsCount)
+ assertEquals(sentPacketCount, it.sentPacketCount)
+ assertEquals(conflictDuringProbingCount, it.conflictDuringProbingCount)
+ assertEquals(conflictAfterProbingCount, it.conflictAfterProbingCount)
}
}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index f8e3166..e5dec56 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -154,6 +154,7 @@
import static android.os.Process.INVALID_UID;
import static android.system.OsConstants.IPPROTO_TCP;
+import static com.android.server.ConnectivityService.DELAY_DESTROY_FROZEN_SOCKETS_VERSION;
import static com.android.server.ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION;
import static com.android.server.ConnectivityService.MAX_NETWORK_REQUESTS_PER_SYSTEM_UID;
import static com.android.server.ConnectivityService.PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED;
@@ -214,6 +215,7 @@
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -2130,6 +2132,8 @@
return true;
case KEY_DESTROY_FROZEN_SOCKETS_VERSION:
return true;
+ case DELAY_DESTROY_FROZEN_SOCKETS_VERSION:
+ return true;
default:
return super.isFeatureEnabled(context, name);
}
@@ -2280,7 +2284,9 @@
@Override @SuppressWarnings("DirectInvocationOnMock")
public void destroyLiveTcpSocketsByOwnerUids(final Set<Integer> ownerUids) {
// Call mocked destroyLiveTcpSocketsByOwnerUids so that test can verify this method call
- mDestroySocketsWrapper.destroyLiveTcpSocketsByOwnerUids(ownerUids);
+ // Create copy of ownerUids so that tests can verify the correct value even if the
+ // ConnectivityService update the ownerUids after this method call.
+ mDestroySocketsWrapper.destroyLiveTcpSocketsByOwnerUids(new ArraySet<>(ownerUids));
}
final ArrayTrackRecord<Pair<Integer, Long>>.ReadHead mScheduledEvaluationTimeouts =
@@ -11290,6 +11296,9 @@
}
private void doTestInterfaceClassActivityChanged(final int transportType) throws Exception {
+ final BaseNetdUnsolicitedEventListener netdUnsolicitedEventListener =
+ getRegisteredNetdUnsolicitedEventListener();
+
final int legacyType = transportToLegacyType(transportType);
final LinkProperties lp = new LinkProperties();
lp.setInterfaceName(transportToTestIfaceName(transportType));
@@ -11306,12 +11315,8 @@
mCm.addDefaultNetworkActiveListener(listener);
- ArgumentCaptor<BaseNetdUnsolicitedEventListener> netdCallbackCaptor =
- ArgumentCaptor.forClass(BaseNetdUnsolicitedEventListener.class);
- verify(mMockNetd).registerUnsolicitedEventListener(netdCallbackCaptor.capture());
-
// Interface goes to inactive state
- netdCallbackCaptor.getValue().onInterfaceClassActivityChanged(false /* isActive */,
+ netdUnsolicitedEventListener.onInterfaceClassActivityChanged(false /* isActive */,
transportType, TIMESTAMP, NETWORK_ACTIVITY_NO_UID);
mServiceContext.expectDataActivityBroadcast(legacyType, false /* isActive */,
TIMESTAMP);
@@ -11319,7 +11324,7 @@
assertFalse(mCm.isDefaultNetworkActive());
// Interface goes to active state
- netdCallbackCaptor.getValue().onInterfaceClassActivityChanged(true /* isActive */,
+ netdUnsolicitedEventListener.onInterfaceClassActivityChanged(true /* isActive */,
transportType, TIMESTAMP, TEST_PACKAGE_UID);
mServiceContext.expectDataActivityBroadcast(legacyType, true /* isActive */, TIMESTAMP);
assertTrue(onNetworkActiveCv.block(TEST_CALLBACK_TIMEOUT_MS));
@@ -18566,6 +18571,27 @@
anyInt());
}
+ // UidFrozenStateChangedCallback is added in U API.
+ // Returning UidFrozenStateChangedCallback directly makes the test fail on T- devices since
+ // AndroidJUnit4ClassRunner iterates all declared methods and tries to resolve the return type.
+ // Solve this by wrapping it in an AtomicReference. Because of erasure, this removes the
+ // resolving problem as the type isn't seen dynamically.
+ private AtomicReference<UidFrozenStateChangedCallback> getUidFrozenStateChangedCallback() {
+ ArgumentCaptor<UidFrozenStateChangedCallback> activityManagerCallbackCaptor =
+ ArgumentCaptor.forClass(UidFrozenStateChangedCallback.class);
+ verify(mActivityManager).registerUidFrozenStateChangedCallback(any(),
+ activityManagerCallbackCaptor.capture());
+ return new AtomicReference<>(activityManagerCallbackCaptor.getValue());
+ }
+
+ private BaseNetdUnsolicitedEventListener getRegisteredNetdUnsolicitedEventListener()
+ throws RemoteException {
+ ArgumentCaptor<BaseNetdUnsolicitedEventListener> netdCallbackCaptor =
+ ArgumentCaptor.forClass(BaseNetdUnsolicitedEventListener.class);
+ verify(mMockNetd).registerUnsolicitedEventListener(netdCallbackCaptor.capture());
+ return netdCallbackCaptor.getValue();
+ }
+
private static final int TEST_FROZEN_UID = 1000;
private static final int TEST_UNFROZEN_UID = 2000;
@@ -18576,22 +18602,177 @@
@Test
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public void testFrozenUidSocketDestroy() throws Exception {
- ArgumentCaptor<UidFrozenStateChangedCallback> callbackArg =
- ArgumentCaptor.forClass(UidFrozenStateChangedCallback.class);
-
- verify(mActivityManager).registerUidFrozenStateChangedCallback(any(),
- callbackArg.capture());
+ final UidFrozenStateChangedCallback callback =
+ getUidFrozenStateChangedCallback().get();
final int[] uids = {TEST_FROZEN_UID, TEST_UNFROZEN_UID};
final int[] frozenStates = {UID_FROZEN_STATE_FROZEN, UID_FROZEN_STATE_UNFROZEN};
- callbackArg.getValue().onUidFrozenStateChanged(uids, frozenStates);
+ callback.onUidFrozenStateChanged(uids, frozenStates);
waitForIdle();
verify(mDestroySocketsWrapper).destroyLiveTcpSocketsByOwnerUids(Set.of(TEST_FROZEN_UID));
}
+ private void doTestDelayFrozenUidSocketDestroy(int transportType,
+ boolean freezeWithNetworkInactive, boolean expectDelay) throws Exception {
+ final TestNetworkCallback defaultCallback = new TestNetworkCallback();
+ final LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName(transportToTestIfaceName(transportType));
+ final TestNetworkAgentWrapper agent = new TestNetworkAgentWrapper(transportType, lp);
+ testAndCleanup(() -> {
+ final UidFrozenStateChangedCallback uidFrozenStateChangedCallback =
+ getUidFrozenStateChangedCallback().get();
+ final BaseNetdUnsolicitedEventListener netdUnsolicitedEventListener =
+ getRegisteredNetdUnsolicitedEventListener();
+
+ mCm.registerDefaultNetworkCallback(defaultCallback);
+ agent.connect(true);
+ defaultCallback.expectAvailableThenValidatedCallbacks(agent);
+ if (freezeWithNetworkInactive) {
+ // Make network inactive
+ netdUnsolicitedEventListener.onInterfaceClassActivityChanged(false /* isActive */,
+ transportType, TIMESTAMP, NETWORK_ACTIVITY_NO_UID);
+ }
+
+ // Freeze TEST_FROZEN_UID and TEST_UNFROZEN_UID
+ final int[] uids1 = {TEST_FROZEN_UID, TEST_UNFROZEN_UID};
+ final int[] frozenStates1 = {UID_FROZEN_STATE_FROZEN, UID_FROZEN_STATE_FROZEN};
+ uidFrozenStateChangedCallback.onUidFrozenStateChanged(uids1, frozenStates1);
+ waitForIdle();
+
+ if (expectDelay) {
+ verify(mDestroySocketsWrapper, never()).destroyLiveTcpSocketsByOwnerUids(any());
+ } else {
+ verify(mDestroySocketsWrapper).destroyLiveTcpSocketsByOwnerUids(
+ Set.of(TEST_FROZEN_UID, TEST_UNFROZEN_UID));
+ clearInvocations(mDestroySocketsWrapper);
+ }
+
+ // Unfreeze TEST_UNFROZEN_UID
+ final int[] uids2 = {TEST_UNFROZEN_UID};
+ final int[] frozenStates2 = {UID_FROZEN_STATE_UNFROZEN};
+ uidFrozenStateChangedCallback.onUidFrozenStateChanged(uids2, frozenStates2);
+
+ // Make network active
+ netdUnsolicitedEventListener.onInterfaceClassActivityChanged(true /* isActive */,
+ transportType, TIMESTAMP, TEST_PACKAGE_UID);
+ waitForIdle();
+
+ if (expectDelay) {
+ verify(mDestroySocketsWrapper).destroyLiveTcpSocketsByOwnerUids(
+ Set.of(TEST_FROZEN_UID));
+ } else {
+ verify(mDestroySocketsWrapper, never()).destroyLiveTcpSocketsByOwnerUids(any());
+ }
+ }, () -> { // Cleanup
+ agent.disconnect();
+ }, () -> {
+ mCm.unregisterNetworkCallback(defaultCallback);
+ });
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testDelayFrozenUidSocketDestroy_ActiveCellular() throws Exception {
+ doTestDelayFrozenUidSocketDestroy(TRANSPORT_CELLULAR,
+ false /* freezeWithNetworkInactive */, false /* expectDelay */);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testDelayFrozenUidSocketDestroy_InactiveCellular() throws Exception {
+ // When the default network is cellular and cellular network is inactive, closing socket
+ // is delayed.
+ doTestDelayFrozenUidSocketDestroy(TRANSPORT_CELLULAR,
+ true /* freezeWithNetworkInactive */, true /* expectDelay */);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testDelayFrozenUidSocketDestroy_ActiveWifi() throws Exception {
+ doTestDelayFrozenUidSocketDestroy(TRANSPORT_WIFI,
+ false /* freezeWithNetworkInactive */, false /* expectDelay */);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testDelayFrozenUidSocketDestroy_InactiveWifi() throws Exception {
+ doTestDelayFrozenUidSocketDestroy(TRANSPORT_WIFI,
+ true /* freezeWithNetworkInactive */, false /* expectDelay */);
+ }
+
+ /**
+ * @param switchToWifi if true, simulate a migration of the default network to wifi
+ * if false, simulate a cell disconnection
+ */
+ private void doTestLoseCellDefaultNetwork_ClosePendingFrozenSockets(final boolean switchToWifi)
+ throws Exception {
+ final UidFrozenStateChangedCallback uidFrozenStateChangedCallback =
+ getUidFrozenStateChangedCallback().get();
+ final BaseNetdUnsolicitedEventListener netdUnsolicitedEventListener =
+ getRegisteredNetdUnsolicitedEventListener();
+
+ final LinkProperties wifiLp = new LinkProperties();
+ wifiLp.setInterfaceName(WIFI_IFNAME);
+ mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+
+ final LinkProperties cellLp = new LinkProperties();
+ cellLp.setInterfaceName(MOBILE_IFNAME);
+ mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+
+ final TestNetworkCallback defaultCallback = new TestNetworkCallback();
+ mCm.registerDefaultNetworkCallback(defaultCallback);
+ try {
+ mCellAgent.connect(true);
+ defaultCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+
+ // Make cell network inactive
+ netdUnsolicitedEventListener.onInterfaceClassActivityChanged(false /* isActive */,
+ TRANSPORT_CELLULAR, TIMESTAMP, NETWORK_ACTIVITY_NO_UID);
+
+ // Freeze TEST_FROZEN_UID
+ final int[] uids = {TEST_FROZEN_UID};
+ final int[] frozenStates = {UID_FROZEN_STATE_FROZEN};
+ uidFrozenStateChangedCallback.onUidFrozenStateChanged(uids, frozenStates);
+ waitForIdle();
+
+ // Closing frozen sockets should be delayed since the default network is cellular
+ // and cellular network is inactive.
+ verify(mDestroySocketsWrapper, never()).destroyLiveTcpSocketsByOwnerUids(any());
+
+ if (switchToWifi) {
+ mWiFiAgent.connect(true);
+ defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiAgent);
+ } else {
+ mCellAgent.disconnect();
+ waitForIdle();
+ }
+
+ // Pending frozen sockets should be closed since the cellular network is no longer the
+ // default network.
+ verify(mDestroySocketsWrapper)
+ .destroyLiveTcpSocketsByOwnerUids(Set.of(TEST_FROZEN_UID));
+ } finally {
+ mCm.unregisterNetworkCallback(defaultCallback);
+ }
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testLoseCellDefaultNetwork_SwitchToWifi_ClosePendingFrozenSockets()
+ throws Exception {
+ doTestLoseCellDefaultNetwork_ClosePendingFrozenSockets(true /* switchToWifi */);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testLoseCellDefaultNetwork_NoDefaultNetwork_ClosePendingFrozenSockets()
+ throws Exception {
+ doTestLoseCellDefaultNetwork_ClosePendingFrozenSockets(false /* switchToWifi */);
+ }
+
@Test
public void testDisconnectSuspendedNetworkStopClatd() throws Exception {
final TestNetworkCallback networkCallback = new TestNetworkCallback();
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index f0c7dcc..44ed02a 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -16,20 +16,27 @@
package com.android.server;
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.NETWORK_STACK;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.content.pm.PermissionInfo.PROTECTION_SIGNATURE;
import static android.net.InetAddresses.parseNumericAddress;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_PLATFORM_MDNS_BACKEND;
import static android.net.connectivity.ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER;
import static android.net.nsd.NsdManager.FAILURE_BAD_PARAMETERS;
import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
import static android.net.nsd.NsdManager.FAILURE_OPERATION_NOT_RUNNING;
+import static com.android.networkstack.apishim.api33.ConstantsShim.REGISTER_NSD_OFFLOAD_ENGINE;
import static com.android.server.NsdService.DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF;
import static com.android.server.NsdService.MdnsListener;
import static com.android.server.NsdService.NO_TRANSACTION;
@@ -68,6 +75,8 @@
import android.compat.testing.PlatformCompatChangeRule;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
import android.net.INetd;
import android.net.Network;
import android.net.mdns.aidl.DiscoveryInfo;
@@ -84,6 +93,7 @@
import android.net.nsd.NsdManager.ResolveListener;
import android.net.nsd.NsdManager.ServiceInfoCallback;
import android.net.nsd.NsdServiceInfo;
+import android.net.nsd.OffloadEngine;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Build;
@@ -161,6 +171,7 @@
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
@Mock Context mContext;
+ @Mock PackageManager mPackageManager;
@Mock ContentResolver mResolver;
@Mock MDnsManager mMockMDnsM;
@Mock Dependencies mDeps;
@@ -198,6 +209,7 @@
mockService(mContext, MDnsManager.class, MDnsManager.MDNS_SERVICE, mMockMDnsM);
mockService(mContext, WifiManager.class, Context.WIFI_SERVICE, mWifiManager);
mockService(mContext, ActivityManager.class, Context.ACTIVITY_SERVICE, mActivityManager);
+ doReturn(mPackageManager).when(mContext).getPackageManager();
if (mContext.getSystemService(MDnsManager.class) == null) {
// Test is using mockito-extended
doCallRealMethod().when(mContext).getSystemService(MDnsManager.class);
@@ -1210,6 +1222,8 @@
verify(mMockMDnsM).stopOperation(legacyIdCaptor.getValue());
verify(mAdvertiser, never()).removeService(anyInt());
+ doReturn(mock(MdnsAdvertiser.AdvertiserMetrics.class))
+ .when(mAdvertiser).getAdvertiserMetrics(anyInt());
client.unregisterService(regListenerWithFeature);
waitForIdle();
verify(mAdvertiser).removeService(serviceIdCaptor.getValue());
@@ -1300,14 +1314,20 @@
new NsdServiceInfo(regInfo.getServiceName(), null))));
verify(mMetrics).reportServiceRegistrationSucceeded(regId, 10L /* durationMs */);
+ final MdnsAdvertiser.AdvertiserMetrics metrics = new MdnsAdvertiser.AdvertiserMetrics(
+ 50 /* repliedRequestCount */, 100 /* sentPacketCount */,
+ 3 /* conflictDuringProbingCount */, 2 /* conflictAfterProbingCount */);
doReturn(TEST_TIME_MS + 100L).when(mClock).elapsedRealtime();
+ doReturn(metrics).when(mAdvertiser).getAdvertiserMetrics(regId);
client.unregisterService(regListener);
waitForIdle();
verify(mAdvertiser).removeService(idCaptor.getValue());
verify(regListener, timeout(TIMEOUT_MS)).onServiceUnregistered(
argThat(info -> matches(info, regInfo)));
verify(mSocketProvider, timeout(TIMEOUT_MS)).requestStopWhenInactive();
- verify(mMetrics).reportServiceUnregistration(regId, 100L /* durationMs */);
+ verify(mMetrics).reportServiceUnregistration(regId, 100L /* durationMs */,
+ 50 /* repliedRequestCount */, 100 /* sentPacketCount */,
+ 3 /* conflictDuringProbingCount */, 2 /* conflictAfterProbingCount */);
}
@Test
@@ -1664,6 +1684,33 @@
assertThrows(IllegalArgumentException.class, () -> new NsdManager(mContext, service));
}
+ @Test
+ @EnableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
+ public void testRegisterOffloadEngine_checkPermission()
+ throws PackageManager.NameNotFoundException {
+ final NsdManager client = connectClient(mService);
+ final OffloadEngine offloadEngine = mock(OffloadEngine.class);
+ doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(NETWORK_STACK);
+ doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(
+ PERMISSION_MAINLINE_NETWORK_STACK);
+ doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(NETWORK_SETTINGS);
+ doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(
+ REGISTER_NSD_OFFLOAD_ENGINE);
+
+ PermissionInfo permissionInfo = new PermissionInfo("");
+ permissionInfo.packageName = "android";
+ permissionInfo.protectionLevel = PROTECTION_SIGNATURE;
+ doReturn(permissionInfo).when(mPackageManager).getPermissionInfo(
+ REGISTER_NSD_OFFLOAD_ENGINE, 0);
+ client.registerOffloadEngine("iface1", OffloadEngine.OFFLOAD_TYPE_REPLY,
+ OffloadEngine.OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK,
+ Runnable::run, offloadEngine);
+ client.unregisterOffloadEngine(offloadEngine);
+
+ // TODO: add checks to test the packageName other than android
+ }
+
+
private void waitForIdle() {
HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
index 9b38fea..8b10e0b 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
@@ -33,7 +33,10 @@
import com.android.testutils.waitForIdle
import java.net.NetworkInterface
import java.util.Objects
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
import org.junit.After
+import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -56,7 +59,9 @@
private const val CASE_INSENSITIVE_TEST_SERVICE_ID = 5
private const val TIMEOUT_MS = 10_000L
private val TEST_ADDR = parseNumericAddress("2001:db8::123")
+private val TEST_ADDR2 = parseNumericAddress("2001:db8::124")
private val TEST_LINKADDR = LinkAddress(TEST_ADDR, 64 /* prefixLength */)
+private val TEST_LINKADDR2 = LinkAddress(TEST_ADDR2, 64 /* prefixLength */)
private val TEST_NETWORK_1 = mock(Network::class.java)
private val TEST_SOCKETKEY_1 = SocketKey(1001 /* interfaceIndex */)
private val TEST_SOCKETKEY_2 = SocketKey(1002 /* interfaceIndex */)
@@ -64,6 +69,8 @@
private const val TEST_SUBTYPE = "_subtype"
private val TEST_INTERFACE1 = "test_iface1"
private val TEST_INTERFACE2 = "test_iface2"
+private val TEST_OFFLOAD_PACKET1 = byteArrayOf(0x01, 0x02, 0x03)
+private val TEST_OFFLOAD_PACKET2 = byteArrayOf(0x02, 0x03, 0x04)
private val SERVICE_1 = NsdServiceInfo("TestServiceName", "_advertisertest._tcp").apply {
port = 12345
@@ -102,7 +109,7 @@
OffloadServiceInfo.Key("TestServiceName", "_advertisertest._tcp"),
listOf(TEST_SUBTYPE),
"Android_test.local",
- null, /* rawOffloadPacket */
+ TEST_OFFLOAD_PACKET1,
0, /* priority */
OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
)
@@ -111,7 +118,16 @@
OffloadServiceInfo.Key("TestServiceName", "_advertisertest._tcp"),
listOf(),
"Android_test.local",
- null, /* rawOffloadPacket */
+ TEST_OFFLOAD_PACKET1,
+ 0, /* priority */
+ OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+)
+
+private val OFFLOAD_SERVICEINFO_NO_SUBTYPE2 = OffloadServiceInfo(
+ OffloadServiceInfo.Key("TestServiceName", "_advertisertest._tcp"),
+ listOf(),
+ "Android_test.local",
+ TEST_OFFLOAD_PACKET2,
0, /* priority */
OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
)
@@ -147,6 +163,10 @@
doReturn(createEmptyNetworkInterface()).`when`(mockSocket2).getInterface()
doReturn(TEST_INTERFACE1).`when`(mockInterfaceAdvertiser1).socketInterfaceName
doReturn(TEST_INTERFACE2).`when`(mockInterfaceAdvertiser2).socketInterfaceName
+ doReturn(TEST_OFFLOAD_PACKET1).`when`(mockInterfaceAdvertiser1).getRawOffloadPayload(
+ SERVICE_ID_1)
+ doReturn(TEST_OFFLOAD_PACKET1).`when`(mockInterfaceAdvertiser2).getRawOffloadPayload(
+ SERVICE_ID_1)
}
@After
@@ -189,10 +209,35 @@
verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1), argThat { it.matches(SERVICE_1) })
verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO_NO_SUBTYPE))
+ // Service is conflicted.
+ postSync { intAdvCbCaptor.value.onServiceConflict(mockInterfaceAdvertiser1, SERVICE_ID_1) }
+
+ // Verify the metrics data
+ doReturn(25).`when`(mockInterfaceAdvertiser1).getServiceRepliedRequestsCount(SERVICE_ID_1)
+ doReturn(40).`when`(mockInterfaceAdvertiser1).getSentPacketCount(SERVICE_ID_1)
+ val metrics = postReturn { advertiser.getAdvertiserMetrics(SERVICE_ID_1) }
+ assertEquals(25, metrics.mRepliedRequestsCount)
+ assertEquals(40, metrics.mSentPacketCount)
+ assertEquals(0, metrics.mConflictDuringProbingCount)
+ assertEquals(1, metrics.mConflictAfterProbingCount)
+
+ doReturn(TEST_OFFLOAD_PACKET2).`when`(mockInterfaceAdvertiser1)
+ .getRawOffloadPayload(
+ SERVICE_ID_1
+ )
+ postSync {
+ socketCb.onAddressesChanged(
+ TEST_SOCKETKEY_1,
+ mockSocket1,
+ listOf(TEST_LINKADDR2)
+ )
+ }
+ verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO_NO_SUBTYPE2))
+
postSync { socketCb.onInterfaceDestroyed(TEST_SOCKETKEY_1, mockSocket1) }
verify(mockInterfaceAdvertiser1).destroyNow()
postSync { intAdvCbCaptor.value.onDestroyed(mockSocket1) }
- verify(cb).onOffloadStop(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO_NO_SUBTYPE))
+ verify(cb).onOffloadStop(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO_NO_SUBTYPE2))
}
@Test
@@ -235,6 +280,22 @@
verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1),
argThat { it.matches(ALL_NETWORKS_SERVICE) })
+ // Services are conflicted.
+ postSync { intAdvCbCaptor1.value.onServiceConflict(mockInterfaceAdvertiser1, SERVICE_ID_1) }
+ postSync { intAdvCbCaptor1.value.onServiceConflict(mockInterfaceAdvertiser1, SERVICE_ID_1) }
+ postSync { intAdvCbCaptor2.value.onServiceConflict(mockInterfaceAdvertiser2, SERVICE_ID_1) }
+
+ // Verify the metrics data
+ doReturn(10).`when`(mockInterfaceAdvertiser1).getServiceRepliedRequestsCount(SERVICE_ID_1)
+ doReturn(5).`when`(mockInterfaceAdvertiser2).getServiceRepliedRequestsCount(SERVICE_ID_1)
+ doReturn(22).`when`(mockInterfaceAdvertiser1).getSentPacketCount(SERVICE_ID_1)
+ doReturn(12).`when`(mockInterfaceAdvertiser2).getSentPacketCount(SERVICE_ID_1)
+ val metrics = postReturn { advertiser.getAdvertiserMetrics(SERVICE_ID_1) }
+ assertEquals(15, metrics.mRepliedRequestsCount)
+ assertEquals(34, metrics.mSentPacketCount)
+ assertEquals(2, metrics.mConflictDuringProbingCount)
+ assertEquals(1, metrics.mConflictAfterProbingCount)
+
// Unregister the service
postSync { advertiser.removeService(SERVICE_ID_1) }
verify(mockInterfaceAdvertiser1).removeService(SERVICE_ID_1)
@@ -346,6 +407,14 @@
handler.post(r)
handler.waitForIdle(TIMEOUT_MS)
}
+
+ private fun <T> postReturn(r: (() -> T)): T {
+ val future = CompletableFuture<T>()
+ handler.post {
+ future.complete(r())
+ }
+ return future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }
}
// NsdServiceInfo does not implement equals; this is useful to use in argument matchers
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
index 12faa50..c39ee1e 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
@@ -253,7 +253,7 @@
val captor = ArgumentCaptor.forClass(DatagramPacket::class.java)
repeat(FIRST_ANNOUNCES_COUNT) { i ->
- verify(cb, timeout(TEST_TIMEOUT_MS)).onSent(i, request)
+ verify(cb, timeout(TEST_TIMEOUT_MS)).onSent(i, request, 1 /* sentPacketCount */)
verify(socket, atLeast(i + 1)).send(any())
val now = SystemClock.elapsedRealtime()
assertTrue(now > timeStart + startDelay + i * FIRST_ANNOUNCES_DELAY)
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
index 5ca4dd6..f284819 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
@@ -92,7 +92,7 @@
private fun assertProbesSent(probeInfo: TestProbeInfo, expectedHex: String) {
repeat(probeInfo.numSends) { i ->
- verify(cb, timeout(TEST_TIMEOUT_MS)).onSent(i, probeInfo)
+ verify(cb, timeout(TEST_TIMEOUT_MS)).onSent(i, probeInfo, 1 /* sentPacketCount */)
// If the probe interval is short, more than (i+1) probes may have been sent already
verify(socket, atLeast(i + 1)).send(any())
}
@@ -190,7 +190,7 @@
prober.startProbing(probeInfo)
// Expect the initial probe
- verify(cb, timeout(TEST_TIMEOUT_MS)).onSent(0, probeInfo)
+ verify(cb, timeout(TEST_TIMEOUT_MS)).onSent(0, probeInfo, 1 /* sentPacketCount */)
// Stop probing
val stopResult = CompletableFuture<Boolean>()
@@ -200,7 +200,7 @@
// Wait for a bit (more than the probe delay) to ensure no more probes were sent
Thread.sleep(SHORT_TIMEOUT_MS * 2)
- verify(cb, never()).onSent(1, probeInfo)
+ verify(cb, never()).onSent(1, probeInfo, 1 /* sentPacketCount */)
verify(cb, never()).onFinished(probeInfo)
// Only one sent packet
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
index 0033b5a..af47b1c 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
@@ -155,7 +155,7 @@
val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
repository.onProbingSucceeded(probingInfo)
- repository.onAdvertisementSent(TEST_SERVICE_ID_1)
+ repository.onAdvertisementSent(TEST_SERVICE_ID_1, 2 /* sentPacketCount */)
assertTrue(repository.hasActiveService(TEST_SERVICE_ID_1))
repository.exitService(TEST_SERVICE_ID_1)
@@ -166,7 +166,7 @@
fun testExitAnnouncements() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
- repository.onAdvertisementSent(TEST_SERVICE_ID_1)
+ repository.onAdvertisementSent(TEST_SERVICE_ID_1, 2 /* sentPacketCount */)
val exitAnnouncement = repository.exitService(TEST_SERVICE_ID_1)
assertNotNull(exitAnnouncement)
@@ -195,7 +195,7 @@
fun testExitAnnouncements_WithSubtype() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, TEST_SUBTYPE)
- repository.onAdvertisementSent(TEST_SERVICE_ID_1)
+ repository.onAdvertisementSent(TEST_SERVICE_ID_1, 2 /* sentPacketCount */)
val exitAnnouncement = repository.exitService(TEST_SERVICE_ID_1)
assertNotNull(exitAnnouncement)
@@ -230,7 +230,7 @@
fun testExitingServiceReAdded() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
- repository.onAdvertisementSent(TEST_SERVICE_ID_1)
+ repository.onAdvertisementSent(TEST_SERVICE_ID_1, 2 /* sentPacketCount */)
repository.exitService(TEST_SERVICE_ID_1)
assertEquals(TEST_SERVICE_ID_1,
@@ -246,7 +246,7 @@
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
val announcementInfo = repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1,
TEST_SUBTYPE)
- repository.onAdvertisementSent(TEST_SERVICE_ID_1)
+ repository.onAdvertisementSent(TEST_SERVICE_ID_1, 2 /* sentPacketCount */)
val packet = announcementInfo.getPacket(0)
assertEquals(0x8400 /* response, authoritative */, packet.flags)
@@ -366,6 +366,58 @@
}
@Test
+ fun testGetOffloadPacket() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+ repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+ val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
+ val serviceType = arrayOf("_testservice", "_tcp", "local")
+ val offloadPacket = repository.getOffloadPacket(TEST_SERVICE_ID_1)
+ assertEquals(0x8400, offloadPacket.flags)
+ assertEquals(0, offloadPacket.questions.size)
+ assertEquals(0, offloadPacket.additionalRecords.size)
+ assertEquals(0, offloadPacket.authorityRecords.size)
+ assertContentEquals(listOf(
+ MdnsPointerRecord(
+ serviceType,
+ 0L /* receiptTimeMillis */,
+ // Not a unique name owned by the announcer, so cacheFlush=false
+ false /* cacheFlush */,
+ 4500000L /* ttlMillis */,
+ serviceName),
+ MdnsServiceRecord(
+ serviceName,
+ 0L /* receiptTimeMillis */,
+ true /* cacheFlush */,
+ 120000L /* ttlMillis */,
+ 0 /* servicePriority */,
+ 0 /* serviceWeight */,
+ TEST_PORT /* servicePort */,
+ TEST_HOSTNAME),
+ MdnsTextRecord(
+ serviceName,
+ 0L /* receiptTimeMillis */,
+ true /* cacheFlush */,
+ 4500000L /* ttlMillis */,
+ emptyList() /* entries */),
+ MdnsInetAddressRecord(TEST_HOSTNAME,
+ 0L /* receiptTimeMillis */,
+ true /* cacheFlush */,
+ 120000L /* ttlMillis */,
+ TEST_ADDRESSES[0].address),
+ MdnsInetAddressRecord(TEST_HOSTNAME,
+ 0L /* receiptTimeMillis */,
+ true /* cacheFlush */,
+ 120000L /* ttlMillis */,
+ TEST_ADDRESSES[1].address),
+ MdnsInetAddressRecord(TEST_HOSTNAME,
+ 0L /* receiptTimeMillis */,
+ true /* cacheFlush */,
+ 120000L /* ttlMillis */,
+ TEST_ADDRESSES[2].address),
+ ), offloadPacket.answers)
+ }
+
+ @Test
fun testGetReverseDnsAddress() {
val expectedV6 = "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.B.D.0.1.0.0.2.ip6.arpa"
.split(".").toTypedArray()
@@ -605,6 +657,34 @@
// Above records are identical to the actual registrations: no conflict
assertEquals(emptySet(), repository.getConflictingServices(packet))
}
+
+ @Test
+ fun testGetServiceRepliedRequestsCount() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+ repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+ // Verify that there is no packet replied.
+ assertEquals(MdnsConstants.NO_PACKET,
+ repository.getServiceRepliedRequestsCount(TEST_SERVICE_ID_1))
+
+ val questions = listOf(MdnsPointerRecord(arrayOf("_testservice", "_tcp", "local"),
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ // TTL and data is empty for a question
+ 0L /* ttlMillis */,
+ null /* pointer */))
+ val query = MdnsPacket(0 /* flags */, questions, listOf() /* answers */,
+ listOf() /* authorityRecords */, listOf() /* additionalRecords */)
+ val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
+
+ // Reply to the question and verify there is one packet replied.
+ val reply = repository.getReply(query, src)
+ assertNotNull(reply)
+ assertEquals(1, repository.getServiceRepliedRequestsCount(TEST_SERVICE_ID_1))
+
+ // No package replied for unknown service.
+ assertEquals(MdnsConstants.NO_PACKET,
+ repository.getServiceRepliedRequestsCount(TEST_SERVICE_ID_2))
+ }
}
private fun MdnsRecordRepository.initWithService(