Add API for FLAG_SKIP_PROBING
The flag, used in the newly exposed AdvertisingRequest, allows
skipping the probing step when the service name is known to be unique
already.
This allows implementing RFC 6762 section 8.1 that specifies probing
SHOULD be skipped if it is known via other means that the record set
is unique.
Bug: 384841047
Test: atest CtsNetTestCases:NsdManagerTest
Change-Id: I19d67ff2863af631bd9c2f518b004f1688ac5f87
diff --git a/framework-t/api/current.txt b/framework-t/api/current.txt
index 75578aa..d66482c 100644
--- a/framework-t/api/current.txt
+++ b/framework-t/api/current.txt
@@ -210,6 +210,23 @@
package android.net.nsd {
+ @FlaggedApi("com.android.net.flags.ipv6_over_ble") public final class AdvertisingRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method public long getFlags();
+ method public int getProtocolType();
+ method @NonNull public android.net.nsd.NsdServiceInfo getServiceInfo();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.nsd.AdvertisingRequest> CREATOR;
+ field public static final long FLAG_SKIP_PROBING = 2L; // 0x2L
+ }
+
+ @FlaggedApi("com.android.net.flags.ipv6_over_ble") public static final class AdvertisingRequest.Builder {
+ ctor public AdvertisingRequest.Builder(@NonNull android.net.nsd.NsdServiceInfo);
+ method @NonNull public android.net.nsd.AdvertisingRequest build();
+ method @NonNull public android.net.nsd.AdvertisingRequest.Builder setFlags(long);
+ method @NonNull public android.net.nsd.AdvertisingRequest.Builder setProtocolType(int);
+ }
+
@FlaggedApi("com.android.net.flags.nsd_subtypes_support_enabled") public final class DiscoveryRequest implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.net.Network getNetwork();
diff --git a/framework-t/src/android/net/nsd/AdvertisingRequest.java b/framework-t/src/android/net/nsd/AdvertisingRequest.java
index 6afb2d5..a62df65 100644
--- a/framework-t/src/android/net/nsd/AdvertisingRequest.java
+++ b/framework-t/src/android/net/nsd/AdvertisingRequest.java
@@ -15,12 +15,16 @@
*/
package android.net.nsd;
+import android.annotation.FlaggedApi;
import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.net.nsd.NsdManager.ProtocolType;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.net.flags.Flags;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Duration;
@@ -28,16 +32,32 @@
/**
* Encapsulates parameters for {@link NsdManager#registerService}.
- * @hide
*/
-//@FlaggedApi(NsdManager.Flags.ADVERTISE_REQUEST_API)
+@FlaggedApi(Flags.FLAG_IPV6_OVER_BLE)
public final class AdvertisingRequest implements Parcelable {
/**
* Only update the registration without sending exit and re-announcement.
+ * @hide
*/
public static final long NSD_ADVERTISING_UPDATE_ONLY = 1;
+ // TODO: if apps are allowed to set hostnames, the below doc should be updated to mention that
+ // passed in hostnames must also be known unique to use this flag.
+ /**
+ * Skip the probing step when advertising.
+ *
+ * <p>This must only be used when the service name ({@link NsdServiceInfo#getServiceName()} is
+ * known to be unique and cannot possibly be used by any other device on the network.
+ */
+ public static final long FLAG_SKIP_PROBING = 1 << 1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @LongDef(flag = true, prefix = {"FLAG_"}, value = {
+ FLAG_SKIP_PROBING,
+ })
+ public @interface AdvertisingFlags {}
@NonNull
public static final Creator<AdvertisingRequest> CREATOR =
@@ -79,7 +99,7 @@
/**
* The constructor for the advertiseRequest
*/
- private AdvertisingRequest(@NonNull NsdServiceInfo serviceInfo, int protocolType,
+ private AdvertisingRequest(@NonNull NsdServiceInfo serviceInfo, @ProtocolType int protocolType,
long advertisingConfig, @NonNull Duration ttl) {
mServiceInfo = serviceInfo;
mProtocolType = protocolType;
@@ -88,7 +108,7 @@
}
/**
- * Returns the {@link NsdServiceInfo}
+ * @return the {@link NsdServiceInfo} describing the service to advertise.
*/
@NonNull
public NsdServiceInfo getServiceInfo() {
@@ -96,16 +116,18 @@
}
/**
- * Returns the service advertise protocol
+ * @return the service advertisement protocol.
*/
+ @ProtocolType
public int getProtocolType() {
return mProtocolType;
}
/**
- * Returns the advertising config.
+ * @return the flags affecting advertising behavior.
*/
- public long getAdvertisingConfig() {
+ @AdvertisingFlags
+ public long getFlags() {
return mAdvertisingConfig;
}
@@ -165,34 +187,45 @@
dest.writeLong(mTtl == null ? -1L : mTtl.getSeconds());
}
-// @FlaggedApi(NsdManager.Flags.ADVERTISE_REQUEST_API)
/**
- * The builder for creating new {@link AdvertisingRequest} objects.
- * @hide
+ * A builder for creating new {@link AdvertisingRequest} objects.
*/
+ @FlaggedApi(Flags.FLAG_IPV6_OVER_BLE)
public static final class Builder {
@NonNull
private final NsdServiceInfo mServiceInfo;
- private final int mProtocolType;
+ private int mProtocolType;
private long mAdvertisingConfig;
@Nullable
private Duration mTtl;
+
/**
* Creates a new {@link Builder} object.
+ * @param serviceInfo the {@link NsdServiceInfo} describing the service to advertise.
+ * @param protocolType the advertising protocol to use.
+ * @hide
*/
- public Builder(@NonNull NsdServiceInfo serviceInfo, int protocolType) {
+ public Builder(@NonNull NsdServiceInfo serviceInfo, @ProtocolType int protocolType) {
mServiceInfo = serviceInfo;
mProtocolType = protocolType;
}
/**
+ * Creates a new {@link Builder} object.
+ * @param serviceInfo the {@link NsdServiceInfo} describing the service to advertise.
+ */
+ public Builder(@NonNull NsdServiceInfo serviceInfo) {
+ this(serviceInfo, NsdManager.PROTOCOL_DNS_SD);
+ }
+
+ /**
* Sets advertising configuration flags.
*
- * @param advertisingConfigFlags Bitmask of {@code AdvertisingConfig} flags.
+ * @param flags flags to use for advertising.
*/
@NonNull
- public Builder setAdvertisingConfig(long advertisingConfigFlags) {
- mAdvertisingConfig = advertisingConfigFlags;
+ public Builder setFlags(@AdvertisingFlags long flags) {
+ mAdvertisingConfig = flags;
return this;
}
@@ -232,6 +265,16 @@
return this;
}
+ /**
+ * Sets the protocol to use for advertising.
+ * @param protocolType the advertising protocol to use.
+ */
+ @NonNull
+ public Builder setProtocolType(@ProtocolType int protocolType) {
+ mProtocolType = protocolType;
+ return this;
+ }
+
/** Creates a new {@link AdvertisingRequest} object. */
@NonNull
public AdvertisingRequest build() {
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index 116bea6..426a92d 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -314,6 +314,13 @@
/** Dns based service discovery protocol */
public static final int PROTOCOL_DNS_SD = 0x0001;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"PROTOCOL_"}, value = {
+ PROTOCOL_DNS_SD,
+ })
+ public @interface ProtocolType {}
+
/**
* The minimum TTL seconds which is allowed for a service registration.
*
@@ -1272,7 +1279,7 @@
// documented in the NsdServiceInfo.setSubtypes API instead, but this provides a limited
// option for users of the older undocumented behavior, only for subtype changes.
if (isSubtypeUpdateRequest(serviceInfo, listener)) {
- builder.setAdvertisingConfig(AdvertisingRequest.NSD_ADVERTISING_UPDATE_ONLY);
+ builder.setFlags(AdvertisingRequest.NSD_ADVERTISING_UPDATE_ONLY);
}
registerService(builder.build(), executor, listener);
}
@@ -1358,7 +1365,7 @@
checkProtocol(protocolType);
final int key;
// For update only request, the old listener has to be reused
- if ((advertisingRequest.getAdvertisingConfig()
+ if ((advertisingRequest.getFlags()
& AdvertisingRequest.NSD_ADVERTISING_UPDATE_ONLY) > 0) {
key = updateRegisteredListener(listener, executor, serviceInfo);
} else {
diff --git a/framework-t/src/android/net/nsd/NsdServiceInfo.java b/framework-t/src/android/net/nsd/NsdServiceInfo.java
index 52d6e7e..6a5ab4d 100644
--- a/framework-t/src/android/net/nsd/NsdServiceInfo.java
+++ b/framework-t/src/android/net/nsd/NsdServiceInfo.java
@@ -211,6 +211,8 @@
return mHostname;
}
+ // TODO: if setHostname is made public, AdvertisingRequest#FLAG_SKIP_PROBING javadoc must be
+ // updated to mention that hostnames must also be known unique to use that flag.
/**
* Set a custom hostname for this service instance for registration.
*
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 0adb290..7c8585a 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -23,6 +23,7 @@
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.AdvertisingRequest.FLAG_SKIP_PROBING;
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;
@@ -981,7 +982,7 @@
NsdManager.FAILURE_INTERNAL_ERROR, false /* isLegacy */);
break;
}
- boolean isUpdateOnly = (advertisingRequest.getAdvertisingConfig()
+ boolean isUpdateOnly = (advertisingRequest.getFlags()
& AdvertisingRequest.NSD_ADVERTISING_UPDATE_ONLY) > 0;
// If it is an update request, then reuse the old transactionId
if (isUpdateOnly) {
@@ -1046,9 +1047,12 @@
serviceInfo.setSubtypes(subtypes);
maybeStartMonitoringSockets();
+ final boolean skipProbing = (advertisingRequest.getFlags()
+ & FLAG_SKIP_PROBING) > 0;
final MdnsAdvertisingOptions mdnsAdvertisingOptions =
MdnsAdvertisingOptions.newBuilder()
.setIsOnlyUpdate(isUpdateOnly)
+ .setSkipProbing(skipProbing)
.setTtl(advertisingRequest.getTtl())
.build();
mAdvertiser.addOrUpdateService(transactionId, serviceInfo,
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertisingOptions.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertisingOptions.java
index a81d1e4..5133d4f 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertisingOptions.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertisingOptions.java
@@ -34,13 +34,15 @@
private final boolean mIsOnlyUpdate;
@Nullable
private final Duration mTtl;
+ private final boolean mSkipProbing;
/**
* Parcelable constructs for a {@link MdnsAdvertisingOptions}.
*/
- MdnsAdvertisingOptions(boolean isOnlyUpdate, @Nullable Duration ttl) {
+ MdnsAdvertisingOptions(boolean isOnlyUpdate, @Nullable Duration ttl, boolean skipProbing) {
this.mIsOnlyUpdate = isOnlyUpdate;
this.mTtl = ttl;
+ this.mSkipProbing = skipProbing;
}
/**
@@ -68,6 +70,13 @@
}
/**
+ * @return {@code true} if the probing step should be skipped.
+ */
+ public boolean skipProbing() {
+ return mSkipProbing;
+ }
+
+ /**
* Returns the TTL for all records in a service.
*/
@Nullable
@@ -104,6 +113,7 @@
*/
public static final class Builder {
private boolean mIsOnlyUpdate = false;
+ private boolean mSkipProbing = false;
@Nullable
private Duration mTtl;
@@ -127,10 +137,18 @@
}
/**
+ * Sets whether to skip the probing step.
+ */
+ public Builder setSkipProbing(boolean skipProbing) {
+ this.mSkipProbing = skipProbing;
+ return this;
+ }
+
+ /**
* Builds a {@link MdnsAdvertisingOptions} with the arguments supplied to this builder.
*/
public MdnsAdvertisingOptions build() {
- return new MdnsAdvertisingOptions(mIsOnlyUpdate, mTtl);
+ return new MdnsAdvertisingOptions(mIsOnlyUpdate, mTtl, mSkipProbing);
}
}
}
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 58defa9..b9b09ed 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -122,28 +122,32 @@
}
@Override
public void onFinished(MdnsProber.ProbingInfo info) {
- final MdnsAnnouncer.AnnouncementInfo announcementInfo;
- mSharedLog.i("Probing finished for service " + info.getServiceId());
- mCbHandler.post(() -> mCb.onServiceProbingSucceeded(
- MdnsInterfaceAdvertiser.this, info.getServiceId()));
- try {
- announcementInfo = mRecordRepository.onProbingSucceeded(info);
- } catch (IOException e) {
- mSharedLog.e("Error building announcements", e);
- return;
- }
+ handleProbingFinished(info);
+ }
+ }
- mAnnouncer.startSending(info.getServiceId(), announcementInfo,
- 0L /* initialDelayMs */);
+ private void handleProbingFinished(MdnsProber.ProbingInfo info) {
+ final MdnsAnnouncer.AnnouncementInfo announcementInfo;
+ mSharedLog.i("Probing finished for service " + info.getServiceId());
+ mCbHandler.post(() -> mCb.onServiceProbingSucceeded(
+ MdnsInterfaceAdvertiser.this, info.getServiceId()));
+ try {
+ announcementInfo = mRecordRepository.onProbingSucceeded(info);
+ } catch (IOException e) {
+ mSharedLog.e("Error building announcements", e);
+ return;
+ }
- // Re-announce the services which have the same custom hostname.
- final String hostname = mRecordRepository.getHostnameForServiceId(info.getServiceId());
- if (hostname != null) {
- final List<MdnsAnnouncer.AnnouncementInfo> announcementInfos =
- new ArrayList<>(mRecordRepository.restartAnnouncingForHostname(hostname));
- announcementInfos.removeIf((i) -> i.getServiceId() == info.getServiceId());
- reannounceServices(announcementInfos);
- }
+ mAnnouncer.startSending(info.getServiceId(), announcementInfo,
+ 0L /* initialDelayMs */);
+
+ // Re-announce the services which have the same custom hostname.
+ final String hostname = mRecordRepository.getHostnameForServiceId(info.getServiceId());
+ if (hostname != null) {
+ final List<MdnsAnnouncer.AnnouncementInfo> announcementInfos =
+ new ArrayList<>(mRecordRepository.restartAnnouncingForHostname(hostname));
+ announcementInfos.removeIf((i) -> i.getServiceId() == info.getServiceId());
+ reannounceServices(announcementInfos);
}
}
@@ -280,7 +284,12 @@
+ " getting re-added, cancelling exit announcements");
mAnnouncer.stop(replacedExitingService);
}
- mProber.startProbing(mRecordRepository.setServiceProbing(id));
+ final MdnsProber.ProbingInfo probingInfo = mRecordRepository.setServiceProbing(id);
+ if (advertisingOptions.skipProbing()) {
+ handleProbingFinished(probingInfo);
+ } else {
+ mProber.startProbing(probingInfo);
+ }
}
/**
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 7fc8863..c981a1b 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -40,8 +40,11 @@
import android.net.TestNetworkSpecifier
import android.net.connectivity.ConnectivityCompatChanges
import android.net.cts.util.CtsNetUtils
+import android.net.nsd.AdvertisingRequest
+import android.net.nsd.AdvertisingRequest.FLAG_SKIP_PROBING
import android.net.nsd.DiscoveryRequest
import android.net.nsd.NsdManager
+import android.net.nsd.NsdManager.PROTOCOL_DNS_SD
import android.net.nsd.NsdServiceInfo
import android.net.nsd.OffloadEngine
import android.net.nsd.OffloadServiceInfo
@@ -98,9 +101,9 @@
import com.android.testutils.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdated
import com.android.testutils.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdatedLost
import com.android.testutils.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.UnregisterCallbackSucceeded
+import com.android.testutils.PollPacketReader
import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
-import com.android.testutils.PollPacketReader
import com.android.testutils.TestDnsPacket
import com.android.testutils.TestableNetworkAgent
import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkCreated
@@ -2629,6 +2632,49 @@
verifyCachedServicesRemoval(isCachedServiceRemoved = true)
}
+ @Test
+ fun testSkipProbing() {
+ val si = makeTestServiceInfo(testNetwork1.network)
+ val request = AdvertisingRequest.Builder(si)
+ .setFlags(FLAG_SKIP_PROBING)
+ .build()
+ assertEquals(FLAG_SKIP_PROBING, request.flags)
+ assertEquals(PROTOCOL_DNS_SD, request.protocolType)
+ assertEquals(si.serviceName, request.serviceInfo.serviceName)
+
+ // Register service on testNetwork1
+ val registrationRecord = NsdRegistrationRecord()
+ nsdManager.registerService(request, { it.run() }, registrationRecord)
+ registrationRecord.expectCallback<ServiceRegistered>()
+ val packetReader = makePacketReader()
+
+ tryTest {
+ val srvRecordName = "$serviceName.$serviceType.local"
+ // Look for either announcements or probes
+ val packet = packetReader.pollForMdnsPacket {
+ it.isProbeFor(srvRecordName) || it.isReplyFor(srvRecordName)
+ }
+ assertNotNull(packet, "Probe or announcement not received within timeout")
+ // The first packet should be an announcement, not a probe.
+ assertTrue("Found initial probes with NSD_ADVERTISING_SKIP_PROBING enabled",
+ packet.isReplyFor(srvRecordName))
+
+ // Force a conflict now that the service is getting announced
+ val conflictingAnnouncement = buildConflictingAnnouncement()
+ packetReader.sendResponse(conflictingAnnouncement)
+
+ // Expect to see probes now (RFC6762 9., service is reset to probing state)
+ assertNotNull(packetReader.pollForProbe(serviceName, serviceType),
+ "Probe not received within timeout after conflict")
+ } cleanupStep {
+ nsdManager.unregisterService(registrationRecord)
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ } cleanup {
+ packetReader.handler.post { packetReader.stop() }
+ handlerThread.waitForIdle(TIMEOUT_MS)
+ }
+ }
+
private fun hasServiceTypeClientsForNetwork(clients: List<String>, network: Network): Boolean {
return clients.any { client -> client.substring(
client.indexOf("network=") + "network=".length,
diff --git a/tests/unit/java/android/net/nsd/AdvertisingRequestTest.kt b/tests/unit/java/android/net/nsd/AdvertisingRequestTest.kt
index c491f37..8117431 100644
--- a/tests/unit/java/android/net/nsd/AdvertisingRequestTest.kt
+++ b/tests/unit/java/android/net/nsd/AdvertisingRequestTest.kt
@@ -44,14 +44,14 @@
serviceType = "_ipp._tcp"
}
val beforeParcel = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD)
- .setAdvertisingConfig(NSD_ADVERTISING_UPDATE_ONLY)
+ .setFlags(NSD_ADVERTISING_UPDATE_ONLY)
.setTtl(Duration.ofSeconds(30L))
.build()
val afterParcel = parcelingRoundTrip(beforeParcel)
assertEquals(beforeParcel.serviceInfo.serviceType, afterParcel.serviceInfo.serviceType)
- assertEquals(beforeParcel.advertisingConfig, afterParcel.advertisingConfig)
+ assertEquals(beforeParcel.flags, afterParcel.flags)
}
@Test
@@ -72,13 +72,13 @@
serviceType = "_ipp._tcp"
}
val request = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD)
- .setAdvertisingConfig(NSD_ADVERTISING_UPDATE_ONLY)
+ .setFlags(NSD_ADVERTISING_UPDATE_ONLY)
.setTtl(Duration.ofSeconds(100L))
.build()
assertEquals("_ipp._tcp", request.serviceInfo.serviceType)
assertEquals(PROTOCOL_DNS_SD, request.protocolType)
- assertEquals(NSD_ADVERTISING_UPDATE_ONLY, request.advertisingConfig)
+ assertEquals(NSD_ADVERTISING_UPDATE_ONLY, request.flags)
assertEquals(Duration.ofSeconds(100L), request.ttl)
}
@@ -90,11 +90,11 @@
val request1 = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD).build()
val request2 = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD).build()
val request3 = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD)
- .setAdvertisingConfig(NSD_ADVERTISING_UPDATE_ONLY)
+ .setFlags(NSD_ADVERTISING_UPDATE_ONLY)
.setTtl(Duration.ofSeconds(120L))
.build()
val request4 = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD)
- .setAdvertisingConfig(NSD_ADVERTISING_UPDATE_ONLY)
+ .setFlags(NSD_ADVERTISING_UPDATE_ONLY)
.setTtl(Duration.ofSeconds(120L))
.build()