blob: a921897804267167f09e60e80885a4471d1fb7e1 [file] [log] [blame]
// 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