Merge changes from topic "mdns-ttl" into main am: 0520e26936
Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/2870274
Change-Id: I9cda2e567946000894917e5cf5b451da24028d6e
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/framework-t/src/android/net/nsd/AdvertisingRequest.java b/framework-t/src/android/net/nsd/AdvertisingRequest.java
index b1ef98f..2895b0c 100644
--- a/framework-t/src/android/net/nsd/AdvertisingRequest.java
+++ b/framework-t/src/android/net/nsd/AdvertisingRequest.java
@@ -17,11 +17,13 @@
import android.annotation.LongDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
import java.util.Objects;
/**
@@ -34,7 +36,7 @@
/**
* Only update the registration without sending exit and re-announcement.
*/
- public static final int NSD_ADVERTISING_UPDATE_ONLY = 1;
+ public static final long NSD_ADVERTISING_UPDATE_ONLY = 1;
@NonNull
@@ -46,7 +48,9 @@
NsdServiceInfo.class.getClassLoader(), NsdServiceInfo.class);
final int protocolType = in.readInt();
final long advertiseConfig = in.readLong();
- return new AdvertisingRequest(serviceInfo, protocolType, advertiseConfig);
+ final long ttlSeconds = in.readLong();
+ final Duration ttl = ttlSeconds < 0 ? null : Duration.ofSeconds(ttlSeconds);
+ return new AdvertisingRequest(serviceInfo, protocolType, advertiseConfig, ttl);
}
@Override
@@ -60,6 +64,9 @@
// Bitmask of @AdvertisingConfig flags. Uses a long to allow 64 possible flags in the future.
private final long mAdvertisingConfig;
+ @Nullable
+ private final Duration mTtl;
+
/**
* @hide
*/
@@ -73,10 +80,11 @@
* The constructor for the advertiseRequest
*/
private AdvertisingRequest(@NonNull NsdServiceInfo serviceInfo, int protocolType,
- long advertisingConfig) {
+ long advertisingConfig, @NonNull Duration ttl) {
mServiceInfo = serviceInfo;
mProtocolType = protocolType;
mAdvertisingConfig = advertisingConfig;
+ mTtl = ttl;
}
/**
@@ -101,12 +109,25 @@
return mAdvertisingConfig;
}
+ /**
+ * Returns the time interval that the resource records may be cached on a DNS resolver or
+ * {@code null} if not specified.
+ *
+ * @hide
+ */
+ // @FlaggedApi(NsdManager.Flags.NSD_CUSTOM_TTL_ENABLED)
+ @Nullable
+ public Duration getTtl() {
+ return mTtl;
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("serviceInfo: ").append(mServiceInfo)
.append(", protocolType: ").append(mProtocolType)
- .append(", advertisingConfig: ").append(mAdvertisingConfig);
+ .append(", advertisingConfig: ").append(mAdvertisingConfig)
+ .append(", ttl: ").append(mTtl);
return sb.toString();
}
@@ -120,13 +141,14 @@
final AdvertisingRequest otherRequest = (AdvertisingRequest) other;
return mServiceInfo.equals(otherRequest.mServiceInfo)
&& mProtocolType == otherRequest.mProtocolType
- && mAdvertisingConfig == otherRequest.mAdvertisingConfig;
+ && mAdvertisingConfig == otherRequest.mAdvertisingConfig
+ && Objects.equals(mTtl, otherRequest.mTtl);
}
}
@Override
public int hashCode() {
- return Objects.hash(mServiceInfo, mProtocolType, mAdvertisingConfig);
+ return Objects.hash(mServiceInfo, mProtocolType, mAdvertisingConfig, mTtl);
}
@Override
@@ -139,6 +161,7 @@
dest.writeParcelable(mServiceInfo, flags);
dest.writeInt(mProtocolType);
dest.writeLong(mAdvertisingConfig);
+ dest.writeLong(mTtl == null ? -1 : mTtl.getSeconds());
}
// @FlaggedApi(NsdManager.Flags.ADVERTISE_REQUEST_API)
@@ -151,6 +174,8 @@
private final NsdServiceInfo mServiceInfo;
private final int mProtocolType;
private long mAdvertisingConfig;
+ @Nullable
+ private Duration mTtl;
/**
* Creates a new {@link Builder} object.
*/
@@ -170,11 +195,44 @@
return this;
}
+ /**
+ * Sets the time interval that the resource records may be cached on a DNS resolver.
+ *
+ * If this method is not called or {@code ttl} is {@code null}, default TTL values
+ * will be used for the service when it's registered. Otherwise, the {@code ttl}
+ * will be used for all resource records of this service.
+ *
+ * When registering a service, {@link NsdManager#FAILURE_BAD_PARAMETERS} will be returned
+ * if {@code ttl} is smaller than 30 seconds.
+ *
+ * Note: only number of seconds of {@code ttl} is used.
+ *
+ * @param ttl the maximum duration that the DNS resource records will be cached
+ *
+ * @see AdvertisingRequest#getTtl
+ * @hide
+ */
+ // @FlaggedApi(NsdManager.Flags.NSD_CUSTOM_TTL_ENABLED)
+ @NonNull
+ public Builder setTtl(@Nullable Duration ttl) {
+ if (ttl == null) {
+ mTtl = null;
+ return this;
+ }
+ final long ttlSeconds = ttl.getSeconds();
+ if (ttlSeconds < 0 || ttlSeconds > 0xffffffffL) {
+ throw new IllegalArgumentException(
+ "ttlSeconds exceeds the allowed range (value = " + ttlSeconds
+ + ", allowedRanged = [0, 0xffffffffL])");
+ }
+ mTtl = Duration.ofSeconds(ttlSeconds);
+ return this;
+ }
/** Creates a new {@link AdvertisingRequest} object. */
@NonNull
public AdvertisingRequest build() {
- return new AdvertisingRequest(mServiceInfo, mProtocolType, mAdvertisingConfig);
+ return new AdvertisingRequest(mServiceInfo, mProtocolType, mAdvertisingConfig, mTtl);
}
}
}
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index f6e1324..1001423 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -160,6 +160,8 @@
"com.android.net.flags.advertise_request_api";
static final String NSD_CUSTOM_HOSTNAME_ENABLED =
"com.android.net.flags.nsd_custom_hostname_enabled";
+ static final String NSD_CUSTOM_TTL_ENABLED =
+ "com.android.net.flags.nsd_custom_ttl_enabled";
}
/**
@@ -327,6 +329,20 @@
/** Dns based service discovery protocol */
public static final int PROTOCOL_DNS_SD = 0x0001;
+ /**
+ * The minimum TTL seconds which is allowed for a service registration.
+ *
+ * @hide
+ */
+ public static final long TTL_SECONDS_MIN = 30L;
+
+ /**
+ * The maximum TTL seconds which is allowed for a service registration.
+ *
+ * @hide
+ */
+ public static final long TTL_SECONDS_MAX = 10 * 3600L;
+
private static final SparseArray<String> EVENT_NAMES = new SparseArray<>();
static {
EVENT_NAMES.put(DISCOVER_SERVICES, "DISCOVER_SERVICES");
diff --git a/framework-t/src/android/net/nsd/NsdServiceInfo.java b/framework-t/src/android/net/nsd/NsdServiceInfo.java
index 146d4ca..f4cc2ac 100644
--- a/framework-t/src/android/net/nsd/NsdServiceInfo.java
+++ b/framework-t/src/android/net/nsd/NsdServiceInfo.java
@@ -16,8 +16,6 @@
package android.net.nsd;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -35,6 +33,7 @@
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -71,6 +70,10 @@
private int mInterfaceIndex;
+ // The timestamp that all resource records associated with this service are considered invalid.
+ @Nullable
+ private Instant mExpirationTime;
+
public NsdServiceInfo() {
mSubtypes = new ArraySet<>();
mTxtRecord = new ArrayMap<>();
@@ -99,6 +102,7 @@
mPort = other.getPort();
mNetwork = other.getNetwork();
mInterfaceIndex = other.getInterfaceIndex();
+ mExpirationTime = other.getExpirationTime();
}
/** Get the service name */
@@ -490,6 +494,38 @@
return Collections.unmodifiableSet(mSubtypes);
}
+ /**
+ * Sets the timestamp after when this service is expired.
+ *
+ * Note: only number of seconds of {@code expirationTime} is used.
+ *
+ * @hide
+ */
+ public void setExpirationTime(@Nullable Instant expirationTime) {
+ if (expirationTime == null) {
+ mExpirationTime = null;
+ } else {
+ mExpirationTime = Instant.ofEpochSecond(expirationTime.getEpochSecond());
+ }
+ }
+
+ /**
+ * Returns the timestamp after when this service is expired or {@code null} if it's unknown.
+ *
+ * A service is considered expired if any of its DNS record is expired.
+ *
+ * Clients that are depending on the refreshness of the service information should not continue
+ * use this service after the returned timestamp. Instead, clients may re-send queries for the
+ * service to get updated the service information.
+ *
+ * @hide
+ */
+ // @FlaggedApi(NsdManager.Flags.NSD_CUSTOM_TTL_ENABLED)
+ @Nullable
+ public Instant getExpirationTime() {
+ return mExpirationTime;
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@@ -499,7 +535,8 @@
.append(", hostAddresses: ").append(TextUtils.join(", ", mHostAddresses))
.append(", hostname: ").append(mHostname)
.append(", port: ").append(mPort)
- .append(", network: ").append(mNetwork);
+ .append(", network: ").append(mNetwork)
+ .append(", expirationTime: ").append(mExpirationTime);
byte[] txtRecord = getTxtRecord();
sb.append(", txtRecord: ").append(new String(txtRecord, StandardCharsets.UTF_8));
@@ -539,6 +576,7 @@
InetAddressUtils.parcelInetAddress(dest, address, flags);
}
dest.writeString(mHostname);
+ dest.writeLong(mExpirationTime != null ? mExpirationTime.getEpochSecond() : -1);
}
/** Implement the Parcelable interface */
@@ -569,6 +607,8 @@
info.mHostAddresses.add(InetAddressUtils.unparcelInetAddress(in));
}
info.mHostname = in.readString();
+ final long seconds = in.readLong();
+ info.setExpirationTime(seconds < 0 ? null : Instant.ofEpochSecond(seconds));
return info;
}
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index c045eaf..cfb1a33 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -28,6 +28,7 @@
import static android.net.nsd.NsdManager.RESOLVE_SERVICE_SUCCEEDED;
import static android.net.nsd.NsdManager.SUBTYPE_LABEL_REGEX;
import static android.net.nsd.NsdManager.TYPE_REGEX;
+import static android.os.Process.SYSTEM_UID;
import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
import static com.android.modules.utils.build.SdkLevel.isAtLeastU;
@@ -116,6 +117,7 @@
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -739,6 +741,33 @@
return new ArraySet<>(subtypeMap.values());
}
+ private boolean checkTtl(
+ @Nullable Duration ttl, @NonNull ClientInfo clientInfo) {
+ if (ttl == null) {
+ return true;
+ }
+
+ final long ttlSeconds = ttl.toSeconds();
+ final int uid = clientInfo.getUid();
+
+ // Allows Thread module in the system_server to register TTL that is smaller than
+ // 30 seconds
+ final long minTtlSeconds = uid == SYSTEM_UID ? 0 : NsdManager.TTL_SECONDS_MIN;
+
+ // Allows Thread module in the system_server to register TTL that is larger than
+ // 10 hours
+ final long maxTtlSeconds =
+ uid == SYSTEM_UID ? 0xffffffffL : NsdManager.TTL_SECONDS_MAX;
+
+ if (ttlSeconds < minTtlSeconds || ttlSeconds > maxTtlSeconds) {
+ mServiceLogs.e("ttlSeconds exceeds allowed range (value = "
+ + ttlSeconds + ", allowedRange = [" + minTtlSeconds
+ + ", " + maxTtlSeconds + " ])");
+ return false;
+ }
+ return true;
+ }
+
@Override
public boolean processMessage(Message msg) {
final ClientInfo clientInfo;
@@ -965,11 +994,19 @@
break;
}
+ if (!checkTtl(advertisingRequest.getTtl(), clientInfo)) {
+ clientInfo.onRegisterServiceFailedImmediately(clientRequestId,
+ NsdManager.FAILURE_BAD_PARAMETERS, false /* isLegacy */);
+ break;
+ }
+
serviceInfo.setSubtypes(subtypes);
maybeStartMonitoringSockets();
final MdnsAdvertisingOptions mdnsAdvertisingOptions =
- MdnsAdvertisingOptions.newBuilder().setIsOnlyUpdate(
- isUpdateOnly).build();
+ MdnsAdvertisingOptions.newBuilder()
+ .setIsOnlyUpdate(isUpdateOnly)
+ .setTtl(advertisingRequest.getTtl())
+ .build();
mAdvertiser.addOrUpdateService(transactionId, serviceInfo,
mdnsAdvertisingOptions, clientInfo.mUid);
storeAdvertiserRequestMap(clientRequestId, transactionId, clientInfo,
@@ -1512,6 +1549,7 @@
network == null ? INetd.LOCAL_NET_ID : network.netId,
serviceInfo.getInterfaceIndex());
servInfo.setSubtypes(dedupSubtypeLabels(serviceInfo.getSubtypes()));
+ servInfo.setExpirationTime(serviceInfo.getExpirationTime());
return servInfo;
}
@@ -2680,6 +2718,10 @@
return sb.toString();
}
+ public int getUid() {
+ return mUid;
+ }
+
private boolean isPreSClient() {
return mIsPreSClient;
}
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 0b60572..c162bcc 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -449,7 +449,8 @@
mPendingRegistrations.put(id, registration);
for (int i = 0; i < mAdvertisers.size(); i++) {
try {
- mAdvertisers.valueAt(i).addService(id, registration.getServiceInfo());
+ mAdvertisers.valueAt(i).addService(id, registration.getServiceInfo(),
+ registration.getAdvertisingOptions());
} catch (NameConflictException e) {
mSharedLog.wtf("Name conflict adding services that should have unique names",
e);
@@ -515,7 +516,7 @@
final Registration registration = mPendingRegistrations.valueAt(i);
try {
advertiser.addService(mPendingRegistrations.keyAt(i),
- registration.getServiceInfo());
+ registration.getServiceInfo(), registration.getAdvertisingOptions());
} catch (NameConflictException e) {
mSharedLog.wtf("Name conflict adding services that should have unique names",
e);
@@ -587,15 +588,17 @@
@NonNull
private NsdServiceInfo mServiceInfo;
final int mClientUid;
+ private final MdnsAdvertisingOptions mAdvertisingOptions;
int mConflictDuringProbingCount;
int mConflictAfterProbingCount;
-
- private Registration(@NonNull NsdServiceInfo serviceInfo, int clientUid) {
+ private Registration(@NonNull NsdServiceInfo serviceInfo, int clientUid,
+ @NonNull MdnsAdvertisingOptions advertisingOptions) {
this.mOriginalServiceName = serviceInfo.getServiceName();
this.mOriginalHostname = serviceInfo.getHostname();
this.mServiceInfo = serviceInfo;
this.mClientUid = clientUid;
+ this.mAdvertisingOptions = advertisingOptions;
}
/** Check if the new {@link NsdServiceInfo} doesn't update any data other than subtypes. */
@@ -697,6 +700,11 @@
public NsdServiceInfo getServiceInfo() {
return mServiceInfo;
}
+
+ @NonNull
+ public MdnsAdvertisingOptions getAdvertisingOptions() {
+ return mAdvertisingOptions;
+ }
}
/**
@@ -855,7 +863,7 @@
}
mSharedLog.i("Adding service " + service + " with ID " + id + " and subtypes "
+ subtypes + " advertisingOptions " + advertisingOptions);
- registration = new Registration(service, clientUid);
+ registration = new Registration(service, clientUid, advertisingOptions);
final BiPredicate<Network, InterfaceAdvertiserRequest> checkConflictFilter;
if (network == null) {
// If registering on all networks, no advertiser must have conflicts
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 e7a6ca7..a81d1e4 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertisingOptions.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertisingOptions.java
@@ -16,6 +16,11 @@
package com.android.server.connectivity.mdns;
+import android.annotation.Nullable;
+
+import java.time.Duration;
+import java.util.Objects;
+
/**
* API configuration parameters for advertising the mDNS service.
*
@@ -27,13 +32,15 @@
private static MdnsAdvertisingOptions sDefaultOptions;
private final boolean mIsOnlyUpdate;
+ @Nullable
+ private final Duration mTtl;
/**
* Parcelable constructs for a {@link MdnsAdvertisingOptions}.
*/
- MdnsAdvertisingOptions(
- boolean isOnlyUpdate) {
+ MdnsAdvertisingOptions(boolean isOnlyUpdate, @Nullable Duration ttl) {
this.mIsOnlyUpdate = isOnlyUpdate;
+ this.mTtl = ttl;
}
/**
@@ -60,9 +67,36 @@
return mIsOnlyUpdate;
}
+ /**
+ * Returns the TTL for all records in a service.
+ */
+ @Nullable
+ public Duration getTtl() {
+ return mTtl;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else if (!(other instanceof MdnsAdvertisingOptions)) {
+ return false;
+ } else {
+ final MdnsAdvertisingOptions otherOptions = (MdnsAdvertisingOptions) other;
+ return mIsOnlyUpdate == otherOptions.mIsOnlyUpdate
+ && Objects.equals(mTtl, otherOptions.mTtl);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIsOnlyUpdate, mTtl);
+ }
+
@Override
public String toString() {
- return "MdnsAdvertisingOptions{" + "mIsOnlyUpdate=" + mIsOnlyUpdate + '}';
+ return "MdnsAdvertisingOptions{" + "mIsOnlyUpdate=" + mIsOnlyUpdate + ", mTtl=" + mTtl
+ + '}';
}
/**
@@ -70,6 +104,8 @@
*/
public static final class Builder {
private boolean mIsOnlyUpdate = false;
+ @Nullable
+ private Duration mTtl;
private Builder() {
}
@@ -83,10 +119,18 @@
}
/**
+ * Sets the TTL duration for all records of the service.
+ */
+ public Builder setTtl(@Nullable Duration ttl) {
+ this.mTtl = ttl;
+ return this;
+ }
+
+ /**
* Builds a {@link MdnsAdvertisingOptions} with the arguments supplied to this builder.
*/
public MdnsAdvertisingOptions build() {
- return new MdnsAdvertisingOptions(mIsOnlyUpdate);
+ return new MdnsAdvertisingOptions(mIsOnlyUpdate, mTtl);
}
}
}
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 aa51c41..c2363c0 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -258,8 +258,10 @@
*
* @throws NameConflictException There is already a service being advertised with that name.
*/
- public void addService(int id, NsdServiceInfo service) throws NameConflictException {
- final int replacedExitingService = mRecordRepository.addService(id, service);
+ public void addService(int id, NsdServiceInfo service,
+ @NonNull MdnsAdvertisingOptions advertisingOptions) throws NameConflictException {
+ final int replacedExitingService =
+ mRecordRepository.addService(id, service, advertisingOptions.getTtl());
// Cancel announcements for the existing service. This only happens for exiting services
// (so cancelling exiting announcements), as per RecordRepository.addService.
if (replacedExitingService >= 0) {
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 ed0bde2..ac64c3a 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -45,6 +45,7 @@
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -75,9 +76,9 @@
// TTL for records with a host name as the resource record's name (e.g., A, AAAA, HINFO) or a
// host name contained within the resource record's rdata (e.g., SRV, reverse mapping PTR
// record)
- private static final long NAME_RECORDS_TTL_MILLIS = TimeUnit.SECONDS.toMillis(120);
+ private static final long DEFAULT_NAME_RECORDS_TTL_MILLIS = TimeUnit.SECONDS.toMillis(120);
// TTL for other records
- private static final long NON_NAME_RECORDS_TTL_MILLIS = TimeUnit.MINUTES.toMillis(75);
+ private static final long DEFAULT_NON_NAME_RECORDS_TTL_MILLIS = TimeUnit.MINUTES.toMillis(75);
// Top-level domain for link-local queries, as per RFC6762 3.
private static final String LOCAL_TLD = "local";
@@ -193,6 +194,9 @@
*/
private boolean isProbing;
+ @Nullable
+ private Duration ttl;
+
/**
* Create a ServiceRegistration with only update the subType.
*/
@@ -200,16 +204,32 @@
NsdServiceInfo newServiceInfo = new NsdServiceInfo(serviceInfo);
newServiceInfo.setSubtypes(newSubtypes);
return new ServiceRegistration(srvRecord.record.getServiceHost(), newServiceInfo,
- repliedServiceCount, sentPacketCount, exiting, isProbing);
+ repliedServiceCount, sentPacketCount, exiting, isProbing, ttl);
}
/**
* Create a ServiceRegistration for dns-sd service registration (RFC6763).
*/
ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo,
- int repliedServiceCount, int sentPacketCount, boolean exiting, boolean isProbing) {
+ int repliedServiceCount, int sentPacketCount, boolean exiting, boolean isProbing,
+ @Nullable Duration ttl) {
this.serviceInfo = serviceInfo;
+ final long nonNameRecordsTtlMillis;
+ final long nameRecordsTtlMillis;
+
+ // When custom TTL is specified, all records of the service will use the custom TTL.
+ // This is typically useful for SRP (Service Registration Protocol:
+ // https://datatracker.ietf.org/doc/html/draft-ietf-dnssd-srp-24) Advertising Proxy
+ // where all records in a single SRP are required the same TTL.
+ if (ttl != null) {
+ nonNameRecordsTtlMillis = ttl.toMillis();
+ nameRecordsTtlMillis = ttl.toMillis();
+ } else {
+ nonNameRecordsTtlMillis = DEFAULT_NON_NAME_RECORDS_TTL_MILLIS;
+ nameRecordsTtlMillis = DEFAULT_NAME_RECORDS_TTL_MILLIS;
+ }
+
final boolean hasService = !TextUtils.isEmpty(serviceInfo.getServiceType());
final boolean hasCustomHost = !TextUtils.isEmpty(serviceInfo.getHostname());
final String[] hostname =
@@ -229,7 +249,7 @@
serviceType,
0L /* receiptTimeMillis */,
false /* cacheFlush */,
- NON_NAME_RECORDS_TTL_MILLIS,
+ nonNameRecordsTtlMillis,
serviceName),
true /* sharedName */));
for (String subtype : serviceInfo.getSubtypes()) {
@@ -239,7 +259,7 @@
MdnsUtils.constructFullSubtype(serviceType, subtype),
0L /* receiptTimeMillis */,
false /* cacheFlush */,
- NON_NAME_RECORDS_TTL_MILLIS,
+ nonNameRecordsTtlMillis,
serviceName),
true /* sharedName */));
}
@@ -249,7 +269,7 @@
new MdnsServiceRecord(serviceName,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
- NAME_RECORDS_TTL_MILLIS,
+ nameRecordsTtlMillis,
0 /* servicePriority */, 0 /* serviceWeight */,
serviceInfo.getPort(),
hostname),
@@ -261,7 +281,7 @@
0L /* receiptTimeMillis */,
// Service name is verified unique after probing
true /* cacheFlush */,
- NON_NAME_RECORDS_TTL_MILLIS,
+ nonNameRecordsTtlMillis,
attrsToTextEntries(serviceInfo.getAttributes())),
false /* sharedName */);
@@ -275,7 +295,7 @@
DNS_SD_SERVICE_TYPE,
0L /* receiptTimeMillis */,
false /* cacheFlush */,
- NON_NAME_RECORDS_TTL_MILLIS,
+ nonNameRecordsTtlMillis,
serviceType),
true /* sharedName */));
} else {
@@ -292,7 +312,7 @@
new MdnsInetAddressRecord(hostname,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
- NAME_RECORDS_TTL_MILLIS,
+ nameRecordsTtlMillis,
address),
false /* sharedName */));
}
@@ -315,9 +335,9 @@
* @param serviceInfo Service to advertise
*/
ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo,
- int repliedServiceCount, int sentPacketCount) {
+ int repliedServiceCount, int sentPacketCount, @Nullable Duration ttl) {
this(deviceHostname, serviceInfo,repliedServiceCount, sentPacketCount,
- false /* exiting */, true /* isProbing */);
+ false /* exiting */, true /* isProbing */, ttl);
}
void setProbing(boolean probing) {
@@ -339,7 +359,7 @@
revDnsAddr,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
- NAME_RECORDS_TTL_MILLIS,
+ DEFAULT_NAME_RECORDS_TTL_MILLIS,
mDeviceHostname),
false /* sharedName */));
@@ -349,7 +369,7 @@
mDeviceHostname,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
- NAME_RECORDS_TTL_MILLIS,
+ DEFAULT_NAME_RECORDS_TTL_MILLIS,
addr.getAddress()),
false /* sharedName */));
}
@@ -378,11 +398,13 @@
* This may remove/replace any existing service that used the name added but is exiting.
* @param serviceId A unique service ID.
* @param serviceInfo Service info to add.
+ * @param ttl the TTL duration for all records of {@code serviceInfo} or {@code null}
* @return If the added service replaced another with a matching name (which was exiting), the
* ID of the replaced service.
* @throws NameConflictException There is already a (non-exiting) service using the name.
*/
- public int addService(int serviceId, NsdServiceInfo serviceInfo) throws NameConflictException {
+ public int addService(int serviceId, NsdServiceInfo serviceInfo, @Nullable Duration ttl)
+ throws NameConflictException {
if (mServices.contains(serviceId)) {
throw new IllegalArgumentException(
"Service ID must not be reused across registrations: " + serviceId);
@@ -397,7 +419,7 @@
final ServiceRegistration registration = new ServiceRegistration(
mDeviceHostname, serviceInfo, NO_PACKET /* repliedServiceCount */,
- NO_PACKET /* sentPacketCount */);
+ NO_PACKET /* sentPacketCount */, ttl);
mServices.put(serviceId, registration);
// Remove existing exiting service
@@ -776,7 +798,7 @@
true /* cacheFlush */,
// TODO: RFC6762 6.1: "In general, the TTL given for an NSEC record SHOULD
// be the same as the TTL that the record would have had, had it existed."
- NAME_RECORDS_TTL_MILLIS,
+ DEFAULT_NAME_RECORDS_TTL_MILLIS,
question.getName(),
new int[] { question.getType() });
additionalAnswerInfo.add(
@@ -1211,7 +1233,7 @@
if (existing == null) return null;
final ServiceRegistration newService = new ServiceRegistration(mDeviceHostname, newInfo,
- existing.repliedServiceCount, existing.sentPacketCount);
+ existing.repliedServiceCount, existing.sentPacketCount, existing.ttl);
mServices.put(serviceId, newService);
return makeProbingInfo(serviceId, newService);
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
index 78df6df..f60a95e 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
@@ -28,6 +28,7 @@
import com.android.net.module.util.ByteUtils;
import java.nio.charset.Charset;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -62,7 +63,8 @@
source.createStringArrayList(),
source.createTypedArrayList(TextEntry.CREATOR),
source.readInt(),
- source.readParcelable(null));
+ source.readParcelable(Network.class.getClassLoader()),
+ Instant.ofEpochSecond(source.readLong()));
}
@Override
@@ -89,6 +91,9 @@
@Nullable
private final Network network;
+ @NonNull
+ private final Instant expirationTime;
+
/** Constructs a {@link MdnsServiceInfo} object with default values. */
public MdnsServiceInfo(
String serviceInstanceName,
@@ -110,7 +115,8 @@
textStrings,
/* textEntries= */ null,
/* interfaceIndex= */ INTERFACE_INDEX_UNSPECIFIED,
- /* network= */ null);
+ /* network= */ null,
+ /* expirationTime= */ Instant.MAX);
}
/** Constructs a {@link MdnsServiceInfo} object with default values. */
@@ -135,7 +141,8 @@
textStrings,
textEntries,
/* interfaceIndex= */ INTERFACE_INDEX_UNSPECIFIED,
- /* network= */ null);
+ /* network= */ null,
+ /* expirationTime= */ Instant.MAX);
}
/**
@@ -165,7 +172,8 @@
textStrings,
textEntries,
interfaceIndex,
- /* network= */ null);
+ /* network= */ null,
+ /* expirationTime= */ Instant.MAX);
}
/**
@@ -184,7 +192,8 @@
@Nullable List<String> textStrings,
@Nullable List<TextEntry> textEntries,
int interfaceIndex,
- @Nullable Network network) {
+ @Nullable Network network,
+ @NonNull Instant expirationTime) {
this.serviceInstanceName = serviceInstanceName;
this.serviceType = serviceType;
this.subtypes = new ArrayList<>();
@@ -217,6 +226,7 @@
this.attributes = Collections.unmodifiableMap(attributes);
this.interfaceIndex = interfaceIndex;
this.network = network;
+ this.expirationTime = Instant.ofEpochSecond(expirationTime.getEpochSecond());
}
private static List<TextEntry> parseTextStrings(List<String> textStrings) {
@@ -314,6 +324,17 @@
}
/**
+ * Returns the timestamp after when this service is expired or {@code null} if the expiration
+ * time is unknown.
+ *
+ * A service is considered expired if any of its DNS record is expired.
+ */
+ @NonNull
+ public Instant getExpirationTime() {
+ return expirationTime;
+ }
+
+ /**
* Returns attribute value for {@code key} as a UTF-8 string. It's the caller who must make sure
* that the value of {@code key} is indeed a UTF-8 string. {@code null} will be returned if no
* attribute value exists for {@code key}.
@@ -364,6 +385,7 @@
out.writeTypedList(textEntries);
out.writeInt(interfaceIndex);
out.writeParcelable(network, 0);
+ out.writeLong(expirationTime.getEpochSecond());
}
@Override
@@ -377,7 +399,8 @@
+ ", interfaceIndex: " + interfaceIndex
+ ", network: " + network
+ ", textStrings: " + textStrings
- + ", textEntries: " + textEntries;
+ + ", textEntries: " + textEntries
+ + ", expirationTime: " + expirationTime;
}
@@ -496,4 +519,4 @@
out.writeByteArray(value);
}
}
-}
\ No newline at end of file
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index 33b5ea4..8f41b94 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -38,6 +38,7 @@
import java.io.PrintWriter;
import java.net.Inet4Address;
import java.net.Inet6Address;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -309,6 +310,7 @@
textStrings = response.getTextRecord().getStrings();
textEntries = response.getTextRecord().getEntries();
}
+ Instant now = Instant.now();
// TODO: Throw an error message if response doesn't have Inet6 or Inet4 address.
return new MdnsServiceInfo(
serviceInstanceName,
@@ -321,7 +323,8 @@
textStrings,
textEntries,
response.getInterfaceIndex(),
- response.getNetwork());
+ response.getNetwork(),
+ now.plusMillis(response.getMinRemainingTtl(now.toEpochMilli())));
}
/**
diff --git a/tests/unit/java/android/net/nsd/AdvertisingRequestTest.kt b/tests/unit/java/android/net/nsd/AdvertisingRequestTest.kt
new file mode 100644
index 0000000..332f2a3
--- /dev/null
+++ b/tests/unit/java/android/net/nsd/AdvertisingRequestTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.nsd
+
+import android.net.nsd.AdvertisingRequest.NSD_ADVERTISING_UPDATE_ONLY
+import android.net.nsd.NsdManager.PROTOCOL_DNS_SD
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.parcelingRoundTrip
+import java.time.Duration
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNull
+import org.junit.Test
+import org.junit.runner.RunWith
+
+// TODO: move this class to CTS tests when AdvertisingRequest is made public
+/** Unit tests for {@link AdvertisingRequest}. */
+@IgnoreUpTo(Build.VERSION_CODES.S_V2)
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@ConnectivityModuleTest
+class AdvertisingRequestTest {
+ @Test
+ fun testParcelingIsLossLess() {
+ val info = NsdServiceInfo().apply {
+ serviceType = "_ipp._tcp"
+ }
+ val beforeParcel = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD)
+ .setAdvertisingConfig(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)
+ }
+
+@Test
+fun testBuilder_setNullTtl_success() {
+ val info = NsdServiceInfo().apply {
+ serviceType = "_ipp._tcp"
+ }
+ val request = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD)
+ .setTtl(null)
+ .build()
+
+ assertNull(request.ttl)
+}
+
+ @Test
+ fun testBuilder_setPropertiesSuccess() {
+ val info = NsdServiceInfo().apply {
+ serviceType = "_ipp._tcp"
+ }
+ val request = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD)
+ .setAdvertisingConfig(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(Duration.ofSeconds(100L), request.ttl)
+ }
+
+ @Test
+ fun testEquality() {
+ val info = NsdServiceInfo().apply {
+ serviceType = "_ipp._tcp"
+ }
+ 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)
+ .setTtl(Duration.ofSeconds(120L))
+ .build()
+ val request4 = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD)
+ .setAdvertisingConfig(NSD_ADVERTISING_UPDATE_ONLY)
+ .setTtl(Duration.ofSeconds(120L))
+ .build()
+
+ assertEquals(request1, request2)
+ assertEquals(request3, request4)
+ assertNotEquals(request1, request3)
+ assertNotEquals(request2, request4)
+ }
+}
diff --git a/tests/unit/java/android/net/nsd/NsdManagerTest.java b/tests/unit/java/android/net/nsd/NsdManagerTest.java
index 951675c..76a649e 100644
--- a/tests/unit/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/unit/java/android/net/nsd/NsdManagerTest.java
@@ -23,6 +23,7 @@
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -54,6 +55,7 @@
import java.net.InetAddress;
import java.util.List;
+import java.time.Duration;
@DevSdkIgnoreRunner.MonitorThreadLeak
@RunWith(DevSdkIgnoreRunner.class)
@@ -224,6 +226,23 @@
verify(listener, timeout(mTimeoutMs).times(1)).onServiceRegistered(request);
}
+ @Test
+ public void testRegisterServiceWithCustomTtl() throws Exception {
+ final NsdManager manager = mManager;
+ final NsdServiceInfo info = new NsdServiceInfo("another_name2", "another_type2");
+ info.setPort(2203);
+ final AdvertisingRequest request = new AdvertisingRequest.Builder(info, PROTOCOL)
+ .setTtl(Duration.ofSeconds(30)).build();
+ final NsdManager.RegistrationListener listener = mock(
+ NsdManager.RegistrationListener.class);
+
+ manager.registerService(request, Runnable::run, listener);
+
+ AdvertisingRequest capturedRequest = getAdvertisingRequest(
+ req -> verify(mServiceConn).registerService(anyInt(), req.capture()));
+ assertEquals(request, capturedRequest);
+ }
+
private void doTestRegisterService() throws Exception {
NsdManager manager = mManager;
@@ -501,4 +520,12 @@
verifier.accept(captor);
return captor.getValue();
}
+
+ AdvertisingRequest getAdvertisingRequest(
+ ThrowingConsumer<ArgumentCaptor<AdvertisingRequest>> verifier) throws Exception {
+ final ArgumentCaptor<AdvertisingRequest> captor =
+ ArgumentCaptor.forClass(AdvertisingRequest.class);
+ verifier.accept(captor);
+ return captor.getValue();
+ }
}
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 624855e..881de56 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -83,6 +83,7 @@
import android.net.mdns.aidl.IMDnsEventListener;
import android.net.mdns.aidl.RegistrationInfo;
import android.net.mdns.aidl.ResolutionInfo;
+import android.net.nsd.AdvertisingRequest;
import android.net.nsd.INsdManagerCallback;
import android.net.nsd.INsdServiceConnector;
import android.net.nsd.MDnsManager;
@@ -101,6 +102,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
import android.util.Pair;
@@ -110,6 +112,7 @@
import com.android.metrics.NetworkNsdReportedMetrics;
import com.android.server.NsdService.Dependencies;
import com.android.server.connectivity.mdns.MdnsAdvertiser;
+import com.android.server.connectivity.mdns.MdnsAdvertisingOptions;
import com.android.server.connectivity.mdns.MdnsDiscoveryManager;
import com.android.server.connectivity.mdns.MdnsInterfaceSocket;
import com.android.server.connectivity.mdns.MdnsSearchOptions;
@@ -137,6 +140,8 @@
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.time.Duration;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
@@ -971,7 +976,8 @@
List.of() /* textStrings */,
List.of() /* textEntries */,
1234,
- network);
+ network,
+ Instant.MAX /* expirationTime */);
// Callbacks for query sent.
listener.onDiscoveryQuerySent(Collections.emptyList(), 1 /* transactionId */);
@@ -1001,7 +1007,8 @@
List.of() /* textStrings */,
List.of() /* textEntries */,
1234,
- network);
+ network,
+ Instant.MAX /* expirationTime */);
// Verify onServiceUpdated callback.
listener.onServiceUpdated(updatedServiceInfo);
@@ -1133,7 +1140,8 @@
List.of(), /* textStrings */
List.of(), /* textEntries */
1234, /* interfaceIndex */
- network);
+ network,
+ Instant.MAX /* expirationTime */);
// Verify onServiceNameDiscovered callback
listener.onServiceNameDiscovered(foundInfo, false /* isServiceFromCache */);
@@ -1154,7 +1162,8 @@
null, /* textStrings */
null, /* textEntries */
1234, /* interfaceIndex */
- network);
+ network,
+ Instant.MAX /* expirationTime */);
// Verify onServiceNameRemoved callback
listener.onServiceNameRemoved(removedInfo);
verify(discListener, timeout(TIMEOUT_MS)).onServiceLost(argThat(info ->
@@ -1276,7 +1285,8 @@
List.of(MdnsServiceInfo.TextEntry.fromBytes(new byte[]{
'k', 'e', 'y', '=', (byte) 0xFF, (byte) 0xFE})) /* textEntries */,
1234,
- network);
+ network,
+ Instant.ofEpochSecond(1000_000L) /* expirationTime */);
// Verify onServiceFound callback
doReturn(TEST_TIME_MS + 10L).when(mClock).elapsedRealtime();
@@ -1301,6 +1311,7 @@
assertTrue(info.getHostAddresses().stream().anyMatch(
address -> address.equals(parseNumericAddress("2001:db8::2"))));
assertEquals(network, info.getNetwork());
+ assertEquals(Instant.ofEpochSecond(1000_000L), info.getExpirationTime());
// Verify the listener has been unregistered.
verify(mDiscoveryManager, timeout(TIMEOUT_MS))
@@ -1518,6 +1529,82 @@
}
@Test
+ public void testAdvertiseCustomTtl_validTtl_success() {
+ runValidTtlAdvertisingTest(30L);
+ runValidTtlAdvertisingTest(10 * 3600L);
+ }
+
+ @Test
+ public void testAdvertiseCustomTtl_ttlSmallerThan30SecondsButClientIsSystemServer_success() {
+ when(mDeps.getCallingUid()).thenReturn(Process.SYSTEM_UID);
+
+ runValidTtlAdvertisingTest(29L);
+ }
+
+ @Test
+ public void testAdvertiseCustomTtl_ttlLargerThan10HoursButClientIsSystemServer_success() {
+ when(mDeps.getCallingUid()).thenReturn(Process.SYSTEM_UID);
+
+ runValidTtlAdvertisingTest(10 * 3600L + 1);
+ runValidTtlAdvertisingTest(0xffffffffL);
+ }
+
+ private void runValidTtlAdvertisingTest(long validTtlSeconds) {
+ setMdnsAdvertiserEnabled();
+
+ final NsdManager client = connectClient(mService);
+ final RegistrationListener regListener = mock(RegistrationListener.class);
+ final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
+ ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
+ verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any(), any());
+
+ final NsdServiceInfo regInfo = new NsdServiceInfo("Service custom TTL", SERVICE_TYPE);
+ regInfo.setPort(1234);
+ final AdvertisingRequest request =
+ new AdvertisingRequest.Builder(regInfo, NsdManager.PROTOCOL_DNS_SD)
+ .setTtl(Duration.ofSeconds(validTtlSeconds)).build();
+
+ client.registerService(request, Runnable::run, regListener);
+ waitForIdle();
+
+ final ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(Integer.class);
+ final MdnsAdvertisingOptions expectedAdverstingOptions =
+ MdnsAdvertisingOptions.newBuilder().setTtl(request.getTtl()).build();
+ verify(mAdvertiser).addOrUpdateService(idCaptor.capture(), any(),
+ eq(expectedAdverstingOptions), anyInt());
+
+ // Verify onServiceRegistered callback
+ final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
+ final int regId = idCaptor.getValue();
+ cb.onRegisterServiceSucceeded(regId, regInfo);
+
+ verify(regListener, timeout(TIMEOUT_MS)).onServiceRegistered(
+ argThat(info -> matches(info, new NsdServiceInfo(regInfo.getServiceName(), null))));
+ }
+
+ @Test
+ public void testAdvertiseCustomTtl_invalidTtl_FailsWithBadParameters() {
+ setMdnsAdvertiserEnabled();
+ final long invalidTtlSeconds = 29L;
+ final NsdManager client = connectClient(mService);
+ final RegistrationListener regListener = mock(RegistrationListener.class);
+ final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
+ ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
+ verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any(), any());
+
+ final NsdServiceInfo regInfo = new NsdServiceInfo("Service custom TTL", SERVICE_TYPE);
+ regInfo.setPort(1234);
+ final AdvertisingRequest request =
+ new AdvertisingRequest.Builder(regInfo, NsdManager.PROTOCOL_DNS_SD)
+ .setTtl(Duration.ofSeconds(invalidTtlSeconds)).build();
+ client.registerService(request, Runnable::run, regListener);
+ waitForIdle();
+
+ verify(regListener, timeout(TIMEOUT_MS))
+ .onRegistrationFailed(any(), eq(FAILURE_BAD_PARAMETERS));
+ }
+
+ @Test
public void testStopServiceResolutionWithMdnsDiscoveryManager() {
setMdnsDiscoveryManagerEnabled();
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 f753c93..b8ebf0f 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
@@ -38,6 +38,7 @@
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.waitForIdle
import java.net.NetworkInterface
+import java.time.Duration
import java.util.Objects
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
@@ -313,9 +314,9 @@
eq(thread.looper), any(), intAdvCbCaptor2.capture(), eq(TEST_HOSTNAME), any(), any()
)
verify(mockInterfaceAdvertiser1).addService(
- anyInt(), eq(ALL_NETWORKS_SERVICE_SUBTYPE))
+ anyInt(), eq(ALL_NETWORKS_SERVICE_SUBTYPE), any())
verify(mockInterfaceAdvertiser2).addService(
- anyInt(), eq(ALL_NETWORKS_SERVICE_SUBTYPE))
+ anyInt(), eq(ALL_NETWORKS_SERVICE_SUBTYPE), any())
doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
postSync { intAdvCbCaptor1.value.onServiceProbingSucceeded(
@@ -489,15 +490,15 @@
eq(thread.looper), any(), intAdvCbCaptor.capture(), eq(TEST_HOSTNAME), any(), any()
)
verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_1),
- argThat { it.matches(SERVICE_1) })
+ argThat { it.matches(SERVICE_1) }, any())
verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_2),
- argThat { it.matches(expectedRenamed) })
+ argThat { it.matches(expectedRenamed) }, any())
verify(mockInterfaceAdvertiser1).addService(eq(LONG_SERVICE_ID_1),
- argThat { it.matches(LONG_SERVICE_1) })
+ argThat { it.matches(LONG_SERVICE_1) }, any())
verify(mockInterfaceAdvertiser1).addService(eq(LONG_SERVICE_ID_2),
- argThat { it.matches(expectedLongRenamed) })
+ argThat { it.matches(expectedLongRenamed) }, any())
verify(mockInterfaceAdvertiser1).addService(eq(CASE_INSENSITIVE_TEST_SERVICE_ID),
- argThat { it.matches(expectedCaseInsensitiveRenamed) })
+ argThat { it.matches(expectedCaseInsensitiveRenamed) }, any())
doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
postSync { intAdvCbCaptor.value.onServiceProbingSucceeded(
@@ -532,7 +533,7 @@
postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_1, mockSocket1, listOf(TEST_LINKADDR)) }
verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_1),
- argThat { it.matches(ALL_NETWORKS_SERVICE) })
+ argThat { it.matches(ALL_NETWORKS_SERVICE) }, any())
val updateOptions = MdnsAdvertisingOptions.newBuilder().setIsOnlyUpdate(true).build()
@@ -554,7 +555,24 @@
// Newly created MdnsInterfaceAdvertiser will get addService() call.
postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_2, mockSocket2, listOf(TEST_LINKADDR2)) }
verify(mockInterfaceAdvertiser2).addService(eq(SERVICE_ID_1),
- argThat { it.matches(ALL_NETWORKS_SERVICE_SUBTYPE) })
+ argThat { it.matches(ALL_NETWORKS_SERVICE_SUBTYPE) }, any())
+ }
+
+ @Test
+ fun testAddOrUpdateService_customTtl_registeredSuccess() {
+ val advertiser = MdnsAdvertiser(
+ thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
+ val updateOptions =
+ MdnsAdvertisingOptions.newBuilder().setTtl(Duration.ofSeconds(30)).build()
+
+ postSync { advertiser.addOrUpdateService(SERVICE_ID_1, ALL_NETWORKS_SERVICE,
+ updateOptions, TEST_CLIENT_UID_1) }
+
+ val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
+ verify(socketProvider).requestSocket(eq(null), socketCbCaptor.capture())
+ val socketCb = socketCbCaptor.value
+ postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_1, mockSocket1, listOf(TEST_LINKADDR)) }
+ verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_1), any(), eq(updateOptions))
}
@Test
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
index 0637ad1..28608bb 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
@@ -132,7 +132,7 @@
knownServices.add(inv.getArgument(0))
-1
- }.`when`(repository).addService(anyInt(), any())
+ }.`when`(repository).addService(anyInt(), any(), any())
doAnswer { inv ->
knownServices.remove(inv.getArgument(0))
null
@@ -403,9 +403,10 @@
@Test
fun testReplaceExitingService() {
doReturn(TEST_SERVICE_ID_DUPLICATE).`when`(repository)
- .addService(eq(TEST_SERVICE_ID_DUPLICATE), any())
- advertiser.addService(TEST_SERVICE_ID_DUPLICATE, TEST_SERVICE_1_SUBTYPE)
- verify(repository).addService(eq(TEST_SERVICE_ID_DUPLICATE), any())
+ .addService(eq(TEST_SERVICE_ID_DUPLICATE), any(), any())
+ advertiser.addService(TEST_SERVICE_ID_DUPLICATE, TEST_SERVICE_1_SUBTYPE,
+ MdnsAdvertisingOptions.getDefaultOptions())
+ verify(repository).addService(eq(TEST_SERVICE_ID_DUPLICATE), any(), any())
verify(announcer).stop(TEST_SERVICE_ID_DUPLICATE)
verify(prober).startProbing(any())
}
@@ -413,7 +414,7 @@
@Test
fun testUpdateExistingService() {
doReturn(TEST_SERVICE_ID_DUPLICATE).`when`(repository)
- .addService(eq(TEST_SERVICE_ID_DUPLICATE), any())
+ .addService(eq(TEST_SERVICE_ID_DUPLICATE), any(), any())
val subTypes = setOf("_sub")
advertiser.updateService(TEST_SERVICE_ID_DUPLICATE, subTypes)
verify(repository).updateService(eq(TEST_SERVICE_ID_DUPLICATE), any())
@@ -427,8 +428,8 @@
doReturn(serviceId).`when`(testProbingInfo).serviceId
doReturn(testProbingInfo).`when`(repository).setServiceProbing(serviceId)
- advertiser.addService(serviceId, serviceInfo)
- verify(repository).addService(serviceId, serviceInfo)
+ advertiser.addService(serviceId, serviceInfo, MdnsAdvertisingOptions.getDefaultOptions())
+ verify(repository).addService(serviceId, serviceInfo, null /* ttl */)
verify(prober).startProbing(testProbingInfo)
// Simulate probing success: continues to announcing
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 fd8d98b..8d1dff6 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
@@ -145,7 +145,8 @@
fun testAddServiceAndProbe() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
assertEquals(0, repository.servicesCount)
- assertEquals(-1, repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1))
+ assertEquals(-1,
+ repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */))
assertEquals(1, repository.servicesCount)
val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
@@ -180,10 +181,10 @@
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
assertFailsWith(NameConflictException::class) {
- repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1)
+ repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1, null /* ttl */)
}
assertFailsWith(NameConflictException::class) {
- repository.addService(TEST_SERVICE_ID_3, TEST_SERVICE_3)
+ repository.addService(TEST_SERVICE_ID_3, TEST_SERVICE_3, null /* ttl */)
}
}
@@ -224,9 +225,9 @@
@Test
fun testInvalidReuseOfServiceId() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
- repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+ repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */)
assertFailsWith(IllegalArgumentException::class) {
- repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_2)
+ repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_2, null /* ttl */)
}
}
@@ -235,7 +236,7 @@
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
assertFalse(repository.hasActiveService(TEST_SERVICE_ID_1))
- repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+ repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */)
assertTrue(repository.hasActiveService(TEST_SERVICE_ID_1))
val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
@@ -327,7 +328,7 @@
repository.exitService(TEST_SERVICE_ID_1)
assertEquals(TEST_SERVICE_ID_1,
- repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1))
+ repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1, null /* ttl */))
assertEquals(1, repository.servicesCount)
repository.removeService(TEST_SERVICE_ID_2)
@@ -824,7 +825,7 @@
repository.initWithService(
TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, subtypes = setOf(),
listOf(LinkAddress(parseNumericAddress("192.0.2.111"), 24)))
- repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
+ repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
val src = InetSocketAddress(parseNumericAddress("fe80::123"), 5353)
val query = makeQuery(TYPE_AAAA to TEST_CUSTOM_HOST_1_NAME)
@@ -890,7 +891,8 @@
repository.initWithService(
TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, subtypes = setOf(),
listOf(LinkAddress(parseNumericAddress("192.0.2.111"), 24)))
- repository.addService(TEST_SERVICE_CUSTOM_HOST_ID_1, TEST_SERVICE_CUSTOM_HOST_1)
+ repository.addService(
+ TEST_SERVICE_CUSTOM_HOST_ID_1, TEST_SERVICE_CUSTOM_HOST_1, null /* ttl */)
repository.removeService(TEST_CUSTOM_HOST_ID_1)
repository.removeService(TEST_SERVICE_CUSTOM_HOST_ID_1)
@@ -989,8 +991,8 @@
@Test
fun testGetConflictingServices() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
- repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
- repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2)
+ repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */)
+ repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* ttl */)
val packet = MdnsPacket(
0 /* flags */,
@@ -1020,8 +1022,8 @@
@Test
fun testGetConflictingServicesCaseInsensitive() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
- repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
- repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2)
+ repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */)
+ repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* ttl */)
val packet = MdnsPacket(
0 /* flags */,
@@ -1050,8 +1052,8 @@
@Test
fun testGetConflictingServices_customHosts_differentAddresses() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
- repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1)
- repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
+ repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */)
+ repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
val packet = MdnsPacket(
0, /* flags */
@@ -1074,8 +1076,8 @@
@Test
fun testGetConflictingServices_customHosts_moreAddressesThanUs_conflict() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
- repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1)
- repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
+ repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */)
+ repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
val packet = MdnsPacket(
0, /* flags */
@@ -1101,8 +1103,8 @@
@Test
fun testGetConflictingServices_customHostsReplyHasFewerAddressesThanUs_noConflict() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
- repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1)
- repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
+ repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */)
+ repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
val packet = MdnsPacket(
0, /* flags */
@@ -1122,8 +1124,8 @@
@Test
fun testGetConflictingServices_customHostsReplyHasIdenticalHosts_noConflict() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
- repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1)
- repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
+ repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */)
+ repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
val packet = MdnsPacket(
0, /* flags */
@@ -1147,8 +1149,8 @@
@Test
fun testGetConflictingServices_customHostsCaseInsensitiveReplyHasIdenticalHosts_noConflict() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
- repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1)
- repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
+ repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */)
+ repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
val packet = MdnsPacket(
0, /* flags */
@@ -1171,8 +1173,8 @@
@Test
fun testGetConflictingServices_IdenticalService() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
- repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
- repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2)
+ repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */)
+ repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* ttl */)
val otherTtlMillis = 1234L
val packet = MdnsPacket(
@@ -1200,8 +1202,8 @@
@Test
fun testGetConflictingServicesCaseInsensitive_IdenticalService() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
- repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
- repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2)
+ repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */)
+ repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* ttl */)
val otherTtlMillis = 1234L
val packet = MdnsPacket(
@@ -1256,7 +1258,8 @@
makeFlags(includeInetAddressesInProbing = true))
repository.updateAddresses(TEST_ADDRESSES)
assertEquals(0, repository.servicesCount)
- assertEquals(-1, repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1))
+ assertEquals(-1,
+ repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */))
assertEquals(1, repository.servicesCount)
val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
@@ -1689,7 +1692,7 @@
serviceId: Int,
serviceInfo: NsdServiceInfo
): AnnouncementInfo {
- addService(serviceId, serviceInfo)
+ addService(serviceId, serviceInfo, null /* ttl */)
val probingInfo = setServiceProbing(serviceId)
assertNotNull(probingInfo)
return onProbingSucceeded(probingInfo)
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
index e7d7a98..8740e80 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
@@ -35,6 +35,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.time.Instant;
import java.util.List;
import java.util.Map;
@@ -202,7 +203,8 @@
List.of(),
/* textEntries= */ null,
/* interfaceIndex= */ 20,
- network);
+ network,
+ Instant.MAX /* expirationTime */);
assertEquals(network, info2.getNetwork());
}
@@ -225,7 +227,8 @@
MdnsServiceInfo.TextEntry.fromString("mn=Google Nest Hub Max"),
MdnsServiceInfo.TextEntry.fromString("test=")),
20 /* interfaceIndex */,
- new Network(123));
+ new Network(123),
+ Instant.MAX /* expirationTime */);
beforeParcel.writeToParcel(parcel, 0);
parcel.setDataPosition(0);