// Copyright 2012 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/dns/dns_response.h" #include #include #include #include #include #include #include "base/check.h" #include "base/containers/span.h" #include "base/containers/span_writer.h" #include "base/time/time.h" #include "net/base/io_buffer.h" #include "net/dns/dns_names_util.h" #include "net/dns/dns_query.h" #include "net/dns/dns_test_util.h" #include "net/dns/public/dns_protocol.h" #include "net/dns/record_rdata.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { namespace { TEST(DnsRecordParserTest, Constructor) { const char data[] = { 0 }; EXPECT_FALSE(DnsRecordParser().IsValid()); EXPECT_TRUE(DnsRecordParser(data, 1, 0, 0).IsValid()); EXPECT_TRUE(DnsRecordParser(data, 1, 1, 0).IsValid()); EXPECT_FALSE(DnsRecordParser(data, 1, 0, 0).AtEnd()); EXPECT_TRUE(DnsRecordParser(data, 1, 1, 0).AtEnd()); } TEST(DnsRecordParserTest, ReadName) { const uint8_t data[] = { // all labels "foo.example.com" 0x03, 'f', 'o', 'o', 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm', // byte 0x10 0x00, // byte 0x11 // part label, part pointer, "bar.example.com" 0x03, 'b', 'a', 'r', 0xc0, 0x04, // byte 0x17 // all pointer to "bar.example.com", 2 jumps 0xc0, 0x11, // byte 0x1a }; std::string out; DnsRecordParser parser(data, sizeof(data), 0, /*num_records=*/0); ASSERT_TRUE(parser.IsValid()); EXPECT_EQ(0x11u, parser.ReadName(data + 0x00, &out)); EXPECT_EQ("foo.example.com", out); // Check that the last "." is never stored. out.clear(); EXPECT_EQ(0x1u, parser.ReadName(data + 0x10, &out)); EXPECT_EQ("", out); out.clear(); EXPECT_EQ(0x6u, parser.ReadName(data + 0x11, &out)); EXPECT_EQ("bar.example.com", out); out.clear(); EXPECT_EQ(0x2u, parser.ReadName(data + 0x17, &out)); EXPECT_EQ("bar.example.com", out); // Parse name without storing it. EXPECT_EQ(0x11u, parser.ReadName(data + 0x00, nullptr)); EXPECT_EQ(0x1u, parser.ReadName(data + 0x10, nullptr)); EXPECT_EQ(0x6u, parser.ReadName(data + 0x11, nullptr)); EXPECT_EQ(0x2u, parser.ReadName(data + 0x17, nullptr)); // Check that it works even if initial position is different. parser = DnsRecordParser(data, sizeof(data), 0x12, /*num_records=*/0); EXPECT_EQ(0x6u, parser.ReadName(data + 0x11, nullptr)); } TEST(DnsRecordParserTest, ReadNameFail) { const uint8_t data[] = { // label length beyond packet 0x30, 'x', 'x', 0x00, // pointer offset beyond packet 0xc0, 0x20, // pointer loop 0xc0, 0x08, 0xc0, 0x06, // incorrect label type (currently supports only direct and pointer) 0x80, 0x00, // truncated name (missing root label) 0x02, 'x', 'x', }; DnsRecordParser parser(data, sizeof(data), 0, /*num_records=*/0); ASSERT_TRUE(parser.IsValid()); std::string out; EXPECT_EQ(0u, parser.ReadName(data + 0x00, &out)); EXPECT_EQ(0u, parser.ReadName(data + 0x04, &out)); EXPECT_EQ(0u, parser.ReadName(data + 0x08, &out)); EXPECT_EQ(0u, parser.ReadName(data + 0x0a, &out)); EXPECT_EQ(0u, parser.ReadName(data + 0x0c, &out)); EXPECT_EQ(0u, parser.ReadName(data + 0x0e, &out)); } // Returns an RFC 1034 style domain name with a length of |name_len|. // Also writes the expected dotted string representation into |dotted_str|, // which must be non-null. std::vector BuildRfc1034Name(const size_t name_len, std::string* dotted_str) { // Impossible length. If length not zero, need at least 2 to allow label // length and label contents. CHECK_NE(name_len, 1u); CHECK(dotted_str != nullptr); auto ChoosePrintableCharLambda = [](uint8_t n) { return n % 26 + 'A'; }; const size_t max_label_len = 63; std::vector data; dotted_str->clear(); while (data.size() < name_len) { // Compute the size of the next label. // // No need to account for next label length because the final zero length is // not considered included in overall length. size_t label_len = std::min(name_len - data.size() - 1, max_label_len); // Need to ensure the remainder is not 1 because that would leave room for a // label length but not a label. if (name_len - data.size() - label_len - 1 == 1) { CHECK_GT(label_len, 1u); label_len -= 1; } // Write the length octet data.push_back(label_len); // Write |label_len| bytes of label data const size_t size_with_label = data.size() + label_len; while (data.size() < size_with_label) { const uint8_t chr = ChoosePrintableCharLambda(data.size()); data.push_back(chr); dotted_str->push_back(chr); CHECK(data.size() <= name_len); } // Write a trailing dot after every label dotted_str->push_back('.'); } // Omit the final dot if (!dotted_str->empty()) dotted_str->pop_back(); CHECK(data.size() == name_len); // Final zero-length label (not considered included in overall length). data.push_back(0); return data; } TEST(DnsRecordParserTest, ReadNameGoodLength) { const size_t name_len_cases[] = {2, 10, 40, 250, 254, 255}; for (auto name_len : name_len_cases) { std::string expected_name; const std::vector data_vector = BuildRfc1034Name(name_len, &expected_name); ASSERT_EQ(data_vector.size(), name_len + 1); const uint8_t* data = data_vector.data(); DnsRecordParser parser(data, data_vector.size(), 0, /*num_records=*/0); ASSERT_TRUE(parser.IsValid()); std::string out; EXPECT_EQ(data_vector.size(), parser.ReadName(data, &out)); EXPECT_EQ(expected_name, out); } } // Tests against incorrect name length validation, which is anti-pattern #3 from // the "NAME:WRECK" report: // https://www.forescout.com/company/resources/namewreck-breaking-and-fixing-dns-implementations/ TEST(DnsRecordParserTest, ReadNameTooLongFail) { const size_t name_len_cases[] = {256, 257, 258, 300, 10000}; for (auto name_len : name_len_cases) { std::string expected_name; const std::vector data_vector = BuildRfc1034Name(name_len, &expected_name); ASSERT_EQ(data_vector.size(), name_len + 1); const uint8_t* data = data_vector.data(); DnsRecordParser parser(data, data_vector.size(), 0, /*num_records=*/0); ASSERT_TRUE(parser.IsValid()); std::string out; EXPECT_EQ(0u, parser.ReadName(data, &out)); } } // Tests against incorrect name compression pointer validation, which is anti- // pattern #6 from the "NAME:WRECK" report: // https://www.forescout.com/company/resources/namewreck-breaking-and-fixing-dns-implementations/ TEST(DnsRecordParserTest, RejectsNamesWithLoops) { const char kData[] = "\003www\007example\300\031" // www.example with pointer to byte 25 "aaaaaaaaaaa" // Garbage data to spread things out. "\003foo\300\004"; // foo with pointer to byte 4. DnsRecordParser parser(kData, /*length=*/sizeof(kData) - 1, /*offset=*/0, /*num_records=*/0); ASSERT_TRUE(parser.IsValid()); std::string out; EXPECT_EQ(0u, parser.ReadName(kData, &out)); } // Tests against incorrect name compression pointer validation, which is anti- // pattern #6 from the "NAME:WRECK" report: // https://www.forescout.com/company/resources/namewreck-breaking-and-fixing-dns-implementations/ TEST(DnsRecordParserTest, RejectsNamesPointingOutsideData) { const char kData[] = "\003www\007example\300\031"; // www.example with pointer to byte 25 DnsRecordParser parser(kData, /*length=*/sizeof(kData) - 1, /*offset=*/0, /*num_records=*/0); ASSERT_TRUE(parser.IsValid()); std::string out; EXPECT_EQ(0u, parser.ReadName(kData, &out)); } TEST(DnsRecordParserTest, ParsesValidPointer) { const char kData[] = "\003www\007example\300\022" // www.example with pointer to byte 25. "aaaa" // Garbage data to spread things out. "\004test\000"; // .test DnsRecordParser parser(kData, /*length=*/sizeof(kData) - 1, /*offset=*/0, /*num_records=*/0); ASSERT_TRUE(parser.IsValid()); std::string out; EXPECT_EQ(14u, parser.ReadName(kData, &out)); EXPECT_EQ(out, "www.example.test"); } // Per RFC 1035, section 4.1.4, the first 2 bits of a DNS name label determine // if it is a length label (if the bytes are 00) or a pointer label (if the // bytes are 11). It is a common DNS parsing bug to treat 01 or 10 as pointer // labels, but these are reserved and invalid. Such labels should always result // in DnsRecordParser rejecting the name. // // Tests against incorrect name compression pointer validation, which is anti- // pattern #6 from the "NAME:WRECK" report: // https://www.forescout.com/company/resources/namewreck-breaking-and-fixing-dns-implementations/ TEST(DnsRecordParserTest, RejectsNamesWithInvalidLabelTypeAsPointer) { const char kData[] = "\003www\007example\200\022" // www.example with invalid label as pointer "aaaa" // Garbage data to spread things out. "\004test\000"; // .test DnsRecordParser parser(kData, /*length=*/sizeof(kData) - 1, /*offset=*/0, /*num_records=*/0); ASSERT_TRUE(parser.IsValid()); std::string out; EXPECT_EQ(0u, parser.ReadName(kData, &out)); } // Per RFC 1035, section 4.1.4, the first 2 bits of a DNS name label determine // if it is a length label (if the bytes are 00) or a pointer label (if the // bytes are 11). Such labels should always result in DnsRecordParser rejecting // the name. // // Tests against incorrect name compression pointer validation, which is anti- // pattern #6 from the "NAME:WRECK" report: // https://www.forescout.com/company/resources/namewreck-breaking-and-fixing-dns-implementations/ TEST(DnsRecordParserTest, RejectsNamesWithInvalidLabelTypeAsLength) { const char kData[] = "\003www\007example\104" // www.example with invalid label as length "test\000"; // test. (in case \104 is interpreted as length=4) // Append a bunch of zeroes to the buffer in case \104 is interpreted as a // long length. std::string data(kData, sizeof(kData) - 1); data.append(256, '\000'); DnsRecordParser parser(data.data(), data.size(), /*offset=*/0, /*num_records=*/0); ASSERT_TRUE(parser.IsValid()); std::string out; EXPECT_EQ(0u, parser.ReadName(data.data(), &out)); } TEST(DnsRecordParserTest, ReadRecord) { const uint8_t data[] = { // Type CNAME record. 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm', 0x00, 0x00, 0x05, // TYPE is CNAME. 0x00, 0x01, // CLASS is IN. 0x00, 0x01, 0x24, 0x74, // TTL is 0x00012474. 0x00, 0x06, // RDLENGTH is 6 bytes. 0x03, 'f', 'o', 'o', // compressed name in record 0xc0, 0x00, // Type A record. 0x03, 'b', 'a', 'r', // compressed owner name 0xc0, 0x00, 0x00, 0x01, // TYPE is A. 0x00, 0x01, // CLASS is IN. 0x00, 0x20, 0x13, 0x55, // TTL is 0x00201355. 0x00, 0x04, // RDLENGTH is 4 bytes. 0x7f, 0x02, 0x04, 0x01, // IP is 127.2.4.1 }; std::string out; DnsRecordParser parser(data, 0, /*num_records=*/2); DnsResourceRecord record; EXPECT_TRUE(parser.ReadRecord(&record)); EXPECT_EQ("example.com", record.name); EXPECT_EQ(dns_protocol::kTypeCNAME, record.type); EXPECT_EQ(dns_protocol::kClassIN, record.klass); EXPECT_EQ(0x00012474u, record.ttl); EXPECT_EQ(6u, record.rdata.length()); EXPECT_EQ(6u, parser.ReadName(record.rdata.data(), &out)); EXPECT_EQ("foo.example.com", out); EXPECT_FALSE(parser.AtEnd()); EXPECT_TRUE(parser.ReadRecord(&record)); EXPECT_EQ("bar.example.com", record.name); EXPECT_EQ(dns_protocol::kTypeA, record.type); EXPECT_EQ(dns_protocol::kClassIN, record.klass); EXPECT_EQ(0x00201355u, record.ttl); EXPECT_EQ(4u, record.rdata.length()); EXPECT_EQ(std::string_view("\x7f\x02\x04\x01"), record.rdata); EXPECT_TRUE(parser.AtEnd()); // Test truncated record. parser = DnsRecordParser(data, sizeof(data) - 2, 0, /*num_records=*/2); EXPECT_TRUE(parser.ReadRecord(&record)); EXPECT_FALSE(parser.AtEnd()); EXPECT_FALSE(parser.ReadRecord(&record)); } TEST(DnsRecordParserTest, ReadsRecordWithLongName) { std::string dotted_name; const std::vector dns_name = BuildRfc1034Name(dns_protocol::kMaxNameLength, &dotted_name); std::string data(reinterpret_cast(dns_name.data()), dns_name.size()); data.append( "\x00\x01" // TYPE=A "\x00\x01" // CLASS=IN "\x00\x01\x51\x80" // TTL=1 day "\x00\x04" // RDLENGTH=4 bytes "\xc0\xa8\x00\x01", // 192.168.0.1 14); DnsRecordParser parser(data.data(), data.size(), 0, /*num_records=*/1); DnsResourceRecord record; EXPECT_TRUE(parser.ReadRecord(&record)); } // Tests against incorrect name length validation, which is anti-pattern #3 from // the "NAME:WRECK" report: // https://www.forescout.com/company/resources/namewreck-breaking-and-fixing-dns-implementations/ TEST(DnsRecordParserTest, RejectRecordWithTooLongName) { std::string dotted_name; const std::vector dns_name = BuildRfc1034Name(dns_protocol::kMaxNameLength + 1, &dotted_name); std::string data(reinterpret_cast(dns_name.data()), dns_name.size()); data.append( "\x00\x01" // TYPE=A "\x00\x01" // CLASS=IN "\x00\x01\x51\x80" // TTL=1 day "\x00\x04" // RDLENGTH=4 bytes "\xc0\xa8\x00\x01", // 192.168.0.1 14); DnsRecordParser parser(data.data(), data.size(), 0, /*num_records=*/1); DnsResourceRecord record; EXPECT_FALSE(parser.ReadRecord(&record)); } // Test that a record cannot be parsed with a name extending past the end of the // data. // Tests against incorrect name length validation, which is anti-pattern #3 from // the "NAME:WRECK" report: // https://www.forescout.com/company/resources/namewreck-breaking-and-fixing-dns-implementations/ TEST(DnsRecordParserTest, RejectRecordWithNonendedName) { const char kNonendedName[] = "\003www\006google\006www"; DnsRecordParser parser(kNonendedName, sizeof(kNonendedName) - 1, 0, /*num_records=*/1); DnsResourceRecord record; EXPECT_FALSE(parser.ReadRecord(&record)); } // Test that a record cannot be parsed with a name without final null // termination. Parsing should assume the name has not ended and find the first // byte of the TYPE field instead, making the remainder of the record // unparsable. // Tests against incorrect name null termination, which is anti-pattern #4 from // the "NAME:WRECK" report: // https://www.forescout.com/company/resources/namewreck-breaking-and-fixing-dns-implementations/ TEST(DnsRecordParserTest, RejectRecordNameMissingNullTermination) { const char kData[] = "\003www\006google\004test" // Name without termination. "\x00\x01" // TYPE=A "\x00\x01" // CLASS=IN "\x00\x01\x51\x80" // TTL=1 day "\x00\x04" // RDLENGTH=4 bytes "\xc0\xa8\x00\x01"; // 192.168.0.1 DnsRecordParser parser(kData, sizeof(kData) - 1, 0, /*num_records=*/1); DnsResourceRecord record; EXPECT_FALSE(parser.ReadRecord(&record)); } // Test that no more records can be parsed once the claimed number of records // have been parsed. TEST(DnsRecordParserTest, RejectReadingTooManyRecords) { const char kData[] = "\003www\006google\004test\000" "\x00\x01" // TYPE=A "\x00\x01" // CLASS=IN "\x00\x01\x51\x80" // TTL=1 day "\x00\x04" // RDLENGTH=4 bytes "\xc0\xa8\x00\x01" // 192.168.0.1 "\003www\010chromium\004test\000" "\x00\x01" // TYPE=A "\x00\x01" // CLASS=IN "\x00\x01\x51\x80" // TTL=1 day "\x00\x04" // RDLENGTH=4 bytes "\xc0\xa8\x00\x02"; // 192.168.0.2 DnsRecordParser parser( kData, /*length=*/sizeof(kData) - 1, /*offset=*/0, /*num_records=*/1); // Claim 1 record despite there being 2 in `kData`. DnsResourceRecord record1; EXPECT_TRUE(parser.ReadRecord(&record1)); // Expect second record cannot be parsed because only 1 was expected. DnsResourceRecord record2; EXPECT_FALSE(parser.ReadRecord(&record2)); } // Test that no more records can be parsed once the end of the buffer is // reached, even if more records are claimed. TEST(DnsRecordParserTest, RejectReadingPastEnd) { const char kData[] = "\003www\006google\004test\000" "\x00\x01" // TYPE=A "\x00\x01" // CLASS=IN "\x00\x01\x51\x80" // TTL=1 day "\x00\x04" // RDLENGTH=4 bytes "\xc0\xa8\x00\x01" // 192.168.0.1 "\003www\010chromium\004test\000" "\x00\x01" // TYPE=A "\x00\x01" // CLASS=IN "\x00\x01\x51\x80" // TTL=1 day "\x00\x04" // RDLENGTH=4 bytes "\xc0\xa8\x00\x02"; // 192.168.0.2 DnsRecordParser parser( kData, /*length=*/sizeof(kData) - 1, /*offset=*/0, /*num_records=*/3); // Claim 3 record despite there being 2 in `kData`. DnsResourceRecord record; EXPECT_TRUE(parser.ReadRecord(&record)); EXPECT_TRUE(parser.ReadRecord(&record)); EXPECT_FALSE(parser.ReadRecord(&record)); } TEST(DnsResponseTest, InitParse) { // This includes \0 at the end. const char qname[] = "\x0A" "codereview" "\x08" "chromium" "\x03" "org"; // Compilers want to copy when binding temporary to const &, so must use heap. auto query = std::make_unique(0xcafe, base::as_byte_span(qname), dns_protocol::kTypeA); const uint8_t response_data[] = { // Header 0xca, 0xfe, // ID 0x81, 0x80, // Standard query response, RA, no error 0x00, 0x01, // 1 question 0x00, 0x02, // 2 RRs (answers) 0x00, 0x00, // 0 authority RRs 0x00, 0x01, // 1 additional RRs // Question // This part is echoed back from the respective query. 0x0a, 'c', 'o', 'd', 'e', 'r', 'e', 'v', 'i', 'e', 'w', 0x08, 'c', 'h', 'r', 'o', 'm', 'i', 'u', 'm', 0x03, 'o', 'r', 'g', 0x00, 0x00, 0x01, // TYPE is A. 0x00, 0x01, // CLASS is IN. // Answer 1 0xc0, 0x0c, // NAME is a pointer to name in Question section. 0x00, 0x05, // TYPE is CNAME. 0x00, 0x01, // CLASS is IN. 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds. 0x24, 0x74, 0x00, 0x12, // RDLENGTH is 18 bytes. // ghs.l.google.com in DNS format. 0x03, 'g', 'h', 's', 0x01, 'l', 0x06, 'g', 'o', 'o', 'g', 'l', 'e', 0x03, 'c', 'o', 'm', 0x00, // Answer 2 0xc0, 0x35, // NAME is a pointer to name in Answer 1. 0x00, 0x01, // TYPE is A. 0x00, 0x01, // CLASS is IN. 0x00, 0x00, // TTL (4 bytes) is 53 seconds. 0x00, 0x35, 0x00, 0x04, // RDLENGTH is 4 bytes. 0x4a, 0x7d, // RDATA is the IP: 74.125.95.121 0x5f, 0x79, // Additional 1 0x00, // NAME is empty (root domain). 0x00, 0x29, // TYPE is OPT. 0x10, 0x00, // CLASS is max UDP payload size (4096). 0x00, 0x00, 0x00, 0x00, // TTL (4 bytes) is rcode, version and flags. 0x00, 0x08, // RDLENGTH 0x00, 0xFF, // OPT code 0x00, 0x04, // OPT data size 0xDE, 0xAD, 0xBE, 0xEF // OPT data }; DnsResponse resp; memcpy(resp.io_buffer()->data(), response_data, sizeof(response_data)); EXPECT_FALSE(resp.id()); // Reject too short. EXPECT_FALSE(resp.InitParse(query->io_buffer()->size() - 1, *query)); EXPECT_FALSE(resp.IsValid()); EXPECT_FALSE(resp.id()); // Reject wrong id. std::unique_ptr other_query = query->CloneWithNewId(0xbeef); EXPECT_FALSE(resp.InitParse(sizeof(response_data), *other_query)); EXPECT_FALSE(resp.IsValid()); EXPECT_THAT(resp.id(), testing::Optional(0xcafe)); // Reject wrong question. auto wrong_query = std::make_unique( 0xcafe, base::as_byte_span(qname), dns_protocol::kTypeCNAME); EXPECT_FALSE(resp.InitParse(sizeof(response_data), *wrong_query)); EXPECT_FALSE(resp.IsValid()); EXPECT_THAT(resp.id(), testing::Optional(0xcafe)); // Accept matching question. EXPECT_TRUE(resp.InitParse(sizeof(response_data), *query)); EXPECT_TRUE(resp.IsValid()); // Check header access. EXPECT_THAT(resp.id(), testing::Optional(0xcafe)); EXPECT_EQ(0x8180, resp.flags()); EXPECT_EQ(0x0, resp.rcode()); EXPECT_EQ(2u, resp.answer_count()); EXPECT_EQ(1u, resp.additional_answer_count()); // Check question access. std::optional> response_qname = dns_names_util::DottedNameToNetwork(resp.GetSingleDottedName()); ASSERT_TRUE(response_qname.has_value()); EXPECT_THAT(query->qname(), testing::ElementsAreArray(response_qname.value())); EXPECT_EQ(query->qtype(), resp.GetSingleQType()); EXPECT_EQ("codereview.chromium.org", resp.GetSingleDottedName()); DnsResourceRecord record; DnsRecordParser parser = resp.Parser(); EXPECT_TRUE(parser.ReadRecord(&record)); EXPECT_FALSE(parser.AtEnd()); EXPECT_TRUE(parser.ReadRecord(&record)); EXPECT_FALSE(parser.AtEnd()); EXPECT_TRUE(parser.ReadRecord(&record)); EXPECT_TRUE(parser.AtEnd()); EXPECT_FALSE(parser.ReadRecord(&record)); } TEST(DnsResponseTest, InitParseInvalidFlags) { // This includes \0 at the end. const char qname[] = "\x0A" "codereview" "\x08" "chromium" "\x03" "org"; // Compilers want to copy when binding temporary to const &, so must use heap. auto query = std::make_unique(0xcafe, base::as_byte_span(qname), dns_protocol::kTypeA); const uint8_t response_data[] = { // Header 0xca, 0xfe, // ID 0x01, 0x80, // RA, no error. Note the absence of the required QR bit. 0x00, 0x01, // 1 question 0x00, 0x01, // 1 RRs (answers) 0x00, 0x00, // 0 authority RRs 0x00, 0x00, // 0 additional RRs // Question // This part is echoed back from the respective query. 0x0a, 'c', 'o', 'd', 'e', 'r', 'e', 'v', 'i', 'e', 'w', 0x08, 'c', 'h', 'r', 'o', 'm', 'i', 'u', 'm', 0x03, 'o', 'r', 'g', 0x00, 0x00, 0x01, // TYPE is A. 0x00, 0x01, // CLASS is IN. // Answer 1 0xc0, 0x0c, // NAME is a pointer to name in Question section. 0x00, 0x05, // TYPE is CNAME. 0x00, 0x01, // CLASS is IN. 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds. 0x24, 0x74, 0x00, 0x12, // RDLENGTH is 18 bytes. // ghs.l.google.com in DNS format. 0x03, 'g', 'h', 's', 0x01, 'l', 0x06, 'g', 'o', 'o', 'g', 'l', 'e', 0x03, 'c', 'o', 'm', 0x00, }; DnsResponse resp; memcpy(resp.io_buffer()->data(), response_data, sizeof(response_data)); EXPECT_FALSE(resp.InitParse(sizeof(response_data), *query)); EXPECT_FALSE(resp.IsValid()); EXPECT_THAT(resp.id(), testing::Optional(0xcafe)); } TEST(DnsResponseTest, InitParseRejectsResponseWithoutQuestions) { const char kResponse[] = "\x02\x45" // ID=581 "\x81\x80" // Standard query response, RA, no error "\x00\x00" // 0 questions "\x00\x01" // 1 answers "\x00\x00" // 0 authority records "\x00\x00" // 0 additional records "\003www\006google\004test\000" // www.google.test "\x00\x01" // TYPE=A "\x00\x01" // CLASS=IN "\x00\x00\x2a\x30" // TTL=3 hours "\x00\x04" // RDLENGTH=4 bytes "\xa0\xa0\xa0\xa0"; // 10.10.10.10 DnsResponse resp; memcpy(resp.io_buffer()->data(), kResponse, sizeof(kResponse) - 1); // Validate that the response is fine if not matching against a query. ASSERT_TRUE(resp.InitParseWithoutQuery(sizeof(kResponse) - 1)); const char kQueryName[] = "\003www\006google\004test"; DnsQuery query( /*id=*/581, base::as_byte_span(kQueryName), dns_protocol::kTypeA); EXPECT_FALSE(resp.InitParse(sizeof(kResponse) - 1, query)); } TEST(DnsResponseTest, InitParseRejectsResponseWithTooManyQuestions) { const char kResponse[] = "\x02\x46" // ID=582 "\x81\x80" // Standard query response, RA, no error "\x00\x02" // 2 questions "\x00\x00" // 0 answers "\x00\x00" // 0 authority records "\x00\x00" // 0 additional records "\003www\006google\004test\000" // www.google.test "\x00\x01" // TYPE=A "\x00\x01" // CLASS=IN "\003www\010chromium\004test\000" // www.chromium.test "\x00\x01" // TYPE=A "\x00\x01"; // CLASS=IN DnsResponse resp; memcpy(resp.io_buffer()->data(), kResponse, sizeof(kResponse) - 1); // Validate that the response is fine if not matching against a query. ASSERT_TRUE(resp.InitParseWithoutQuery(sizeof(kResponse) - 1)); const char kQueryName[] = "\003www\006google\004test"; DnsQuery query( /*id=*/582, base::as_byte_span(kQueryName), dns_protocol::kTypeA); EXPECT_FALSE(resp.InitParse(sizeof(kResponse) - 1, query)); } TEST(DnsResponseTest, InitParseWithoutQuery) { DnsResponse resp; memcpy(resp.io_buffer()->data(), kT0ResponseDatagram, sizeof(kT0ResponseDatagram)); // Accept matching question. EXPECT_TRUE(resp.InitParseWithoutQuery(sizeof(kT0ResponseDatagram))); EXPECT_TRUE(resp.IsValid()); // Check header access. EXPECT_EQ(0x8180, resp.flags()); EXPECT_EQ(0x0, resp.rcode()); EXPECT_EQ(kT0RecordCount, resp.answer_count()); // Check question access. EXPECT_EQ(kT0Qtype, resp.GetSingleQType()); EXPECT_EQ(kT0HostName, resp.GetSingleDottedName()); DnsResourceRecord record; DnsRecordParser parser = resp.Parser(); for (unsigned i = 0; i < kT0RecordCount; i ++) { EXPECT_FALSE(parser.AtEnd()); EXPECT_TRUE(parser.ReadRecord(&record)); } EXPECT_TRUE(parser.AtEnd()); EXPECT_FALSE(parser.ReadRecord(&record)); } TEST(DnsResponseTest, InitParseWithoutQueryNoQuestions) { const uint8_t response_data[] = { // Header 0xca, 0xfe, // ID 0x81, 0x80, // Standard query response, RA, no error 0x00, 0x00, // No question 0x00, 0x01, // 2 RRs (answers) 0x00, 0x00, // 0 authority RRs 0x00, 0x00, // 0 additional RRs // Answer 1 0x0a, 'c', 'o', 'd', 'e', 'r', 'e', 'v', 'i', 'e', 'w', 0x08, 'c', 'h', 'r', 'o', 'm', 'i', 'u', 'm', 0x03, 'o', 'r', 'g', 0x00, 0x00, 0x01, // TYPE is A. 0x00, 0x01, // CLASS is IN. 0x00, 0x00, // TTL (4 bytes) is 53 seconds. 0x00, 0x35, 0x00, 0x04, // RDLENGTH is 4 bytes. 0x4a, 0x7d, // RDATA is the IP: 74.125.95.121 0x5f, 0x79, }; DnsResponse resp; memcpy(resp.io_buffer()->data(), response_data, sizeof(response_data)); EXPECT_TRUE(resp.InitParseWithoutQuery(sizeof(response_data))); // Check header access. EXPECT_THAT(resp.id(), testing::Optional(0xcafe)); EXPECT_EQ(0x8180, resp.flags()); EXPECT_EQ(0x0, resp.rcode()); EXPECT_EQ(0u, resp.question_count()); EXPECT_EQ(0x1u, resp.answer_count()); EXPECT_THAT(resp.dotted_qnames(), testing::IsEmpty()); EXPECT_THAT(resp.qtypes(), testing::IsEmpty()); DnsResourceRecord record; DnsRecordParser parser = resp.Parser(); EXPECT_FALSE(parser.AtEnd()); EXPECT_TRUE(parser.ReadRecord(&record)); EXPECT_EQ("codereview.chromium.org", record.name); EXPECT_EQ(0x00000035u, record.ttl); EXPECT_EQ(dns_protocol::kTypeA, record.type); EXPECT_TRUE(parser.AtEnd()); EXPECT_FALSE(parser.ReadRecord(&record)); } TEST(DnsResponseTest, InitParseWithoutQueryInvalidFlags) { const uint8_t response_data[] = { // Header 0xca, 0xfe, // ID 0x01, 0x80, // RA, no error. Note the absence of the required QR bit. 0x00, 0x00, // No question 0x00, 0x01, // 2 RRs (answers) 0x00, 0x00, // 0 authority RRs 0x00, 0x00, // 0 additional RRs // Answer 1 0x0a, 'c', 'o', 'd', 'e', 'r', 'e', 'v', 'i', 'e', 'w', 0x08, 'c', 'h', 'r', 'o', 'm', 'i', 'u', 'm', 0x03, 'o', 'r', 'g', 0x00, 0x00, 0x01, // TYPE is A. 0x00, 0x01, // CLASS is IN. 0x00, 0x00, // TTL (4 bytes) is 53 seconds. 0x00, 0x35, 0x00, 0x04, // RDLENGTH is 4 bytes. 0x4a, 0x7d, // RDATA is the IP: 74.125.95.121 0x5f, 0x79, }; DnsResponse resp; memcpy(resp.io_buffer()->data(), response_data, sizeof(response_data)); EXPECT_FALSE(resp.InitParseWithoutQuery(sizeof(response_data))); EXPECT_THAT(resp.id(), testing::Optional(0xcafe)); } TEST(DnsResponseTest, InitParseWithoutQueryTwoQuestions) { const uint8_t response_data[] = { // Header 0xca, 0xfe, // ID 0x81, 0x80, // Standard query response, RA, no error 0x00, 0x02, // 2 questions 0x00, 0x01, // 2 RRs (answers) 0x00, 0x00, // 0 authority RRs 0x00, 0x00, // 0 additional RRs // Question 1 0x0a, 'c', 'o', 'd', 'e', 'r', 'e', 'v', 'i', 'e', 'w', 0x08, 'c', 'h', 'r', 'o', 'm', 'i', 'u', 'm', 0x03, 'o', 'r', 'g', 0x00, 0x00, 0x01, // TYPE is A. 0x00, 0x01, // CLASS is IN. // Question 2 0x0b, 'c', 'o', 'd', 'e', 'r', 'e', 'v', 'i', 'e', 'w', '2', 0xc0, 0x17, // pointer to "chromium.org" 0x00, 0x01, // TYPE is A. 0x00, 0x01, // CLASS is IN. // Answer 1 0xc0, 0x0c, // NAME is a pointer to name in Question section. 0x00, 0x01, // TYPE is A. 0x00, 0x01, // CLASS is IN. 0x00, 0x00, // TTL (4 bytes) is 53 seconds. 0x00, 0x35, 0x00, 0x04, // RDLENGTH is 4 bytes. 0x4a, 0x7d, // RDATA is the IP: 74.125.95.121 0x5f, 0x79, }; DnsResponse resp; memcpy(resp.io_buffer()->data(), response_data, sizeof(response_data)); EXPECT_TRUE(resp.InitParseWithoutQuery(sizeof(response_data))); // Check header access. EXPECT_EQ(0x8180, resp.flags()); EXPECT_EQ(0x0, resp.rcode()); EXPECT_EQ(2u, resp.question_count()); EXPECT_EQ(0x01u, resp.answer_count()); EXPECT_THAT(resp.dotted_qnames(), testing::ElementsAre("codereview.chromium.org", "codereview2.chromium.org")); EXPECT_THAT(resp.qtypes(), testing::ElementsAre(dns_protocol::kTypeA, dns_protocol::kTypeA)); DnsResourceRecord record; DnsRecordParser parser = resp.Parser(); EXPECT_FALSE(parser.AtEnd()); EXPECT_TRUE(parser.ReadRecord(&record)); EXPECT_EQ("codereview.chromium.org", record.name); EXPECT_EQ(0x35u, record.ttl); EXPECT_EQ(dns_protocol::kTypeA, record.type); EXPECT_TRUE(parser.AtEnd()); EXPECT_FALSE(parser.ReadRecord(&record)); } TEST(DnsResponseTest, InitParseWithoutQueryPacketTooShort) { const uint8_t response_data[] = { // Header 0xca, 0xfe, // ID 0x81, 0x80, // Standard query response, RA, no error 0x00, 0x00, // No question }; DnsResponse resp; memcpy(resp.io_buffer()->data(), response_data, sizeof(response_data)); EXPECT_FALSE(resp.InitParseWithoutQuery(sizeof(response_data))); } TEST(DnsResponseTest, InitParseAllowsQuestionWithLongName) { const char kResponseHeader[] = "\x02\x45" // ID=581 "\x81\x80" // Standard query response, RA, no error "\x00\x01" // 1 question "\x00\x00" // 0 answers "\x00\x00" // 0 authority records "\x00\x00"; // 0 additional records std::string dotted_name; const std::vector dns_name = BuildRfc1034Name(dns_protocol::kMaxNameLength, &dotted_name); std::string response_data(kResponseHeader, sizeof(kResponseHeader) - 1); response_data.append(reinterpret_cast(dns_name.data()), dns_name.size()); response_data.append( "\x00\x01" // TYPE=A "\x00\x01", // CLASS=IN) 4); DnsResponse resp1; memcpy(resp1.io_buffer()->data(), response_data.data(), response_data.size()); EXPECT_TRUE(resp1.InitParseWithoutQuery(response_data.size())); DnsQuery query(581, dns_name, dns_protocol::kTypeA); DnsResponse resp2(resp1.io_buffer(), response_data.size()); EXPECT_TRUE(resp2.InitParse(response_data.size(), query)); } // Tests against incorrect name length validation, which is anti-pattern #3 from // the "NAME:WRECK" report: // https://www.forescout.com/company/resources/namewreck-breaking-and-fixing-dns-implementations/ TEST(DnsResponseTest, InitParseRejectsQuestionWithTooLongName) { const char kResponseHeader[] = "\x02\x45" // ID=581 "\x81\x80" // Standard query response, RA, no error "\x00\x01" // 1 question "\x00\x00" // 0 answers "\x00\x00" // 0 authority records "\x00\x00"; // 0 additional records std::string dotted_name; const std::vector dns_name = BuildRfc1034Name(dns_protocol::kMaxNameLength + 1, &dotted_name); std::string response_data(kResponseHeader, sizeof(kResponseHeader) - 1); response_data.append(reinterpret_cast(dns_name.data()), dns_name.size()); response_data.append( "\x00\x01" // TYPE=A "\x00\x01", // CLASS=IN) 4); DnsResponse resp; memcpy(resp.io_buffer()->data(), response_data.data(), response_data.size()); EXPECT_FALSE(resp.InitParseWithoutQuery(response_data.size())); // Note that `DnsQuery` disallows construction without a valid name, so // `InitParse()` can never be tested with a `query` that matches against a // too-long name in the response. Test with an arbitrary valid query name to // ensure no issues if this code is exercised after receiving a response with // a too-long name. const char kQueryName[] = "\005query\004test"; DnsQuery query( /*id=*/581, base::as_byte_span(kQueryName), dns_protocol::kTypeA); EXPECT_FALSE(resp.InitParse(response_data.size(), query)); } // Test that `InitParse[...]()` rejects a response with a question name // extending past the end of the response. // Tests against incorrect name length validation, which is anti-pattern #3 from // the "NAME:WRECK" report: // https://www.forescout.com/company/resources/namewreck-breaking-and-fixing-dns-implementations/ TEST(DnsResponseTest, InitParseRejectsQuestionWithNonendedName) { const char kResponse[] = "\x02\x45" // ID "\x81\x80" // Standard query response, RA, no error "\x00\x01" // 1 question "\x00\x00" // 0 answers "\x00\x00" // 0 authority records "\x00\x00" // 0 additional records "\003www\006google\006test"; // Name extending past the end. DnsResponse resp; memcpy(resp.io_buffer()->data(), kResponse, sizeof(kResponse) - 1); EXPECT_FALSE(resp.InitParseWithoutQuery(sizeof(kResponse) - 1)); const char kQueryName[] = "\003www\006google\006testtt"; DnsQuery query( /*id=*/581, base::as_byte_span(kQueryName), dns_protocol::kTypeA); EXPECT_FALSE(resp.InitParse(sizeof(kResponse) - 1, query)); } // Test that `InitParse[...]()` rejects responses that do not contain at least // the claimed number of questions. // Tests against incorrect record count field validation, which is anti-pattern // #5 from the "NAME:WRECK" report: // https://www.forescout.com/company/resources/namewreck-breaking-and-fixing-dns-implementations/ TEST(DnsResponseTest, InitParseRejectsResponseWithMissingQuestions) { const char kResponse[] = "\x02\x45" // ID "\x81\x80" // Standard query response, RA, no error "\x00\x03" // 3 questions "\x00\x00" // 0 answers "\x00\x00" // 0 authority records "\x00\x00" // 0 additional records "\003www\006google\004test\000" // www.google.test "\x00\x01" // TYPE=A "\x00\x01" // CLASS=IN "\003www\010chromium\004test\000" // www.chromium.test "\x00\x01" // TYPE=A "\x00\x01"; // CLASS=IN // Missing third question. DnsResponse resp; memcpy(resp.io_buffer()->data(), kResponse, sizeof(kResponse) - 1); EXPECT_FALSE(resp.InitParseWithoutQuery(sizeof(kResponse) - 1)); const char kQueryName[] = "\003www\006google\004test"; DnsQuery query( /*id=*/581, base::as_byte_span(kQueryName), dns_protocol::kTypeA); EXPECT_FALSE(resp.InitParse(sizeof(kResponse) - 1, query)); } // Test that a parsed DnsResponse only allows parsing the number of records // claimed in the response header. // Tests against incorrect record count field validation, which is anti-pattern // #5 from the "NAME:WRECK" report: // https://www.forescout.com/company/resources/namewreck-breaking-and-fixing-dns-implementations/ TEST(DnsResponseTest, ParserLimitedToNumClaimedRecords) { const char kResponse[] = "\x02\x45" // ID "\x81\x80" // Standard query response, RA, no error "\x00\x01" // 1 question "\x00\x01" // 1 answers "\x00\x02" // 2 authority records "\x00\x01" // 1 additional records "\003www\006google\004test\000" "\x00\x01" // TYPE=A "\x00\x01" // CLASS=IN // 6 total records. "\003www\006google\004test\000" "\x00\x01" // TYPE=A "\x00\x01" // CLASS=IN "\x00\x01\x51\x80" // TTL=1 day "\x00\x04" // RDLENGTH=4 bytes "\xc0\xa8\x00\x01" // 192.168.0.1 "\003www\010chromium\004test\000" "\x00\x01" // TYPE=A "\x00\x01" // CLASS=IN "\x00\x01\x51\x80" // TTL=1 day "\x00\x04" // RDLENGTH=4 bytes "\xc0\xa8\x00\x02" // 192.168.0.2 "\003www\007google1\004test\000" "\x00\x01" // TYPE=A "\x00\x01" // CLASS=IN "\x00\x01\x51\x80" // TTL=1 day "\x00\x04" // RDLENGTH=4 bytes "\xc0\xa8\x00\x03" // 192.168.0.3 "\003www\011chromium1\004test\000" "\x00\x01" // TYPE=A "\x00\x01" // CLASS=IN "\x00\x01\x51\x80" // TTL=1 day "\x00\x04" // RDLENGTH=4 bytes "\xc0\xa8\x00\x04" // 192.168.0.4 "\003www\007google2\004test\000" "\x00\x01" // TYPE=A "\x00\x01" // CLASS=IN "\x00\x01\x51\x80" // TTL=1 day "\x00\x04" // RDLENGTH=4 bytes "\xc0\xa8\x00\x05" // 192.168.0.5 "\003www\011chromium2\004test\000" "\x00\x01" // TYPE=A "\x00\x01" // CLASS=IN "\x00\x01\x51\x80" // TTL=1 day "\x00\x04" // RDLENGTH=4 bytes "\xc0\xa8\x00\x06"; // 192.168.0.6 DnsResponse resp1; memcpy(resp1.io_buffer()->data(), kResponse, sizeof(kResponse) - 1); ASSERT_TRUE(resp1.InitParseWithoutQuery(sizeof(kResponse) - 1)); DnsRecordParser parser1 = resp1.Parser(); ASSERT_TRUE(parser1.IsValid()); // Response header only claims 4 records, so expect parser to only allow // parsing that many, ignoring extra records in the data. DnsResourceRecord record; EXPECT_TRUE(parser1.ReadRecord(&record)); EXPECT_TRUE(parser1.ReadRecord(&record)); EXPECT_TRUE(parser1.ReadRecord(&record)); EXPECT_TRUE(parser1.ReadRecord(&record)); EXPECT_FALSE(parser1.ReadRecord(&record)); EXPECT_FALSE(parser1.ReadRecord(&record)); // Repeat using InitParse() DnsResponse resp2; memcpy(resp2.io_buffer()->data(), kResponse, sizeof(kResponse) - 1); const char kQueryName[] = "\003www\006google\004test"; DnsQuery query( /*id=*/581, base::as_byte_span(kQueryName), dns_protocol::kTypeA); ASSERT_TRUE(resp2.InitParse(sizeof(kResponse) - 1, query)); DnsRecordParser parser2 = resp2.Parser(); ASSERT_TRUE(parser2.IsValid()); // Response header only claims 4 records, so expect parser to only allow // parsing that many, ignoring extra records in the data. EXPECT_TRUE(parser2.ReadRecord(&record)); EXPECT_TRUE(parser2.ReadRecord(&record)); EXPECT_TRUE(parser2.ReadRecord(&record)); EXPECT_TRUE(parser2.ReadRecord(&record)); EXPECT_FALSE(parser2.ReadRecord(&record)); EXPECT_FALSE(parser2.ReadRecord(&record)); } // Test that a parsed DnsResponse does not allow parsing past the end of the // input, even if more records are claimed in the response header. // Tests against incorrect record count field validation, which is anti-pattern // #5 from the "NAME:WRECK" report: // https://www.forescout.com/company/resources/namewreck-breaking-and-fixing-dns-implementations/ TEST(DnsResponseTest, ParserLimitedToBufferSize) { const char kResponse[] = "\x02\x45" // ID "\x81\x80" // Standard query response, RA, no error "\x00\x01" // 1 question "\x00\x01" // 1 answers "\x00\x02" // 2 authority records "\x00\x01" // 1 additional records "\003www\006google\004test\000" "\x00\x01" // TYPE=A "\x00\x01" // CLASS=IN // 2 total records. "\003www\006google\004test\000" "\x00\x01" // TYPE=A "\x00\x01" // CLASS=IN "\x00\x01\x51\x80" // TTL=1 day "\x00\x04" // RDLENGTH=4 bytes "\xc0\xa8\x00\x01" // 192.168.0.1 "\003www\010chromium\004test\000" "\x00\x01" // TYPE=A "\x00\x01" // CLASS=IN "\x00\x01\x51\x80" // TTL=1 day "\x00\x04" // RDLENGTH=4 bytes "\xc0\xa8\x00\x02"; // 192.168.0.2 DnsResponse resp1; memcpy(resp1.io_buffer()->data(), kResponse, sizeof(kResponse) - 1); ASSERT_TRUE(resp1.InitParseWithoutQuery(sizeof(kResponse) - 1)); DnsRecordParser parser1 = resp1.Parser(); ASSERT_TRUE(parser1.IsValid()); // Response header claims 4 records, but only 2 present in input. DnsResourceRecord record; EXPECT_TRUE(parser1.ReadRecord(&record)); EXPECT_TRUE(parser1.ReadRecord(&record)); EXPECT_FALSE(parser1.ReadRecord(&record)); EXPECT_FALSE(parser1.ReadRecord(&record)); // Repeat using InitParse() DnsResponse resp2; memcpy(resp2.io_buffer()->data(), kResponse, sizeof(kResponse) - 1); ASSERT_TRUE(resp2.InitParseWithoutQuery(sizeof(kResponse) - 1)); DnsRecordParser parser2 = resp2.Parser(); ASSERT_TRUE(parser2.IsValid()); // Response header claims 4 records, but only 2 present in input. EXPECT_TRUE(parser2.ReadRecord(&record)); EXPECT_TRUE(parser2.ReadRecord(&record)); EXPECT_FALSE(parser2.ReadRecord(&record)); EXPECT_FALSE(parser2.ReadRecord(&record)); } TEST(DnsResponseWriteTest, SingleARecordAnswer) { const uint8_t response_data[] = { 0x12, 0x34, // ID 0x84, 0x00, // flags, response with authoritative answer 0x00, 0x00, // number of questions 0x00, 0x01, // number of answer rr 0x00, 0x00, // number of name server rr 0x00, 0x00, // number of additional rr 0x03, 'w', 'w', 'w', 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm', 0x00, // null label 0x00, 0x01, // type A Record 0x00, 0x01, // class IN 0x00, 0x00, 0x00, 0x78, // TTL, 120 seconds 0x00, 0x04, // rdlength, 32 bits 0xc0, 0xa8, 0x00, 0x01, // 192.168.0.1 }; net::DnsResourceRecord answer; answer.name = "www.example.com"; answer.type = dns_protocol::kTypeA; answer.klass = dns_protocol::kClassIN; answer.ttl = 120; // 120 seconds. answer.SetOwnedRdata(std::string("\xc0\xa8\x00\x01", 4)); std::vector answers(1, answer); DnsResponse response(0x1234 /* response_id */, true /* is_authoritative*/, answers, {} /* authority_records */, {} /* additional records */, std::nullopt); ASSERT_NE(nullptr, response.io_buffer()); EXPECT_TRUE(response.IsValid()); std::string expected_response(reinterpret_cast(response_data), sizeof(response_data)); std::string actual_response(response.io_buffer()->data(), response.io_buffer_size()); EXPECT_EQ(expected_response, actual_response); } TEST(DnsResponseWriteTest, SingleARecordAnswerWithFinalDotInName) { const uint8_t response_data[] = { 0x12, 0x34, // ID 0x84, 0x00, // flags, response with authoritative answer 0x00, 0x00, // number of questions 0x00, 0x01, // number of answer rr 0x00, 0x00, // number of name server rr 0x00, 0x00, // number of additional rr 0x03, 'w', 'w', 'w', 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm', 0x00, // null label 0x00, 0x01, // type A Record 0x00, 0x01, // class IN 0x00, 0x00, 0x00, 0x78, // TTL, 120 seconds 0x00, 0x04, // rdlength, 32 bits 0xc0, 0xa8, 0x00, 0x01, // 192.168.0.1 }; net::DnsResourceRecord answer; answer.name = "www.example.com."; // FQDN with the final dot. answer.type = dns_protocol::kTypeA; answer.klass = dns_protocol::kClassIN; answer.ttl = 120; // 120 seconds. answer.SetOwnedRdata(std::string("\xc0\xa8\x00\x01", 4)); std::vector answers(1, answer); DnsResponse response(0x1234 /* response_id */, true /* is_authoritative*/, answers, {} /* authority_records */, {} /* additional records */, std::nullopt); ASSERT_NE(nullptr, response.io_buffer()); EXPECT_TRUE(response.IsValid()); std::string expected_response(reinterpret_cast(response_data), sizeof(response_data)); std::string actual_response(response.io_buffer()->data(), response.io_buffer_size()); EXPECT_EQ(expected_response, actual_response); } TEST(DnsResponseWriteTest, SingleARecordAnswerWithQuestion) { const uint8_t response_data[] = { 0x12, 0x34, // ID 0x84, 0x00, // flags, response with authoritative answer 0x00, 0x01, // number of questions 0x00, 0x01, // number of answer rr 0x00, 0x00, // number of name server rr 0x00, 0x00, // number of additional rr 0x03, 'w', 'w', 'w', 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm', 0x00, // null label 0x00, 0x01, // type A Record 0x00, 0x01, // class IN 0x03, 'w', 'w', 'w', 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm', 0x00, // null label 0x00, 0x01, // type A Record 0x00, 0x01, // class IN 0x00, 0x00, 0x00, 0x78, // TTL, 120 seconds 0x00, 0x04, // rdlength, 32 bits 0xc0, 0xa8, 0x00, 0x01, // 192.168.0.1 }; std::string dotted_name("www.example.com"); std::optional> dns_name = dns_names_util::DottedNameToNetwork(dotted_name); ASSERT_TRUE(dns_name.has_value()); OptRecordRdata opt_rdata; opt_rdata.AddOpt( OptRecordRdata::UnknownOpt::CreateForTesting(255, "\xde\xad\xbe\xef")); std::optional query; query.emplace(0x1234 /* id */, dns_name.value(), dns_protocol::kTypeA, &opt_rdata); net::DnsResourceRecord answer; answer.name = dotted_name; answer.type = dns_protocol::kTypeA; answer.klass = dns_protocol::kClassIN; answer.ttl = 120; // 120 seconds. answer.SetOwnedRdata(std::string("\xc0\xa8\x00\x01", 4)); std::vector answers(1, answer); DnsResponse response(0x1234 /* id */, true /* is_authoritative*/, answers, {} /* authority_records */, {} /* additional records */, query); ASSERT_NE(nullptr, response.io_buffer()); EXPECT_TRUE(response.IsValid()); std::string expected_response(reinterpret_cast(response_data), sizeof(response_data)); std::string actual_response(response.io_buffer()->data(), response.io_buffer_size()); EXPECT_EQ(expected_response, actual_response); } TEST(DnsResponseWriteTest, SingleAnswerWithQuestionConstructedFromSizeInflatedQuery) { const uint8_t response_data[] = { 0x12, 0x34, // ID 0x84, 0x00, // flags, response with authoritative answer 0x00, 0x01, // number of questions 0x00, 0x01, // number of answer rr 0x00, 0x00, // number of name server rr 0x00, 0x00, // number of additional rr 0x03, 'w', 'w', 'w', 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm', 0x00, // null label 0x00, 0x01, // type A Record 0x00, 0x01, // class IN 0x03, 'w', 'w', 'w', 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm', 0x00, // null label 0x00, 0x01, // type A Record 0x00, 0x01, // class IN 0x00, 0x00, 0x00, 0x78, // TTL, 120 seconds 0x00, 0x04, // rdlength, 32 bits 0xc0, 0xa8, 0x00, 0x01, // 192.168.0.1 }; std::string dotted_name("www.example.com"); std::optional> dns_name = dns_names_util::DottedNameToNetwork(dotted_name); ASSERT_TRUE(dns_name.has_value()); size_t buf_size = sizeof(dns_protocol::Header) + dns_name.value().size() + 2 /* qtype */ + 2 /* qclass */ + 10 /* extra bytes that inflate the internal buffer of a query */; auto buf = base::MakeRefCounted(buf_size); std::ranges::fill(buf->span(), char{0}); auto writer = base::SpanWriter(base::as_writable_bytes(buf->span())); writer.WriteU16BigEndian(0x1234); // id writer.WriteU16BigEndian(0); // flags, is query writer.WriteU16BigEndian(1); // qdcount writer.WriteU16BigEndian(0); // ancount writer.WriteU16BigEndian(0); // nscount writer.WriteU16BigEndian(0); // arcount writer.Write(dns_name.value()); // qname writer.WriteU16BigEndian(dns_protocol::kTypeA); // qtype writer.WriteU16BigEndian(dns_protocol::kClassIN); // qclass // buf contains 10 extra zero bytes. std::optional query; query.emplace(buf); query->Parse(buf_size); net::DnsResourceRecord answer; answer.name = dotted_name; answer.type = dns_protocol::kTypeA; answer.klass = dns_protocol::kClassIN; answer.ttl = 120; // 120 seconds. answer.SetOwnedRdata(std::string("\xc0\xa8\x00\x01", 4)); std::vector answers(1, answer); DnsResponse response(0x1234 /* id */, true /* is_authoritative*/, answers, {} /* authority_records */, {} /* additional records */, query); ASSERT_NE(nullptr, response.io_buffer()); EXPECT_TRUE(response.IsValid()); std::string expected_response(reinterpret_cast(response_data), sizeof(response_data)); std::string actual_response(response.io_buffer()->data(), response.io_buffer_size()); EXPECT_EQ(expected_response, actual_response); } TEST(DnsResponseWriteTest, SingleQuadARecordAnswer) { const uint8_t response_data[] = { 0x12, 0x34, // ID 0x84, 0x00, // flags, response with authoritative answer 0x00, 0x00, // number of questions 0x00, 0x01, // number of answer rr 0x00, 0x00, // number of name server rr 0x00, 0x00, // number of additional rr 0x03, 'w', 'w', 'w', 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm', 0x00, // null label 0x00, 0x1c, // type AAAA Record 0x00, 0x01, // class IN 0x00, 0x00, 0x00, 0x78, // TTL, 120 seconds 0x00, 0x10, // rdlength, 128 bits 0xfd, 0x12, 0x34, 0x56, 0x78, 0x9a, 0x00, 0x01, // fd12:3456:789a:1::1 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, }; net::DnsResourceRecord answer; answer.name = "www.example.com"; answer.type = dns_protocol::kTypeAAAA; answer.klass = dns_protocol::kClassIN; answer.ttl = 120; // 120 seconds. answer.SetOwnedRdata(std::string( "\xfd\x12\x34\x56\x78\x9a\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01", 16)); std::vector answers(1, answer); DnsResponse response(0x1234 /* id */, true /* is_authoritative*/, answers, {} /* authority_records */, {} /* additional records */, std::nullopt); ASSERT_NE(nullptr, response.io_buffer()); EXPECT_TRUE(response.IsValid()); std::string expected_response(reinterpret_cast(response_data), sizeof(response_data)); std::string actual_response(response.io_buffer()->data(), response.io_buffer_size()); EXPECT_EQ(expected_response, actual_response); } TEST(DnsResponseWriteTest, SingleARecordAnswerWithQuestionAndNsecAdditionalRecord) { const uint8_t response_data[] = { 0x12, 0x34, // ID 0x84, 0x00, // flags, response with authoritative answer 0x00, 0x01, // number of questions 0x00, 0x01, // number of answer rr 0x00, 0x00, // number of name server rr 0x00, 0x01, // number of additional rr 0x03, 'w', 'w', 'w', 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm', 0x00, // null label 0x00, 0x01, // type A Record 0x00, 0x01, // class IN 0x03, 'w', 'w', 'w', 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm', 0x00, // null label 0x00, 0x01, // type A Record 0x00, 0x01, // class IN 0x00, 0x00, 0x00, 0x78, // TTL, 120 seconds 0x00, 0x04, // rdlength, 32 bits 0xc0, 0xa8, 0x00, 0x01, // 192.168.0.1 0x03, 'w', 'w', 'w', 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm', 0x00, // null label 0x00, 0x2f, // type NSEC Record 0x00, 0x01, // class IN 0x00, 0x00, 0x00, 0x78, // TTL, 120 seconds 0x00, 0x05, // rdlength, 5 bytes 0xc0, 0x0c, // pointer to the previous "www.example.com" 0x00, 0x01, 0x40, // type bit map of type A: window block 0, bitmap // length 1, bitmap with bit 1 set }; std::string dotted_name("www.example.com"); std::optional> dns_name = dns_names_util::DottedNameToNetwork(dotted_name); ASSERT_TRUE(dns_name.has_value()); std::optional query; query.emplace(0x1234 /* id */, dns_name.value(), dns_protocol::kTypeA); net::DnsResourceRecord answer; answer.name = dotted_name; answer.type = dns_protocol::kTypeA; answer.klass = dns_protocol::kClassIN; answer.ttl = 120; // 120 seconds. answer.SetOwnedRdata(std::string("\xc0\xa8\x00\x01", 4)); std::vector answers(1, answer); net::DnsResourceRecord additional_record; additional_record.name = dotted_name; additional_record.type = dns_protocol::kTypeNSEC; additional_record.klass = dns_protocol::kClassIN; additional_record.ttl = 120; // 120 seconds. // Bitmap for "www.example.com" with type A set. additional_record.SetOwnedRdata(std::string("\xc0\x0c\x00\x01\x40", 5)); std::vector additional_records(1, additional_record); DnsResponse response(0x1234 /* id */, true /* is_authoritative*/, answers, {} /* authority_records */, additional_records, query); ASSERT_NE(nullptr, response.io_buffer()); EXPECT_TRUE(response.IsValid()); std::string expected_response(reinterpret_cast(response_data), sizeof(response_data)); std::string actual_response(response.io_buffer()->data(), response.io_buffer_size()); EXPECT_EQ(expected_response, actual_response); } TEST(DnsResponseWriteTest, TwoAnswersWithAAndQuadARecords) { const uint8_t response_data[] = { 0x12, 0x34, // ID 0x84, 0x00, // flags, response with authoritative answer 0x00, 0x00, // number of questions 0x00, 0x02, // number of answer rr 0x00, 0x00, // number of name server rr 0x00, 0x00, // number of additional rr 0x03, 'w', 'w', 'w', 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm', 0x00, // null label 0x00, 0x01, // type A Record 0x00, 0x01, // class IN 0x00, 0x00, 0x00, 0x78, // TTL, 120 seconds 0x00, 0x04, // rdlength, 32 bits 0xc0, 0xa8, 0x00, 0x01, // 192.168.0.1 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'o', 'r', 'g', 0x00, // null label 0x00, 0x1c, // type AAAA Record 0x00, 0x01, // class IN 0x00, 0x00, 0x00, 0x3c, // TTL, 60 seconds 0x00, 0x10, // rdlength, 128 bits 0xfd, 0x12, 0x34, 0x56, 0x78, 0x9a, 0x00, 0x01, // fd12:3456:789a:1::1 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, }; net::DnsResourceRecord answer1; answer1.name = "www.example.com"; answer1.type = dns_protocol::kTypeA; answer1.klass = dns_protocol::kClassIN; answer1.ttl = 120; // 120 seconds. answer1.SetOwnedRdata(std::string("\xc0\xa8\x00\x01", 4)); net::DnsResourceRecord answer2; answer2.name = "example.org"; answer2.type = dns_protocol::kTypeAAAA; answer2.klass = dns_protocol::kClassIN; answer2.ttl = 60; answer2.SetOwnedRdata(std::string( "\xfd\x12\x34\x56\x78\x9a\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01", 16)); std::vector answers(2); answers[0] = answer1; answers[1] = answer2; DnsResponse response(0x1234 /* id */, true /* is_authoritative*/, answers, {} /* authority_records */, {} /* additional records */, std::nullopt); ASSERT_NE(nullptr, response.io_buffer()); EXPECT_TRUE(response.IsValid()); std::string expected_response(reinterpret_cast(response_data), sizeof(response_data)); std::string actual_response(response.io_buffer()->data(), response.io_buffer_size()); EXPECT_EQ(expected_response, actual_response); } TEST(DnsResponseWriteTest, AnswerWithAuthorityRecord) { const uint8_t response_data[] = { 0x12, 0x35, // ID 0x84, 0x00, // flags, response with authoritative answer 0x00, 0x00, // number of questions 0x00, 0x00, // number of answer rr 0x00, 0x01, // number of name server rr 0x00, 0x00, // number of additional rr 0x03, 'w', 'w', 'w', 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm', 0x00, // null label 0x00, 0x01, // type A Record 0x00, 0x01, // class IN 0x00, 0x00, 0x00, 0x78, // TTL, 120 seconds 0x00, 0x04, // rdlength, 32 bits 0xc0, 0xa8, 0x00, 0x01, // 192.168.0.1 }; DnsResourceRecord record; record.name = "www.example.com"; record.type = dns_protocol::kTypeA; record.klass = dns_protocol::kClassIN; record.ttl = 120; // 120 seconds. record.SetOwnedRdata(std::string("\xc0\xa8\x00\x01", 4)); std::vector authority_records(1, record); DnsResponse response(0x1235 /* response_id */, true /* is_authoritative*/, {} /* answers */, authority_records, {} /* additional records */, std::nullopt); ASSERT_NE(nullptr, response.io_buffer()); EXPECT_TRUE(response.IsValid()); std::string expected_response(reinterpret_cast(response_data), sizeof(response_data)); std::string actual_response(response.io_buffer()->data(), response.io_buffer_size()); EXPECT_EQ(expected_response, actual_response); } TEST(DnsResponseWriteTest, AnswerWithRcode) { const uint8_t response_data[] = { 0x12, 0x12, // ID 0x80, 0x03, // flags (response with non-existent domain) 0x00, 0x00, // number of questions 0x00, 0x00, // number of answer rr 0x00, 0x00, // number of name server rr 0x00, 0x00, // number of additional rr }; DnsResponse response(0x1212 /* response_id */, false /* is_authoritative*/, {} /* answers */, {} /* authority_records */, {} /* additional records */, std::nullopt, dns_protocol::kRcodeNXDOMAIN); ASSERT_NE(nullptr, response.io_buffer()); EXPECT_TRUE(response.IsValid()); std::string expected_response(reinterpret_cast(response_data), sizeof(response_data)); std::string actual_response(response.io_buffer()->data(), response.io_buffer_size()); EXPECT_EQ(expected_response, actual_response); EXPECT_EQ(dns_protocol::kRcodeNXDOMAIN, response.rcode()); } // CNAME answers are always allowed for any question. TEST(DnsResponseWriteTest, AAAAQuestionAndCnameAnswer) { const std::string kName = "www.example.com"; std::optional> dns_name = dns_names_util::DottedNameToNetwork(kName); ASSERT_TRUE(dns_name.has_value()); DnsResourceRecord answer; answer.name = kName; answer.type = dns_protocol::kTypeCNAME; answer.klass = dns_protocol::kClassIN; answer.ttl = 120; // 120 seconds. answer.SetOwnedRdata( std::string(reinterpret_cast(dns_name.value().data()), dns_name.value().size())); std::vector answers(1, answer); std::optional query(std::in_place, 114 /* id */, dns_name.value(), dns_protocol::kTypeAAAA); DnsResponse response(114 /* response_id */, true /* is_authoritative*/, answers, {} /* authority_records */, {} /* additional records */, query); EXPECT_TRUE(response.IsValid()); } TEST(DnsResponseWriteTest, WrittenResponseCanBeParsed) { std::string dotted_name("www.example.com"); net::DnsResourceRecord answer; answer.name = dotted_name; answer.type = dns_protocol::kTypeA; answer.klass = dns_protocol::kClassIN; answer.ttl = 120; // 120 seconds. answer.SetOwnedRdata(std::string("\xc0\xa8\x00\x01", 4)); std::vector answers(1, answer); net::DnsResourceRecord additional_record; additional_record.name = dotted_name; additional_record.type = dns_protocol::kTypeNSEC; additional_record.klass = dns_protocol::kClassIN; additional_record.ttl = 120; // 120 seconds. additional_record.SetOwnedRdata(std::string("\xc0\x0c\x00\x01\x04", 5)); std::vector additional_records(1, additional_record); DnsResponse response(0x1234 /* response_id */, true /* is_authoritative*/, answers, {} /* authority_records */, additional_records, std::nullopt); ASSERT_NE(nullptr, response.io_buffer()); EXPECT_TRUE(response.IsValid()); EXPECT_THAT(response.id(), testing::Optional(0x1234)); EXPECT_EQ(1u, response.answer_count()); EXPECT_EQ(1u, response.additional_answer_count()); auto parser = response.Parser(); net::DnsResourceRecord parsed_record; EXPECT_TRUE(parser.ReadRecord(&parsed_record)); // Answer with an A record. EXPECT_EQ(answer.name, parsed_record.name); EXPECT_EQ(answer.type, parsed_record.type); EXPECT_EQ(answer.klass, parsed_record.klass); EXPECT_EQ(answer.ttl, parsed_record.ttl); EXPECT_EQ(answer.owned_rdata, parsed_record.rdata); // Additional NSEC record. EXPECT_TRUE(parser.ReadRecord(&parsed_record)); EXPECT_EQ(additional_record.name, parsed_record.name); EXPECT_EQ(additional_record.type, parsed_record.type); EXPECT_EQ(additional_record.klass, parsed_record.klass); EXPECT_EQ(additional_record.ttl, parsed_record.ttl); EXPECT_EQ(additional_record.owned_rdata, parsed_record.rdata); } TEST(DnsResponseWriteTest, CreateEmptyNoDataResponse) { DnsResponse response = DnsResponse::CreateEmptyNoDataResponse( /*id=*/4, /*is_authoritative=*/true, base::as_byte_span("\x04name\x04test\x00"), dns_protocol::kTypeA); EXPECT_TRUE(response.IsValid()); EXPECT_THAT(response.id(), testing::Optional(4)); EXPECT_TRUE(response.flags() & dns_protocol::kFlagAA); EXPECT_EQ(response.question_count(), 1u); EXPECT_EQ(response.answer_count(), 0u); EXPECT_EQ(response.authority_count(), 0u); EXPECT_EQ(response.additional_answer_count(), 0u); EXPECT_THAT(response.qtypes(), testing::ElementsAre(dns_protocol::kTypeA)); EXPECT_THAT(response.dotted_qnames(), testing::ElementsAre("name.test")); } } // namespace } // namespace net