Implement proper subtype advertising
Implement subtype advertising by advertising an additional PTR record if
subtype advertising was requested.
For an advertiser a subtype is just an additional PTR record, so this
just involves plumbing the subtype down to MdnsRecordRepository, which
includes the additional record in the service registration.
Bug: 266167702
Test: atest
Change-Id: I09e780af25149162f16bd75410ddc50f160a0dab
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 9a2cc5f..4af4c6a 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -727,7 +727,7 @@
// service type would generate service instance names like
// Name._subtype._sub._type._tcp, which is incorrect
// (it should be Name._type._tcp).
- mAdvertiser.addService(id, serviceInfo);
+ mAdvertiser.addService(id, serviceInfo, typeSubtype.second);
storeAdvertiserRequestMap(clientId, id, clientInfo);
} else {
maybeStartDaemon();
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 655c364..cc08ea1 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -270,7 +270,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.getSubtype());
} catch (NameConflictException e) {
Log.wtf(TAG, "Name conflict adding services that should have unique names", e);
}
@@ -298,9 +299,10 @@
}
mAdvertisers.put(socket, advertiser);
for (int i = 0; i < mPendingRegistrations.size(); i++) {
+ final Registration registration = mPendingRegistrations.valueAt(i);
try {
advertiser.addService(mPendingRegistrations.keyAt(i),
- mPendingRegistrations.valueAt(i).getServiceInfo());
+ registration.getServiceInfo(), registration.getSubtype());
} catch (NameConflictException e) {
Log.wtf(TAG, "Name conflict adding services that should have unique names", e);
}
@@ -329,10 +331,13 @@
private int mConflictCount;
@NonNull
private NsdServiceInfo mServiceInfo;
+ @Nullable
+ private final String mSubtype;
- private Registration(@NonNull NsdServiceInfo serviceInfo) {
+ private Registration(@NonNull NsdServiceInfo serviceInfo, @Nullable String subtype) {
this.mOriginalName = serviceInfo.getServiceName();
this.mServiceInfo = serviceInfo;
+ this.mSubtype = subtype;
}
/**
@@ -387,6 +392,11 @@
public NsdServiceInfo getServiceInfo() {
return mServiceInfo;
}
+
+ @Nullable
+ public String getSubtype() {
+ return mSubtype;
+ }
}
/**
@@ -443,8 +453,9 @@
* Add a service to advertise.
* @param id A unique ID for the service.
* @param service The service info to advertise.
+ * @param subtype An optional subtype to advertise the service with.
*/
- public void addService(int id, NsdServiceInfo service) {
+ public void addService(int id, NsdServiceInfo service, @Nullable String subtype) {
checkThread();
if (mRegistrations.get(id) != null) {
Log.e(TAG, "Adding duplicate registration for " + service);
@@ -453,10 +464,10 @@
return;
}
- mSharedLog.i("Adding service " + service + " with ID " + id);
+ mSharedLog.i("Adding service " + service + " with ID " + id + " and subtype " + subtype);
final Network network = service.getNetwork();
- final Registration registration = new Registration(service);
+ final Registration registration = new Registration(service, subtype);
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/MdnsInterfaceAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
index 4e09515..724a704 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -212,8 +212,9 @@
*
* @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, @Nullable String subtype)
+ throws NameConflictException {
+ final int replacedExitingService = mRecordRepository.addService(id, service, subtype);
// 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 1329172..f756459 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -69,6 +69,8 @@
// Top-level domain for link-local queries, as per RFC6762 3.
private static final String LOCAL_TLD = "local";
+ // Subtype separator as per RFC6763 7.1 (_printer._sub._http._tcp.local)
+ private static final String SUBTYPE_SEPARATOR = "_sub";
// Service type for service enumeration (RFC6763 9.)
private static final String[] DNS_SD_SERVICE_TYPE =
@@ -156,13 +158,15 @@
@NonNull
public final List<RecordInfo<?>> allRecords;
@NonNull
- public final RecordInfo<MdnsPointerRecord> ptrRecord;
+ public final List<RecordInfo<MdnsPointerRecord>> ptrRecords;
@NonNull
public final RecordInfo<MdnsServiceRecord> srvRecord;
@NonNull
public final RecordInfo<MdnsTextRecord> txtRecord;
@NonNull
public final NsdServiceInfo serviceInfo;
+ @Nullable
+ public final String subtype;
/**
* Whether the service is sending exit announcements and will be destroyed soon.
@@ -175,14 +179,16 @@
* @param deviceHostname Hostname of the device (for the interface used)
* @param serviceInfo Service to advertise
*/
- ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo) {
+ ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo,
+ @Nullable String subtype) {
this.serviceInfo = serviceInfo;
+ this.subtype = subtype;
final String[] serviceType = splitServiceType(serviceInfo);
final String[] serviceName = splitFullyQualifiedName(serviceInfo, serviceType);
// Service PTR record
- ptrRecord = new RecordInfo<>(
+ final RecordInfo<MdnsPointerRecord> ptrRecord = new RecordInfo<>(
serviceInfo,
new MdnsPointerRecord(
serviceType,
@@ -192,6 +198,26 @@
serviceName),
true /* sharedName */, true /* probing */);
+ if (subtype == null) {
+ this.ptrRecords = Collections.singletonList(ptrRecord);
+ } else {
+ final String[] subtypeName = new String[serviceType.length + 2];
+ System.arraycopy(serviceType, 0, subtypeName, 2, serviceType.length);
+ subtypeName[0] = subtype;
+ subtypeName[1] = SUBTYPE_SEPARATOR;
+ final RecordInfo<MdnsPointerRecord> subtypeRecord = new RecordInfo<>(
+ serviceInfo,
+ new MdnsPointerRecord(
+ subtypeName,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ NON_NAME_RECORDS_TTL_MILLIS,
+ serviceName),
+ true /* sharedName */, true /* probing */);
+
+ this.ptrRecords = List.of(ptrRecord, subtypeRecord);
+ }
+
srvRecord = new RecordInfo<>(
serviceInfo,
new MdnsServiceRecord(serviceName,
@@ -211,8 +237,8 @@
attrsToTextEntries(serviceInfo.getAttributes())),
false /* sharedName */, true /* probing */);
- final ArrayList<RecordInfo<?>> allRecords = new ArrayList<>(4);
- allRecords.add(ptrRecord);
+ final ArrayList<RecordInfo<?>> allRecords = new ArrayList<>(5);
+ allRecords.addAll(ptrRecords);
allRecords.add(srvRecord);
allRecords.add(txtRecord);
// Service type enumeration record (RFC6763 9.)
@@ -275,7 +301,8 @@
* 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 String subtype)
+ throws NameConflictException {
if (mServices.contains(serviceId)) {
throw new IllegalArgumentException(
"Service ID must not be reused across registrations: " + serviceId);
@@ -288,7 +315,7 @@
}
final ServiceRegistration registration = new ServiceRegistration(
- mDeviceHostname, serviceInfo);
+ mDeviceHostname, serviceInfo, subtype);
mServices.put(serviceId, registration);
// Remove existing exiting service
@@ -344,24 +371,25 @@
if (registration == null) return null;
if (registration.exiting) return null;
- // Send exit (TTL 0) for the PTR record, if the record was sent (in particular don't send
+ // Send exit (TTL 0) for the PTR records, if at least one was sent (in particular don't send
// if still probing)
- if (registration.ptrRecord.lastSentTimeMs == 0L) {
+ if (CollectionUtils.all(registration.ptrRecords, r -> r.lastSentTimeMs == 0L)) {
return null;
}
registration.exiting = true;
- final MdnsPointerRecord expiredRecord = new MdnsPointerRecord(
- registration.ptrRecord.record.getName(),
- 0L /* receiptTimeMillis */,
- true /* cacheFlush */,
- 0L /* ttlMillis */,
- registration.ptrRecord.record.getPointer());
+ final List<MdnsRecord> expiredRecords = CollectionUtils.map(registration.ptrRecords,
+ r -> new MdnsPointerRecord(
+ r.record.getName(),
+ 0L /* receiptTimeMillis */,
+ true /* cacheFlush */,
+ 0L /* ttlMillis */,
+ r.record.getPointer()));
// Exit should be skipped if the record is still advertised by another service, but that
// would be a conflict (2 service registrations with the same service name), so it would
// not have been allowed by the repository.
- return new MdnsAnnouncer.ExitAnnouncementInfo(id, Collections.singletonList(expiredRecord));
+ return new MdnsAnnouncer.ExitAnnouncementInfo(id, expiredRecords);
}
public void removeService(int id) {
@@ -442,7 +470,7 @@
for (int i = 0; i < mServices.size(); i++) {
final ServiceRegistration registration = mServices.valueAt(i);
if (registration.exiting) continue;
- addReplyFromService(question, registration.allRecords, registration.ptrRecord,
+ addReplyFromService(question, registration.allRecords, registration.ptrRecords,
registration.srvRecord, registration.txtRecord, replyUnicast, now,
answerInfo, additionalAnswerRecords);
}
@@ -499,7 +527,7 @@
*/
private void addReplyFromService(@NonNull MdnsRecord question,
@NonNull List<RecordInfo<?>> serviceRecords,
- @Nullable RecordInfo<MdnsPointerRecord> servicePtrRecord,
+ @Nullable List<RecordInfo<MdnsPointerRecord>> servicePtrRecords,
@Nullable RecordInfo<MdnsServiceRecord> serviceSrvRecord,
@Nullable RecordInfo<MdnsTextRecord> serviceTxtRecord,
boolean replyUnicast, long now, @NonNull List<RecordInfo<?>> answerInfo,
@@ -531,7 +559,8 @@
}
hasKnownAnswer = true;
- hasDnsSdPtrRecordAnswer |= (info == servicePtrRecord);
+ hasDnsSdPtrRecordAnswer |= (servicePtrRecords != null
+ && CollectionUtils.any(servicePtrRecords, r -> info == r));
hasDnsSdSrvRecordAnswer |= (info == serviceSrvRecord);
// TODO: responses to probe queries should bypass this check and only ensure the
@@ -791,10 +820,11 @@
*/
@Nullable
public MdnsProber.ProbingInfo renameServiceForConflict(int serviceId, NsdServiceInfo newInfo) {
- if (!mServices.contains(serviceId)) return null;
+ final ServiceRegistration existing = mServices.get(serviceId);
+ if (existing == null) return null;
final ServiceRegistration newService = new ServiceRegistration(
- mDeviceHostname, newInfo);
+ mDeviceHostname, newInfo, existing.subtype);
mServices.put(serviceId, newService);
return makeProbingInfo(serviceId, newService.srvRecord.record);
}