diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsAnyRecord.java b/service/mdns/com/android/server/connectivity/mdns/MdnsAnyRecord.java new file mode 100644 index 0000000000..fcfe9f7fad --- /dev/null +++ b/service/mdns/com/android/server/connectivity/mdns/MdnsAnyRecord.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 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 com.android.server.connectivity.mdns; + +import android.net.DnsResolver; + +import java.io.IOException; + +/** + * A mDNS "ANY" record, used in mDNS questions to query for any record type. + */ +public class MdnsAnyRecord extends MdnsRecord { + + protected MdnsAnyRecord(String[] name, MdnsPacketReader reader) throws IOException { + super(name, TYPE_ANY, reader, true /* isQuestion */); + } + + protected MdnsAnyRecord(String[] name, boolean unicast) { + super(name, TYPE_ANY, DnsResolver.CLASS_IN /* cls */, + 0L /* receiptTimeMillis */, unicast /* cacheFlush */, 0L /* ttlMillis */); + } + + @Override + protected void readData(MdnsPacketReader reader) throws IOException { + // No data to read + } + + @Override + protected void writeData(MdnsPacketWriter writer) throws IOException { + // No data to write + } +} diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java b/service/mdns/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java index 47ac20ea2e..dd8a526c2a 100644 --- a/service/mdns/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java +++ b/service/mdns/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java @@ -43,7 +43,32 @@ public class MdnsInetAddressRecord extends MdnsRecord { */ public MdnsInetAddressRecord(String[] name, int type, MdnsPacketReader reader) throws IOException { - super(name, type, reader); + this(name, type, reader, false); + } + + /** + * Constructs the {@link MdnsRecord} + * + * @param name the service host name + * @param type the type of record (either Type 'AAAA' or Type 'A') + * @param reader the reader to read the record from. + * @param isQuestion whether the record is in the question section + */ + public MdnsInetAddressRecord(String[] name, int type, MdnsPacketReader reader, + boolean isQuestion) + throws IOException { + super(name, type, reader, isQuestion); + } + + public MdnsInetAddressRecord(String[] name, long receiptTimeMillis, boolean cacheFlush, + long ttlMillis, InetAddress address) { + super(name, address instanceof Inet4Address ? TYPE_A : TYPE_AAAA, + MdnsConstants.QCLASS_INTERNET, receiptTimeMillis, cacheFlush, ttlMillis); + if (address instanceof Inet4Address) { + inet4Address = (Inet4Address) address; + } else { + inet6Address = (Inet6Address) address; + } } /** Returns the IPv6 address. */ @@ -127,4 +152,4 @@ public class MdnsInetAddressRecord extends MdnsRecord { && Objects.equals(inet4Address, ((MdnsInetAddressRecord) other).inet4Address) && Objects.equals(inet6Address, ((MdnsInetAddressRecord) other).inet6Address); } -} \ No newline at end of file +} diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsPointerRecord.java b/service/mdns/com/android/server/connectivity/mdns/MdnsPointerRecord.java index 9641a406ea..2c7b26bc93 100644 --- a/service/mdns/com/android/server/connectivity/mdns/MdnsPointerRecord.java +++ b/service/mdns/com/android/server/connectivity/mdns/MdnsPointerRecord.java @@ -29,7 +29,19 @@ public class MdnsPointerRecord extends MdnsRecord { private String[] pointer; public MdnsPointerRecord(String[] name, MdnsPacketReader reader) throws IOException { - super(name, TYPE_PTR, reader); + this(name, reader, false); + } + + public MdnsPointerRecord(String[] name, MdnsPacketReader reader, boolean isQuestion) + throws IOException { + super(name, TYPE_PTR, reader, isQuestion); + } + + public MdnsPointerRecord(String[] name, long receiptTimeMillis, boolean cacheFlush, + long ttlMillis, String[] pointer) { + super(name, TYPE_PTR, MdnsConstants.QCLASS_INTERNET, receiptTimeMillis, cacheFlush, + ttlMillis); + this.pointer = pointer; } /** Returns the pointer as an array of labels. */ diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsRecord.java b/service/mdns/com/android/server/connectivity/mdns/MdnsRecord.java index 35f6da1f2f..c0481a4691 100644 --- a/service/mdns/com/android/server/connectivity/mdns/MdnsRecord.java +++ b/service/mdns/com/android/server/connectivity/mdns/MdnsRecord.java @@ -39,6 +39,11 @@ public abstract class MdnsRecord { public static final int TYPE_PTR = 0x000C; public static final int TYPE_SRV = 0x0021; public static final int TYPE_TXT = 0x0010; + public static final int TYPE_ANY = 0x00ff; + + private static final int FLAG_CACHE_FLUSH = 0x8000; + + public static final long RECEIPT_TIME_NOT_SENT = 0L; /** Status indicating that the record is current. */ public static final int STATUS_OK = 0; @@ -54,6 +59,33 @@ public abstract class MdnsRecord { private final long ttlMillis; private Object key; + /** + * Constructs a new record with the given name and type. + * + * @param reader The reader to read the record from. + * @param isQuestion Whether the record was included in the questions part of the message. + * @throws IOException If an error occurs while reading the packet. + */ + protected MdnsRecord(String[] name, int type, MdnsPacketReader reader, boolean isQuestion) + throws IOException { + this.name = name; + this.type = type; + cls = reader.readUInt16(); + receiptTimeMillis = SystemClock.elapsedRealtime(); + + if (isQuestion) { + // Questions do not have TTL or data + ttlMillis = 0L; + } else { + ttlMillis = SECONDS.toMillis(reader.readUInt32()); + int dataLength = reader.readUInt16(); + + reader.setLimit(dataLength); + readData(reader); + reader.clearLimit(); + } + } + /** * Constructs a new record with the given name and type. * @@ -64,17 +96,19 @@ public abstract class MdnsRecord { // receiver. @SuppressWarnings("nullness:method.invocation.invalid") protected MdnsRecord(String[] name, int type, MdnsPacketReader reader) throws IOException { + this(name, type, reader, false); + } + + /** + * Constructs a new record with the given properties. + */ + protected MdnsRecord(String[] name, int type, int cls, long receiptTimeMillis, + boolean cacheFlush, long ttlMillis) { this.name = name; this.type = type; - cls = reader.readUInt16(); - ttlMillis = SECONDS.toMillis(reader.readUInt32()); - int dataLength = reader.readUInt16(); - - receiptTimeMillis = SystemClock.elapsedRealtime(); - - reader.setLimit(dataLength); - readData(reader); - reader.clearLimit(); + this.cls = cls | (cacheFlush ? FLAG_CACHE_FLUSH : 0); + this.receiptTimeMillis = receiptTimeMillis; + this.ttlMillis = ttlMillis; } /** @@ -126,13 +160,29 @@ public abstract class MdnsRecord { return type; } + /** Return the record's class. */ + public final int getRecordClass() { + return cls & ~FLAG_CACHE_FLUSH; + } + + /** Return whether the cache flush flag is set. */ + public final boolean getCacheFlush() { + return (cls & FLAG_CACHE_FLUSH) != 0; + } + /** * Returns the record's remaining TTL. * + * If the record was not sent yet (receipt time {@link #RECEIPT_TIME_NOT_SENT}), this is the + * original TTL of the record. * @param now The current system time. * @return The remaning TTL, in milliseconds. */ public long getRemainingTTL(final long now) { + if (receiptTimeMillis == RECEIPT_TIME_NOT_SENT) { + return ttlMillis; + } + long age = now - receiptTimeMillis; if (age > ttlMillis) { return 0; @@ -187,6 +237,9 @@ public abstract class MdnsRecord { /** Gets the status of the record. */ public int getStatus(final long now) { + if (receiptTimeMillis == RECEIPT_TIME_NOT_SENT) { + return STATUS_OK; + } final long age = now - receiptTimeMillis; if (age > ttlMillis) { return STATUS_EXPIRED; diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceRecord.java b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceRecord.java index c52d25e45a..ebd8b77baa 100644 --- a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceRecord.java +++ b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceRecord.java @@ -39,7 +39,23 @@ public class MdnsServiceRecord extends MdnsRecord { private String[] serviceHost; public MdnsServiceRecord(String[] name, MdnsPacketReader reader) throws IOException { - super(name, TYPE_SRV, reader); + this(name, reader, false); + } + + public MdnsServiceRecord(String[] name, MdnsPacketReader reader, boolean isQuestion) + throws IOException { + super(name, TYPE_SRV, reader, isQuestion); + } + + public MdnsServiceRecord(String[] name, long receiptTimeMillis, boolean cacheFlush, + long ttlMillis, int servicePriority, int serviceWeight, int servicePort, + String[] serviceHost) { + super(name, TYPE_SRV, MdnsConstants.QCLASS_INTERNET, receiptTimeMillis, cacheFlush, + ttlMillis); + this.servicePriority = servicePriority; + this.serviceWeight = serviceWeight; + this.servicePort = servicePort; + this.serviceHost = serviceHost; } /** Returns the service's port number. */ diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsTextRecord.java b/service/mdns/com/android/server/connectivity/mdns/MdnsTextRecord.java index 1e66589b9b..4149dbee59 100644 --- a/service/mdns/com/android/server/connectivity/mdns/MdnsTextRecord.java +++ b/service/mdns/com/android/server/connectivity/mdns/MdnsTextRecord.java @@ -33,7 +33,19 @@ public class MdnsTextRecord extends MdnsRecord { private List entries; public MdnsTextRecord(String[] name, MdnsPacketReader reader) throws IOException { - super(name, TYPE_TXT, reader); + this(name, reader, false); + } + + public MdnsTextRecord(String[] name, MdnsPacketReader reader, boolean isQuestion) + throws IOException { + super(name, TYPE_TXT, reader, isQuestion); + } + + public MdnsTextRecord(String[] name, long receiptTimeMillis, boolean cacheFlush, long ttlMillis, + List entries) { + super(name, TYPE_TXT, MdnsConstants.QCLASS_INTERNET, receiptTimeMillis, cacheFlush, + ttlMillis); + this.entries = entries; } /** Returns the list of strings. */ diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTests.java index 9fc46749ca..9746a35dbb 100644 --- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTests.java +++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTests.java @@ -79,14 +79,7 @@ public class MdnsRecordTests { Inet4Address addr = record.getInet4Address(); assertEquals("/10.1.2.3", addr.toString()); - // Encode - MdnsPacketWriter writer = new MdnsPacketWriter(MAX_PACKET_SIZE); - record.write(writer, record.getReceiptTime()); - - packet = writer.getPacket(MULTICAST_IPV4_ADDRESS); - byte[] dataOut = packet.getData(); - - String dataOutText = HexDump.dumpHexString(dataOut, 0, packet.getLength()); + String dataOutText = toHex(record); Log.d(TAG, dataOutText); assertEquals(dataInText, dataOutText); @@ -123,14 +116,7 @@ public class MdnsRecordTests { Inet6Address addr = record.getInet6Address(); assertEquals("/aabb:ccdd:1122:3344:a0b0:c0d0:1020:3040", addr.toString()); - // Encode - MdnsPacketWriter writer = new MdnsPacketWriter(MAX_PACKET_SIZE); - record.write(writer, record.getReceiptTime()); - - packet = writer.getPacket(MULTICAST_IPV6_ADDRESS); - byte[] dataOut = packet.getData(); - - String dataOutText = HexDump.dumpHexString(dataOut, 0, packet.getLength()); + String dataOutText = toHex(record); Log.d(TAG, dataOutText); assertEquals(dataInText, dataOutText); @@ -167,14 +153,7 @@ public class MdnsRecordTests { Inet4Address addr = record.getInet4Address(); assertEquals("/16.32.48.64", addr.toString()); - // Encode - MdnsPacketWriter writer = new MdnsPacketWriter(MAX_PACKET_SIZE); - record.write(writer, record.getReceiptTime()); - - packet = writer.getPacket(MULTICAST_IPV4_ADDRESS); - byte[] dataOut = packet.getData(); - - String dataOutText = HexDump.dumpHexString(dataOut, 0, packet.getLength()); + String dataOutText = toHex(record); Log.d(TAG, dataOutText); final byte[] expectedDataIn = @@ -215,14 +194,7 @@ public class MdnsRecordTests { assertFalse(record.hasSubtype()); assertNull(record.getSubtype()); - // Encode - MdnsPacketWriter writer = new MdnsPacketWriter(MAX_PACKET_SIZE); - record.write(writer, record.getReceiptTime()); - - packet = writer.getPacket(MULTICAST_IPV4_ADDRESS); - byte[] dataOut = packet.getData(); - - String dataOutText = HexDump.dumpHexString(dataOut, 0, packet.getLength()); + String dataOutText = toHex(record); Log.d(TAG, dataOutText); assertEquals(dataInText, dataOutText); @@ -263,14 +235,35 @@ public class MdnsRecordTests { assertEquals(1, record.getServicePriority()); assertEquals(255, record.getServiceWeight()); - // Encode - MdnsPacketWriter writer = new MdnsPacketWriter(MAX_PACKET_SIZE); - record.write(writer, record.getReceiptTime()); + String dataOutText = toHex(record); + Log.d(TAG, dataOutText); - packet = writer.getPacket(MULTICAST_IPV4_ADDRESS); - byte[] dataOut = packet.getData(); + assertEquals(dataInText, dataOutText); + } - String dataOutText = HexDump.dumpHexString(dataOut, 0, packet.getLength()); + @Test + public void testAnyRecord() throws IOException { + final byte[] dataIn = HexDump.hexStringToByteArray( + "047465737407616E64726F696403636F6D0000FF0001000000000000"); + assertNotNull(dataIn); + String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length); + + // Decode + DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length); + MdnsPacketReader reader = new MdnsPacketReader(packet); + + String[] name = reader.readLabels(); + assertNotNull(name); + assertEquals(3, name.length); + String fqdn = MdnsRecord.labelsToString(name); + assertEquals("test.android.com", fqdn); + + int type = reader.readUInt16(); + assertEquals(MdnsRecord.TYPE_ANY, type); + + MdnsAnyRecord record = new MdnsAnyRecord(name, reader); + + String dataOutText = toHex(record); Log.d(TAG, dataOutText); assertEquals(dataInText, dataOutText); @@ -320,19 +313,23 @@ public class MdnsRecordTests { assertEquals(new TextEntry("b", "1234567890"), entries.get(1)); assertEquals(new TextEntry("xyz", "!@#$"), entries.get(2)); - // Encode - MdnsPacketWriter writer = new MdnsPacketWriter(MAX_PACKET_SIZE); - record.write(writer, record.getReceiptTime()); - - packet = writer.getPacket(MULTICAST_IPV4_ADDRESS); - byte[] dataOut = packet.getData(); - - String dataOutText = HexDump.dumpHexString(dataOut, 0, packet.getLength()); + String dataOutText = toHex(record); Log.d(TAG, dataOutText); assertEquals(dataInText, dataOutText); } + private static String toHex(MdnsRecord record) throws IOException { + MdnsPacketWriter writer = new MdnsPacketWriter(MAX_PACKET_SIZE); + record.write(writer, record.getReceiptTime()); + + // The address does not matter as only the data is used + final DatagramPacket packet = writer.getPacket(MULTICAST_IPV4_ADDRESS); + final byte[] dataOut = packet.getData(); + + return HexDump.dumpHexString(dataOut, 0, packet.getLength()); + } + @Test public void textRecord_recordDoesNotHaveDataOfGivenLength_throwsEOFException() throws Exception {