1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.connectivity.mdns; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.net.Network; 22 23 import com.android.internal.annotations.VisibleForTesting; 24 import com.android.server.connectivity.mdns.util.MdnsUtils; 25 26 import java.io.IOException; 27 import java.util.ArrayList; 28 import java.util.Collections; 29 import java.util.Iterator; 30 import java.util.LinkedList; 31 import java.util.List; 32 import java.util.Objects; 33 34 /** An mDNS response. */ 35 public class MdnsResponse { 36 public static final long EXPIRATION_NEVER = Long.MAX_VALUE; 37 private final List<MdnsRecord> records; 38 private final List<MdnsPointerRecord> pointerRecords; 39 private MdnsServiceRecord serviceRecord; 40 private MdnsTextRecord textRecord; 41 @NonNull private List<MdnsInetAddressRecord> inet4AddressRecords; 42 @NonNull private List<MdnsInetAddressRecord> inet6AddressRecords; 43 private long lastUpdateTime; 44 private final int interfaceIndex; 45 @Nullable private final Network network; 46 @NonNull private final String[] serviceName; 47 48 /** Constructs a new, empty response. */ MdnsResponse(long now, @NonNull String[] serviceName, int interfaceIndex, @Nullable Network network)49 public MdnsResponse(long now, @NonNull String[] serviceName, int interfaceIndex, 50 @Nullable Network network) { 51 lastUpdateTime = now; 52 records = new LinkedList<>(); 53 pointerRecords = new LinkedList<>(); 54 inet4AddressRecords = new ArrayList<>(); 55 inet6AddressRecords = new ArrayList<>(); 56 this.interfaceIndex = interfaceIndex; 57 this.network = network; 58 this.serviceName = serviceName; 59 } 60 MdnsResponse(@onNull MdnsResponse base)61 public MdnsResponse(@NonNull MdnsResponse base) { 62 records = new ArrayList<>(base.records); 63 pointerRecords = new ArrayList<>(base.pointerRecords); 64 serviceRecord = base.serviceRecord; 65 textRecord = base.textRecord; 66 inet4AddressRecords = new ArrayList<>(base.inet4AddressRecords); 67 inet6AddressRecords = new ArrayList<>(base.inet6AddressRecords); 68 lastUpdateTime = base.lastUpdateTime; 69 serviceName = base.serviceName; 70 interfaceIndex = base.interfaceIndex; 71 network = base.network; 72 } 73 74 /** 75 * Compare records for equality, including their TTL. 76 * 77 * MdnsRecord#equals ignores TTL and receiptTimeMillis, but methods in this class need to update 78 * records when the TTL changes (especially for goodbye announcements). 79 */ recordsAreSame(MdnsRecord a, MdnsRecord b)80 private boolean recordsAreSame(MdnsRecord a, MdnsRecord b) { 81 if (!Objects.equals(a, b)) return false; 82 return a == null || a.getTtl() == b.getTtl(); 83 } 84 addOrReplaceRecord(@onNull T record, @NonNull List<T> recordsList)85 private <T extends MdnsRecord> boolean addOrReplaceRecord(@NonNull T record, 86 @NonNull List<T> recordsList) { 87 final int existing = recordsList.indexOf(record); 88 boolean isSame = false; 89 if (existing >= 0) { 90 isSame = recordsAreSame(record, recordsList.get(existing)); 91 final MdnsRecord existedRecord = recordsList.remove(existing); 92 records.remove(existedRecord); 93 } 94 recordsList.add(record); 95 records.add(record); 96 return !isSame; 97 } 98 99 /** 100 * @return True if this response contains an identical (original TTL included) record. 101 */ hasIdenticalRecord(@onNull MdnsRecord record)102 public boolean hasIdenticalRecord(@NonNull MdnsRecord record) { 103 final int existing = records.indexOf(record); 104 return existing >= 0 && recordsAreSame(record, records.get(existing)); 105 } 106 107 /** 108 * Adds a pointer record. 109 * 110 * @return <code>true</code> if the record was added, or <code>false</code> if a matching 111 * pointer record is already present in the response with the same TTL. 112 */ addPointerRecord(MdnsPointerRecord pointerRecord)113 public synchronized boolean addPointerRecord(MdnsPointerRecord pointerRecord) { 114 if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase(serviceName, pointerRecord.getPointer())) { 115 throw new IllegalArgumentException( 116 "Pointer records for different service names cannot be added"); 117 } 118 return addOrReplaceRecord(pointerRecord, pointerRecords); 119 } 120 121 /** Gets the pointer records. */ getPointerRecords()122 public synchronized List<MdnsPointerRecord> getPointerRecords() { 123 // Returns a shallow copy. 124 return new LinkedList<>(pointerRecords); 125 } 126 hasPointerRecords()127 public synchronized boolean hasPointerRecords() { 128 return !pointerRecords.isEmpty(); 129 } 130 131 @VisibleForTesting clearPointerRecords()132 synchronized void clearPointerRecords() { 133 pointerRecords.clear(); 134 } 135 hasSubtypes()136 public synchronized boolean hasSubtypes() { 137 for (MdnsPointerRecord pointerRecord : pointerRecords) { 138 if (pointerRecord.hasSubtype()) { 139 return true; 140 } 141 } 142 return false; 143 } 144 145 @Nullable getSubtypes()146 public synchronized List<String> getSubtypes() { 147 List<String> subtypes = null; 148 for (MdnsPointerRecord pointerRecord : pointerRecords) { 149 String pointerRecordSubtype = pointerRecord.getSubtype(); 150 if (pointerRecordSubtype != null) { 151 if (subtypes == null) { 152 subtypes = new LinkedList<>(); 153 } 154 subtypes.add(pointerRecordSubtype); 155 } 156 } 157 158 return subtypes; 159 } 160 161 @VisibleForTesting removeSubtypes()162 public synchronized void removeSubtypes() { 163 Iterator<MdnsPointerRecord> iter = pointerRecords.iterator(); 164 while (iter.hasNext()) { 165 MdnsPointerRecord pointerRecord = iter.next(); 166 if (pointerRecord.hasSubtype()) { 167 iter.remove(); 168 } 169 } 170 } 171 172 /** Sets the service record. */ setServiceRecord(MdnsServiceRecord serviceRecord)173 public synchronized boolean setServiceRecord(MdnsServiceRecord serviceRecord) { 174 boolean isSame = recordsAreSame(this.serviceRecord, serviceRecord); 175 if (this.serviceRecord != null) { 176 records.remove(this.serviceRecord); 177 } 178 this.serviceRecord = serviceRecord; 179 if (this.serviceRecord != null) { 180 records.add(this.serviceRecord); 181 } 182 return !isSame; 183 } 184 185 /** Gets the service record. */ getServiceRecord()186 public synchronized MdnsServiceRecord getServiceRecord() { 187 return serviceRecord; 188 } 189 hasServiceRecord()190 public synchronized boolean hasServiceRecord() { 191 return serviceRecord != null; 192 } 193 194 /** Sets the text record. */ setTextRecord(MdnsTextRecord textRecord)195 public synchronized boolean setTextRecord(MdnsTextRecord textRecord) { 196 boolean isSame = recordsAreSame(this.textRecord, textRecord); 197 if (this.textRecord != null) { 198 records.remove(this.textRecord); 199 } 200 this.textRecord = textRecord; 201 if (this.textRecord != null) { 202 records.add(this.textRecord); 203 } 204 return !isSame; 205 } 206 207 /** Gets the text record. */ getTextRecord()208 public synchronized MdnsTextRecord getTextRecord() { 209 return textRecord; 210 } 211 hasTextRecord()212 public synchronized boolean hasTextRecord() { 213 return textRecord != null; 214 } 215 216 /** Add the IPv4 address record. */ addInet4AddressRecord( @onNull MdnsInetAddressRecord newInet4AddressRecord)217 public synchronized boolean addInet4AddressRecord( 218 @NonNull MdnsInetAddressRecord newInet4AddressRecord) { 219 return addOrReplaceRecord(newInet4AddressRecord, inet4AddressRecords); 220 } 221 222 /** Gets the IPv4 address records. */ 223 @NonNull getInet4AddressRecords()224 public synchronized List<MdnsInetAddressRecord> getInet4AddressRecords() { 225 return Collections.unmodifiableList(inet4AddressRecords); 226 } 227 228 /** Return the first IPv4 address record or null if no record. */ 229 @Nullable getInet4AddressRecord()230 public synchronized MdnsInetAddressRecord getInet4AddressRecord() { 231 return inet4AddressRecords.isEmpty() ? null : inet4AddressRecords.get(0); 232 } 233 234 /** Check whether response has IPv4 address record */ hasInet4AddressRecord()235 public synchronized boolean hasInet4AddressRecord() { 236 return !inet4AddressRecords.isEmpty(); 237 } 238 239 /** Clear all IPv4 address records */ clearInet4AddressRecords()240 synchronized void clearInet4AddressRecords() { 241 for (MdnsInetAddressRecord record : inet4AddressRecords) { 242 records.remove(record); 243 } 244 inet4AddressRecords.clear(); 245 } 246 247 /** Sets the IPv6 address records. */ addInet6AddressRecord( @onNull MdnsInetAddressRecord newInet6AddressRecord)248 public synchronized boolean addInet6AddressRecord( 249 @NonNull MdnsInetAddressRecord newInet6AddressRecord) { 250 return addOrReplaceRecord(newInet6AddressRecord, inet6AddressRecords); 251 } 252 253 /** 254 * Returns the index of the network interface at which this response was received. Can be set to 255 * {@link MdnsSocket#INTERFACE_INDEX_UNSPECIFIED} if unset. 256 */ getInterfaceIndex()257 public int getInterfaceIndex() { 258 return interfaceIndex; 259 } 260 261 /** 262 * Returns the network at which this response was received, or null if the network is unknown. 263 */ 264 @Nullable getNetwork()265 public Network getNetwork() { 266 return network; 267 } 268 269 /** Gets all IPv6 address records. */ getInet6AddressRecords()270 public synchronized List<MdnsInetAddressRecord> getInet6AddressRecords() { 271 return Collections.unmodifiableList(inet6AddressRecords); 272 } 273 274 /** Return the first IPv6 address record or null if no record. */ 275 @Nullable getInet6AddressRecord()276 public synchronized MdnsInetAddressRecord getInet6AddressRecord() { 277 return inet6AddressRecords.isEmpty() ? null : inet6AddressRecords.get(0); 278 } 279 280 /** Check whether response has IPv6 address record */ hasInet6AddressRecord()281 public synchronized boolean hasInet6AddressRecord() { 282 return !inet6AddressRecords.isEmpty(); 283 } 284 285 /** Clear all IPv6 address records */ clearInet6AddressRecords()286 synchronized void clearInet6AddressRecords() { 287 for (MdnsInetAddressRecord record : inet6AddressRecords) { 288 records.remove(record); 289 } 290 inet6AddressRecords.clear(); 291 } 292 293 /** Gets all of the records. */ getRecords()294 public synchronized List<MdnsRecord> getRecords() { 295 return new LinkedList<>(records); 296 } 297 298 /** 299 * Drop address records if they are for a hostname that does not match the service record. 300 * 301 * @return True if the records were dropped. 302 */ dropUnmatchedAddressRecords()303 public synchronized boolean dropUnmatchedAddressRecords() { 304 if (this.serviceRecord == null) return false; 305 boolean dropAddressRecords = false; 306 307 for (MdnsInetAddressRecord inetAddressRecord : getInet4AddressRecords()) { 308 if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase( 309 this.serviceRecord.getServiceHost(), inetAddressRecord.getName())) { 310 dropAddressRecords = true; 311 } 312 } 313 for (MdnsInetAddressRecord inetAddressRecord : getInet6AddressRecords()) { 314 if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase( 315 this.serviceRecord.getServiceHost(), inetAddressRecord.getName())) { 316 dropAddressRecords = true; 317 } 318 } 319 320 if (dropAddressRecords) { 321 clearInet4AddressRecords(); 322 clearInet6AddressRecords(); 323 return true; 324 } 325 return false; 326 } 327 328 /** 329 * Tests if the response is complete. A response is considered complete if it contains SRV, 330 * TXT, and A (for IPv4) or AAAA (for IPv6) records. The service type->name mapping is always 331 * known when constructing a MdnsResponse, so this may return true when there is no PTR record. 332 */ isComplete()333 public synchronized boolean isComplete() { 334 return (serviceRecord != null) 335 && (textRecord != null) 336 && (!inet4AddressRecords.isEmpty() || !inet6AddressRecords.isEmpty()); 337 } 338 339 /** 340 * Returns the key for this response. The key uniquely identifies the response by its service 341 * name. 342 */ 343 @Nullable getServiceInstanceName()344 public String getServiceInstanceName() { 345 return serviceName.length > 0 ? serviceName[0] : null; 346 } 347 348 @NonNull getServiceName()349 public String[] getServiceName() { 350 return serviceName; 351 } 352 353 /** Get the min remaining ttl time from received records */ getMinRemainingTtl(long now)354 public long getMinRemainingTtl(long now) { 355 long minRemainingTtl = EXPIRATION_NEVER; 356 // TODO: Check other records(A, AAAA, TXT) ttl time. 357 if (!hasServiceRecord()) { 358 return EXPIRATION_NEVER; 359 } 360 // Check ttl time. 361 long remainingTtl = serviceRecord.getRemainingTTL(now); 362 if (remainingTtl < minRemainingTtl) { 363 minRemainingTtl = remainingTtl; 364 } 365 return minRemainingTtl; 366 } 367 368 /** 369 * Tests if this response is a goodbye message. This will be true if a service record is present 370 * and any of the records have a TTL of 0. 371 */ isGoodbye()372 public synchronized boolean isGoodbye() { 373 if (getServiceInstanceName() != null) { 374 for (MdnsRecord record : records) { 375 // Expiring PTR records with subtypes just signal a change in known supported 376 // criteria, not the device itself going offline, so ignore those. 377 if ((record instanceof MdnsPointerRecord) 378 && ((MdnsPointerRecord) record).hasSubtype()) { 379 continue; 380 } 381 382 if (record.getTtl() == 0) { 383 return true; 384 } 385 } 386 } 387 return false; 388 } 389 390 /** 391 * Writes the response to a packet. 392 * 393 * @param writer The writer to use. 394 * @param now The current time. This is used to write updated TTLs that reflect the remaining 395 * TTL 396 * since the response was received. 397 * @return The number of records that were written. 398 * @throws IOException If an error occurred while writing (typically indicating overflow). 399 */ write(MdnsPacketWriter writer, long now)400 public synchronized int write(MdnsPacketWriter writer, long now) throws IOException { 401 int count = 0; 402 for (MdnsPointerRecord pointerRecord : pointerRecords) { 403 pointerRecord.write(writer, now); 404 ++count; 405 } 406 407 if (serviceRecord != null) { 408 serviceRecord.write(writer, now); 409 ++count; 410 } 411 412 if (textRecord != null) { 413 textRecord.write(writer, now); 414 ++count; 415 } 416 417 for (MdnsInetAddressRecord inetAddressRecord : inet4AddressRecords) { 418 inetAddressRecord.write(writer, now); 419 ++count; 420 } 421 422 for (MdnsInetAddressRecord inetAddressRecord : inet6AddressRecords) { 423 inetAddressRecord.write(writer, now); 424 ++count; 425 } 426 427 return count; 428 } 429 } 430