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 static com.android.server.connectivity.mdns.MdnsConstants.QCLASS_INTERNET; 20 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2; 21 22 import static org.junit.Assert.assertArrayEquals; 23 import static org.junit.Assert.assertEquals; 24 import static org.junit.Assert.assertFalse; 25 import static org.junit.Assert.assertNotEquals; 26 import static org.junit.Assert.assertNotNull; 27 import static org.junit.Assert.assertNull; 28 import static org.junit.Assert.assertThrows; 29 import static org.junit.Assert.assertTrue; 30 31 import static java.util.Collections.emptyList; 32 33 import android.util.Log; 34 35 import com.android.net.module.util.HexDump; 36 import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry; 37 import com.android.testutils.DevSdkIgnoreRule; 38 import com.android.testutils.DevSdkIgnoreRunner; 39 40 import org.junit.Test; 41 import org.junit.runner.RunWith; 42 43 import java.io.EOFException; 44 import java.io.IOException; 45 import java.net.DatagramPacket; 46 import java.net.Inet4Address; 47 import java.net.Inet6Address; 48 import java.net.InetSocketAddress; 49 import java.util.List; 50 51 // The record test data does not use compressed names (label pointers), since that would require 52 // additional data to populate the label dictionary accordingly. 53 @RunWith(DevSdkIgnoreRunner.class) 54 @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) 55 public class MdnsRecordTests { 56 private static final String TAG = "MdnsRecordTests"; 57 private static final int MAX_PACKET_SIZE = 4096; 58 private static final InetSocketAddress MULTICAST_IPV4_ADDRESS = 59 new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT); 60 private static final InetSocketAddress MULTICAST_IPV6_ADDRESS = 61 new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT); 62 63 @Test testInet4AddressRecord()64 public void testInet4AddressRecord() throws IOException { 65 final byte[] dataIn = HexDump.hexStringToByteArray( 66 "0474657374000001" + "0001000011940004" + "0A010203"); 67 assertNotNull(dataIn); 68 String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length); 69 70 // Decode 71 DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length); 72 MdnsPacketReader reader = new MdnsPacketReader(packet); 73 74 String[] name = reader.readLabels(); 75 assertNotNull(name); 76 assertEquals(1, name.length); 77 assertEquals("test", name[0]); 78 String fqdn = MdnsRecord.labelsToString(name); 79 assertEquals("test", fqdn); 80 81 int type = reader.readUInt16(); 82 assertEquals(MdnsRecord.TYPE_A, type); 83 84 MdnsInetAddressRecord record = new MdnsInetAddressRecord(name, MdnsRecord.TYPE_A, reader); 85 Inet4Address addr = record.getInet4Address(); 86 assertEquals("/10.1.2.3", addr.toString()); 87 88 String dataOutText = toHex(record); 89 Log.d(TAG, dataOutText); 90 91 assertEquals(dataInText, dataOutText); 92 } 93 94 @Test testTypeAAAInet6AddressRecord()95 public void testTypeAAAInet6AddressRecord() throws IOException { 96 final byte[] dataIn = HexDump.hexStringToByteArray( 97 "047465737400001C" 98 + "0001000011940010" 99 + "AABBCCDD11223344" 100 + "A0B0C0D010203040"); 101 assertNotNull(dataIn); 102 String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length); 103 104 // Decode 105 DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length); 106 packet.setSocketAddress( 107 new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT)); 108 MdnsPacketReader reader = new MdnsPacketReader(packet); 109 110 String[] name = reader.readLabels(); 111 assertNotNull(name); 112 assertEquals(1, name.length); 113 String fqdn = MdnsRecord.labelsToString(name); 114 assertEquals("test", fqdn); 115 116 int type = reader.readUInt16(); 117 assertEquals(MdnsRecord.TYPE_AAAA, type); 118 119 MdnsInetAddressRecord record = new MdnsInetAddressRecord(name, MdnsRecord.TYPE_AAAA, 120 reader); 121 assertNull(record.getInet4Address()); 122 Inet6Address addr = record.getInet6Address(); 123 assertEquals("/aabb:ccdd:1122:3344:a0b0:c0d0:1020:3040", addr.toString()); 124 125 String dataOutText = toHex(record); 126 Log.d(TAG, dataOutText); 127 128 assertEquals(dataInText, dataOutText); 129 } 130 131 @Test testTypeAAAInet4AddressRecord()132 public void testTypeAAAInet4AddressRecord() throws IOException { 133 final byte[] dataIn = HexDump.hexStringToByteArray( 134 "047465737400001C" 135 + "0001000011940010" 136 + "0000000000000000" 137 + "0000FFFF10203040"); 138 assertNotNull(dataIn); 139 HexDump.dumpHexString(dataIn, 0, dataIn.length); 140 141 // Decode 142 DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length); 143 packet.setSocketAddress( 144 new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT)); 145 MdnsPacketReader reader = new MdnsPacketReader(packet); 146 147 String[] name = reader.readLabels(); 148 assertNotNull(name); 149 assertEquals(1, name.length); 150 String fqdn = MdnsRecord.labelsToString(name); 151 assertEquals("test", fqdn); 152 153 int type = reader.readUInt16(); 154 assertEquals(MdnsRecord.TYPE_AAAA, type); 155 156 MdnsInetAddressRecord record = new MdnsInetAddressRecord(name, MdnsRecord.TYPE_AAAA, 157 reader); 158 assertNull(record.getInet6Address()); 159 Inet4Address addr = record.getInet4Address(); 160 assertEquals("/16.32.48.64", addr.toString()); 161 162 String dataOutText = toHex(record); 163 Log.d(TAG, dataOutText); 164 165 final byte[] expectedDataIn = 166 HexDump.hexStringToByteArray("047465737400001C000100001194000410203040"); 167 assertNotNull(expectedDataIn); 168 String expectedDataInText = HexDump.dumpHexString(expectedDataIn, 0, expectedDataIn.length); 169 170 assertEquals(expectedDataInText, dataOutText); 171 } 172 173 @Test testPointerRecord()174 public void testPointerRecord() throws IOException { 175 final byte[] dataIn = HexDump.hexStringToByteArray( 176 "047465737400000C" 177 + "000100001194000E" 178 + "03666F6F03626172" 179 + "047175787800"); 180 assertNotNull(dataIn); 181 String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length); 182 183 // Decode 184 DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length); 185 MdnsPacketReader reader = new MdnsPacketReader(packet); 186 187 String[] name = reader.readLabels(); 188 assertNotNull(name); 189 assertEquals(1, name.length); 190 String fqdn = MdnsRecord.labelsToString(name); 191 assertEquals("test", fqdn); 192 193 int type = reader.readUInt16(); 194 assertEquals(MdnsRecord.TYPE_PTR, type); 195 196 MdnsPointerRecord record = new MdnsPointerRecord(name, reader); 197 String[] pointer = record.getPointer(); 198 assertEquals("foo.bar.quxx", MdnsRecord.labelsToString(pointer)); 199 200 assertFalse(record.hasSubtype()); 201 assertNull(record.getSubtype()); 202 203 String dataOutText = toHex(record); 204 Log.d(TAG, dataOutText); 205 206 assertEquals(dataInText, dataOutText); 207 } 208 209 @Test testServiceRecord()210 public void testServiceRecord() throws IOException { 211 final byte[] dataIn = HexDump.hexStringToByteArray( 212 "0474657374000021" 213 + "0001000011940014" 214 + "000100FF1F480366" 215 + "6F6F036261720471" 216 + "75787800"); 217 assertNotNull(dataIn); 218 String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length); 219 220 // Decode 221 DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length); 222 MdnsPacketReader reader = new MdnsPacketReader(packet); 223 224 String[] name = reader.readLabels(); 225 assertNotNull(name); 226 assertEquals(1, name.length); 227 String fqdn = MdnsRecord.labelsToString(name); 228 assertEquals("test", fqdn); 229 230 int type = reader.readUInt16(); 231 assertEquals(MdnsRecord.TYPE_SRV, type); 232 233 MdnsServiceRecord record = new MdnsServiceRecord(name, reader); 234 235 int servicePort = record.getServicePort(); 236 assertEquals(8008, servicePort); 237 238 String serviceHost = MdnsRecord.labelsToString(record.getServiceHost()); 239 assertEquals("foo.bar.quxx", serviceHost); 240 241 assertEquals(1, record.getServicePriority()); 242 assertEquals(255, record.getServiceWeight()); 243 244 String dataOutText = toHex(record); 245 Log.d(TAG, dataOutText); 246 247 assertEquals(dataInText, dataOutText); 248 } 249 250 @Test testAnyRecord()251 public void testAnyRecord() throws IOException { 252 final byte[] dataIn = HexDump.hexStringToByteArray( 253 "047465737407616E64726F696403636F6D0000FF0001000000000000"); 254 assertNotNull(dataIn); 255 String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length); 256 257 // Decode 258 DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length); 259 MdnsPacketReader reader = new MdnsPacketReader(packet); 260 261 String[] name = reader.readLabels(); 262 assertNotNull(name); 263 assertEquals(3, name.length); 264 String fqdn = MdnsRecord.labelsToString(name); 265 assertEquals("test.android.com", fqdn); 266 267 int type = reader.readUInt16(); 268 assertEquals(MdnsRecord.TYPE_ANY, type); 269 270 MdnsAnyRecord record = new MdnsAnyRecord(name, reader); 271 272 String dataOutText = toHex(record); 273 Log.d(TAG, dataOutText); 274 275 assertEquals(dataInText, dataOutText); 276 } 277 278 @Test testNsecRecord()279 public void testNsecRecord() throws IOException { 280 final byte[] dataIn = HexDump.hexStringToByteArray( 281 // record.android.com 282 "067265636F726407616E64726F696403636F6D00" 283 // Type 0x002f (NSEC), cache flush set on class IN (0x8001) 284 + "002F8001" 285 // TTL 0x0000003c (60 secs) 286 + "0000003C" 287 // Data length 288 + "0031" 289 // nextdomain.android.com, with compression for android.com 290 + "0A6E657874646F6D61696EC007" 291 // Type bitmaps: window block 0x00, bitmap length 0x05, 292 // bits 16 (TXT) and 33 (SRV) set: 0x0000800040 293 + "00050000800040" 294 // For 1234, 4*256 + 210 = 1234, so window block 0x04, bitmap length 27/0x1B 295 // (26*8 + 2 = 210, need 27 bytes to set bit 210), 296 // bit 2 set on byte 27 (0x20). 297 + "041B000000000000000000000000000000000000000000000000000020"); 298 assertNotNull(dataIn); 299 String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length); 300 301 // Decode 302 DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length); 303 MdnsPacketReader reader = new MdnsPacketReader(packet); 304 305 String[] name = reader.readLabels(); 306 assertNotNull(name); 307 assertEquals(3, name.length); 308 String fqdn = MdnsRecord.labelsToString(name); 309 assertEquals("record.android.com", fqdn); 310 311 int type = reader.readUInt16(); 312 assertEquals(MdnsRecord.TYPE_NSEC, type); 313 314 MdnsNsecRecord record = new MdnsNsecRecord(name, reader); 315 assertTrue(record.getCacheFlush()); 316 assertEquals(60_000L, record.getTtl()); 317 assertEquals("nextdomain.android.com", MdnsRecord.labelsToString(record.getNextDomain())); 318 assertArrayEquals(new int[] { MdnsRecord.TYPE_TXT, 319 MdnsRecord.TYPE_SRV, 320 // Non-existing record type, > 256 321 1234 }, record.getTypes()); 322 323 String dataOutText = toHex(record); 324 assertEquals(dataInText, dataOutText); 325 } 326 327 @Test testTextRecord()328 public void testTextRecord() throws IOException { 329 final byte[] dataIn = HexDump.hexStringToByteArray( 330 "0474657374000010" 331 + "0001000011940024" 332 + "0D613D68656C6C6F" 333 + "2074686572650C62" 334 + "3D31323334353637" 335 + "3839300878797A3D" 336 + "21402324"); 337 assertNotNull(dataIn); 338 String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length); 339 340 // Decode 341 DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length); 342 MdnsPacketReader reader = new MdnsPacketReader(packet); 343 344 String[] name = reader.readLabels(); 345 assertNotNull(name); 346 assertEquals(1, name.length); 347 String fqdn = MdnsRecord.labelsToString(name); 348 assertEquals("test", fqdn); 349 350 int type = reader.readUInt16(); 351 assertEquals(MdnsRecord.TYPE_TXT, type); 352 353 MdnsTextRecord record = new MdnsTextRecord(name, reader); 354 355 List<String> strings = record.getStrings(); 356 assertNotNull(strings); 357 assertEquals(3, strings.size()); 358 359 assertEquals("a=hello there", strings.get(0)); 360 assertEquals("b=1234567890", strings.get(1)); 361 assertEquals("xyz=!@#$", strings.get(2)); 362 363 List<TextEntry> entries = record.getEntries(); 364 assertNotNull(entries); 365 assertEquals(3, entries.size()); 366 367 assertEquals(new TextEntry("a", "hello there"), entries.get(0)); 368 assertEquals(new TextEntry("b", "1234567890"), entries.get(1)); 369 assertEquals(new TextEntry("xyz", "!@#$"), entries.get(2)); 370 371 String dataOutText = toHex(record); 372 Log.d(TAG, dataOutText); 373 374 assertEquals(dataInText, dataOutText); 375 } 376 makeTextRecordWithEntries(List<TextEntry> entries)377 private static MdnsTextRecord makeTextRecordWithEntries(List<TextEntry> entries) { 378 return new MdnsTextRecord(new String[] { "test", "record" }, 0L /* receiptTimeMillis */, 379 true /* cacheFlush */, 120_000L /* ttlMillis */, entries); 380 } 381 382 @Test testTextRecord_EmptyRecordsAreEquivalent()383 public void testTextRecord_EmptyRecordsAreEquivalent() { 384 final MdnsTextRecord record1 = makeTextRecordWithEntries(emptyList()); 385 final MdnsTextRecord record2 = makeTextRecordWithEntries( 386 List.of(new TextEntry("", (byte[]) null))); 387 final MdnsTextRecord record3 = makeTextRecordWithEntries( 388 List.of(new TextEntry(null, (byte[]) null))); 389 final MdnsTextRecord nonEmptyRecord = makeTextRecordWithEntries( 390 List.of(new TextEntry("a", (byte[]) null))); 391 392 assertEquals(record1, record1); 393 assertEquals(record1, record2); 394 assertEquals(record1, record3); 395 396 assertNotEquals(nonEmptyRecord, record1); 397 assertNotEquals(nonEmptyRecord, record2); 398 assertNotEquals(nonEmptyRecord, record3); 399 } 400 toHex(MdnsRecord record)401 private static String toHex(MdnsRecord record) throws IOException { 402 MdnsPacketWriter writer = new MdnsPacketWriter(MAX_PACKET_SIZE); 403 record.write(writer, record.getReceiptTime()); 404 405 // The address does not matter as only the data is used 406 final DatagramPacket packet = writer.getPacket(MULTICAST_IPV4_ADDRESS); 407 final byte[] dataOut = packet.getData(); 408 409 return HexDump.dumpHexString(dataOut, 0, packet.getLength()); 410 } 411 412 @Test textRecord_recordDoesNotHaveDataOfGivenLength_throwsEOFException()413 public void textRecord_recordDoesNotHaveDataOfGivenLength_throwsEOFException() 414 throws Exception { 415 final byte[] dataIn = HexDump.hexStringToByteArray( 416 "0474657374000010" 417 + "000100001194000D" 418 + "0D613D68656C6C6F" //The TXT entry starts with length of 13, but only 12 419 + "2074686572"); // characters are following it. 420 DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length); 421 MdnsPacketReader reader = new MdnsPacketReader(packet); 422 String[] name = reader.readLabels(); 423 MdnsRecord.labelsToString(name); 424 reader.readUInt16(); 425 426 assertThrows(EOFException.class, () -> new MdnsTextRecord(name, reader)); 427 } 428 429 @Test textRecord_entriesIncludeNonUtf8Bytes_returnsTheSameUtf8Bytes()430 public void textRecord_entriesIncludeNonUtf8Bytes_returnsTheSameUtf8Bytes() throws Exception { 431 final byte[] dataIn = HexDump.hexStringToByteArray( 432 "0474657374000010" 433 + "0001000011940024" 434 + "0D613D68656C6C6F" 435 + "2074686572650C62" 436 + "3D31323334353637" 437 + "3839300878797A3D" 438 + "FFEFDFCF"); 439 DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length); 440 MdnsPacketReader reader = new MdnsPacketReader(packet); 441 String[] name = reader.readLabels(); 442 MdnsRecord.labelsToString(name); 443 reader.readUInt16(); 444 445 MdnsTextRecord record = new MdnsTextRecord(name, reader); 446 447 List<TextEntry> entries = record.getEntries(); 448 assertNotNull(entries); 449 assertEquals(3, entries.size()); 450 assertEquals(new TextEntry("a", "hello there"), entries.get(0)); 451 assertEquals(new TextEntry("b", "1234567890"), entries.get(1)); 452 assertEquals(new TextEntry("xyz", HexDump.hexStringToByteArray("FFEFDFCF")), 453 entries.get(2)); 454 } 455 456 @Test testKeyRecord()457 public void testKeyRecord() throws IOException { 458 final byte[] dataIn = 459 HexDump.hexStringToByteArray( 460 "09746573742d686f7374056c6f63616c" 461 + "00001980010000000a00440201030dc1" 462 + "41d0637960b98cbc12cfca221d2879da" 463 + "c26ee5b460e9007c992e1902d897c391" 464 + "b03764d448f7d0c772fdb03b1d9d6d52" 465 + "ff8886769e8e2362513565270962d3"); 466 final byte[] rData = 467 HexDump.hexStringToByteArray( 468 "0201030dc141d0637960b98cbc12cfca" 469 + "221d2879dac26ee5b460e9007c992e19" 470 + "02d897c391b03764d448f7d0c772fdb0" 471 + "3b1d9d6d52ff8886769e8e2362513565" 472 + "270962d3"); 473 assertNotNull(dataIn); 474 String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length); 475 476 // Decode 477 DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length); 478 MdnsPacketReader reader = new MdnsPacketReader(packet); 479 480 String[] name = reader.readLabels(); 481 assertNotNull(name); 482 assertEquals(2, name.length); 483 String fqdn = MdnsRecord.labelsToString(name); 484 assertEquals("test-host.local", fqdn); 485 486 int type = reader.readUInt16(); 487 assertEquals(MdnsRecord.TYPE_KEY, type); 488 489 MdnsKeyRecord keyRecord; 490 491 // MdnsKeyRecord(String[] name, MdnsPacketReader reader) 492 reader = new MdnsPacketReader(packet); 493 reader.readLabels(); // Skip labels 494 reader.readUInt16(); // Skip type 495 keyRecord = new MdnsKeyRecord(name, reader); 496 assertEquals(MdnsRecord.TYPE_KEY, keyRecord.getType()); 497 assertTrue(keyRecord.getTtl() > 0); // Not a question so the TTL is greater than 0 498 assertTrue(keyRecord.getCacheFlush()); 499 assertArrayEquals(new String[] {"test-host", "local"}, keyRecord.getName()); 500 assertArrayEquals(rData, keyRecord.getRData()); 501 assertNotEquals(rData, keyRecord.getRData()); // Uses a copy of the original RDATA 502 assertEquals(dataInText, toHex(keyRecord)); 503 504 // MdnsKeyRecord(String[] name, MdnsPacketReader reader, boolean isQuestion) 505 reader = new MdnsPacketReader(packet); 506 reader.readLabels(); // Skip labels 507 reader.readUInt16(); // Skip type 508 keyRecord = new MdnsKeyRecord(name, reader, false /* isQuestion */); 509 assertEquals(MdnsRecord.TYPE_KEY, keyRecord.getType()); 510 assertTrue(keyRecord.getTtl() > 0); // Not a question, so the TTL is greater than 0 511 assertTrue(keyRecord.getCacheFlush()); 512 assertArrayEquals(new String[] {"test-host", "local"}, keyRecord.getName()); 513 assertArrayEquals(rData, keyRecord.getRData()); 514 assertNotEquals(rData, keyRecord.getRData()); // Uses a copy of the original RDATA 515 516 // MdnsKeyRecord(String[] name, boolean isUnicast) 517 keyRecord = new MdnsKeyRecord(name, false /* isUnicast */); 518 assertEquals(MdnsRecord.TYPE_KEY, keyRecord.getType()); 519 assertEquals(0, keyRecord.getTtl()); 520 assertEquals(QCLASS_INTERNET, keyRecord.getRecordClass()); 521 assertFalse(keyRecord.getCacheFlush()); 522 assertArrayEquals(new String[] {"test-host", "local"}, keyRecord.getName()); 523 assertArrayEquals(null, keyRecord.getRData()); 524 525 // MdnsKeyRecord(String[] name, long receiptTimeMillis, boolean cacheFlush, long ttlMillis, 526 // byte[] rData) 527 keyRecord = 528 new MdnsKeyRecord( 529 name, 530 10 /* receiptTimeMillis */, 531 true /* cacheFlush */, 532 20_000 /* ttlMillis */, 533 rData); 534 assertEquals(MdnsRecord.TYPE_KEY, keyRecord.getType()); 535 assertEquals(10, keyRecord.getReceiptTime()); 536 assertTrue(keyRecord.getCacheFlush()); 537 assertEquals(20_000, keyRecord.getTtl()); 538 assertEquals(QCLASS_INTERNET, keyRecord.getRecordClass()); 539 assertArrayEquals(new String[] {"test-host", "local"}, keyRecord.getName()); 540 assertArrayEquals(rData, keyRecord.getRData()); 541 assertNotEquals(rData, keyRecord.getRData()); // Uses a copy of the original RDATA 542 } 543 } 544