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