| // 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 <algorithm> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "base/big_endian.h" |
| #include "base/check.h" |
| #include "base/containers/span.h" |
| #include "base/strings/string_piece.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" |
| #include "third_party/abseil-cpp/absl/types/optional.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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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, sizeof(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(base::StringPiece("\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<uint8_t> dns_name = |
| BuildRfc1034Name(dns_protocol::kMaxNameLength, &dotted_name); |
| |
| std::string data(reinterpret_cast<const char*>(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<uint8_t> dns_name = |
| BuildRfc1034Name(dns_protocol::kMaxNameLength + 1, &dotted_name); |
| |
| std::string data(reinterpret_cast<const char*>(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<DnsQuery>( |
| 0xcafe, base::as_bytes(base::make_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<DnsQuery> 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<DnsQuery>( |
| 0xcafe, base::as_bytes(base::make_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. |
| absl::optional<std::vector<uint8_t>> 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<DnsQuery>( |
| 0xcafe, base::as_bytes(base::make_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_bytes(base::make_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_bytes(base::make_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<uint8_t> dns_name = |
| BuildRfc1034Name(dns_protocol::kMaxNameLength, &dotted_name); |
| |
| std::string response_data(kResponseHeader, sizeof(kResponseHeader) - 1); |
| response_data.append(reinterpret_cast<const char*>(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<uint8_t> dns_name = |
| BuildRfc1034Name(dns_protocol::kMaxNameLength + 1, &dotted_name); |
| |
| std::string response_data(kResponseHeader, sizeof(kResponseHeader) - 1); |
| response_data.append(reinterpret_cast<const char*>(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_bytes(base::make_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_bytes(base::make_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_bytes(base::make_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_bytes(base::make_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<DnsResourceRecord> answers(1, answer); |
| DnsResponse response(0x1234 /* response_id */, true /* is_authoritative*/, |
| answers, {} /* authority_records */, |
| {} /* additional records */, absl::nullopt); |
| ASSERT_NE(nullptr, response.io_buffer()); |
| EXPECT_TRUE(response.IsValid()); |
| std::string expected_response(reinterpret_cast<const char*>(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<DnsResourceRecord> answers(1, answer); |
| DnsResponse response(0x1234 /* response_id */, true /* is_authoritative*/, |
| answers, {} /* authority_records */, |
| {} /* additional records */, absl::nullopt); |
| ASSERT_NE(nullptr, response.io_buffer()); |
| EXPECT_TRUE(response.IsValid()); |
| std::string expected_response(reinterpret_cast<const char*>(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"); |
| absl::optional<std::vector<uint8_t>> 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")); |
| |
| absl::optional<DnsQuery> 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<DnsResourceRecord> 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<const char*>(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"); |
| absl::optional<std::vector<uint8_t>> 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<IOBufferWithSize>(buf_size); |
| memset(buf->data(), 0, buf->size()); |
| base::BigEndianWriter writer(buf->data(), buf_size); |
| writer.WriteU16(0x1234); // id |
| writer.WriteU16(0); // flags, is query |
| writer.WriteU16(1); // qdcount |
| writer.WriteU16(0); // ancount |
| writer.WriteU16(0); // nscount |
| writer.WriteU16(0); // arcount |
| writer.WriteBytes(dns_name.value().data(), dns_name.value().size()); // qname |
| writer.WriteU16(dns_protocol::kTypeA); // qtype |
| writer.WriteU16(dns_protocol::kClassIN); // qclass |
| // buf contains 10 extra zero bytes. |
| absl::optional<DnsQuery> 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<DnsResourceRecord> 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<const char*>(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<DnsResourceRecord> answers(1, answer); |
| DnsResponse response(0x1234 /* id */, true /* is_authoritative*/, answers, |
| {} /* authority_records */, {} /* additional records */, |
| absl::nullopt); |
| ASSERT_NE(nullptr, response.io_buffer()); |
| EXPECT_TRUE(response.IsValid()); |
| std::string expected_response(reinterpret_cast<const char*>(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"); |
| absl::optional<std::vector<uint8_t>> dns_name = |
| dns_names_util::DottedNameToNetwork(dotted_name); |
| ASSERT_TRUE(dns_name.has_value()); |
| absl::optional<DnsQuery> 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<DnsResourceRecord> 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<DnsResourceRecord> 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<const char*>(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<DnsResourceRecord> answers(2); |
| answers[0] = answer1; |
| answers[1] = answer2; |
| DnsResponse response(0x1234 /* id */, true /* is_authoritative*/, answers, |
| {} /* authority_records */, {} /* additional records */, |
| absl::nullopt); |
| ASSERT_NE(nullptr, response.io_buffer()); |
| EXPECT_TRUE(response.IsValid()); |
| std::string expected_response(reinterpret_cast<const char*>(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<DnsResourceRecord> authority_records(1, record); |
| DnsResponse response(0x1235 /* response_id */, true /* is_authoritative*/, |
| {} /* answers */, authority_records, |
| {} /* additional records */, absl::nullopt); |
| ASSERT_NE(nullptr, response.io_buffer()); |
| EXPECT_TRUE(response.IsValid()); |
| std::string expected_response(reinterpret_cast<const char*>(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 */, absl::nullopt, |
| dns_protocol::kRcodeNXDOMAIN); |
| ASSERT_NE(nullptr, response.io_buffer()); |
| EXPECT_TRUE(response.IsValid()); |
| std::string expected_response(reinterpret_cast<const char*>(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"; |
| absl::optional<std::vector<uint8_t>> 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<char*>(dns_name.value().data()), |
| dns_name.value().size())); |
| std::vector<DnsResourceRecord> answers(1, answer); |
| |
| absl::optional<DnsQuery> query(absl::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<DnsResourceRecord> 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<DnsResourceRecord> additional_records(1, additional_record); |
| DnsResponse response(0x1234 /* response_id */, true /* is_authoritative*/, |
| answers, {} /* authority_records */, additional_records, |
| absl::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_bytes(base::make_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 |