diff --git a/framework-t/api/current.txt b/framework-t/api/current.txt index 75578aa59b..d66482cb16 100644 --- a/framework-t/api/current.txt +++ b/framework-t/api/current.txt @@ -210,6 +210,23 @@ package android.net { 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 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 6afb2d58a5..a62df65901 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 @@ import java.util.Objects; /** * 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. + * + *

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 CREATOR = @@ -79,7 +99,7 @@ public final class AdvertisingRequest implements Parcelable { /** * 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 @@ public final class AdvertisingRequest implements Parcelable { } /** - * Returns the {@link NsdServiceInfo} + * @return the {@link NsdServiceInfo} describing the service to advertise. */ @NonNull public NsdServiceInfo getServiceInfo() { @@ -96,16 +116,18 @@ public final class AdvertisingRequest implements Parcelable { } /** - * 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 @@ public final class AdvertisingRequest implements Parcelable { 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 @@ public final class AdvertisingRequest implements Parcelable { 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 116bea6a0e..426a92d2bf 100644 --- a/framework-t/src/android/net/nsd/NsdManager.java +++ b/framework-t/src/android/net/nsd/NsdManager.java @@ -314,6 +314,13 @@ public final class NsdManager { /** 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 @@ public final class NsdManager { // 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 @@ public final class NsdManager { 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 52d6e7eb84..6a5ab4d978 100644 --- a/framework-t/src/android/net/nsd/NsdServiceInfo.java +++ b/framework-t/src/android/net/nsd/NsdServiceInfo.java @@ -211,6 +211,8 @@ public final class NsdServiceInfo implements Parcelable { 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 0adb2908c9..7c8585a508 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.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.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 @@ public class NsdService extends INsdManager.Stub { 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 @@ public class NsdService extends INsdManager.Stub { 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 a81d1e489b..5133d4f730 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 @@ public class MdnsAdvertisingOptions { 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; } /** @@ -67,6 +69,13 @@ public class MdnsAdvertisingOptions { return mIsOnlyUpdate; } + /** + * @return {@code true} if the probing step should be skipped. + */ + public boolean skipProbing() { + return mSkipProbing; + } + /** * Returns the TTL for all records in a service. */ @@ -104,6 +113,7 @@ public class MdnsAdvertisingOptions { */ public static final class Builder { private boolean mIsOnlyUpdate = false; + private boolean mSkipProbing = false; @Nullable private Duration mTtl; @@ -126,11 +136,19 @@ public class MdnsAdvertisingOptions { return this; } + /** + * 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 58defa9886..b9b09ed0c8 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 @@ public class MdnsInterfaceAdvertiser implements MulticastPacketReader.PacketHand } @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 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 announcementInfos = + new ArrayList<>(mRecordRepository.restartAnnouncingForHostname(hostname)); + announcementInfos.removeIf((i) -> i.getServiceId() == info.getServiceId()); + reannounceServices(announcementInfos); } } @@ -280,7 +284,12 @@ public class MdnsInterfaceAdvertiser implements MulticastPacketReader.PacketHand + " 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 7fc8863092..c981a1b7c3 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.TestNetworkManager 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 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 @@ class NsdManagerTest { 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() + 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() + } cleanup { + packetReader.handler.post { packetReader.stop() } + handlerThread.waitForIdle(TIMEOUT_MS) + } + } + private fun hasServiceTypeClientsForNetwork(clients: List, 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 c491f373a6..8117431e06 100644 --- a/tests/unit/java/android/net/nsd/AdvertisingRequestTest.kt +++ b/tests/unit/java/android/net/nsd/AdvertisingRequestTest.kt @@ -44,14 +44,14 @@ class AdvertisingRequestTest { 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 @@ class AdvertisingRequestTest { 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 @@ class AdvertisingRequestTest { 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()