#include "net/dns/dns_response_result_extractor.h"
#include <algorithm>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "base/test/metrics/histogram_tester.h"
#include "base/test/simple_test_clock.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/time/time.h"
#include "net/base/connection_endpoint_metadata_test_util.h"
#include "net/base/host_port_pair.h"
#include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.h"
#include "net/dns/dns_query.h"
#include "net/dns/dns_response.h"
#include "net/dns/dns_test_util.h"
#include "net/dns/host_cache.h"
#include "net/dns/host_resolver_internal_result.h"
#include "net/dns/host_resolver_internal_result_test_util.h"
#include "net/dns/host_resolver_results_test_util.h"
#include "net/dns/public/dns_protocol.h"
#include "net/dns/public/dns_query_type.h"
#include "net/test/gtest_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
namespace {
using ::testing::AllOf;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::Ne;
using ::testing::Optional;
using ::testing::Pair;
using ::testing::Pointee;
using ::testing::ResultOf;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAre;
using ExtractionError = DnsResponseResultExtractor::ExtractionError;
using ResultsOrError = DnsResponseResultExtractor::ResultsOrError;
constexpr HostResolverInternalResult::Source kDnsSource =
HostResolverInternalResult::Source::kDns;
class DnsResponseResultExtractorTest : public ::testing::Test {
protected:
base::SimpleTestClock clock_;
base::SimpleTestTickClock tick_clock_;
base::HistogramTester histogram_tester_;
};
constexpr uint8_t fake_test_rdata[] = {'f', 'a', 'k', 'e', ' ',
'r', 'd', 'a', 't', 'a'};
constexpr uint8_t malformed_test_rdata[] = {
'm', 'a', 'l', 'f', 'o', 'r', 'm', 'e', 'd', ' ', 'r', 'd', 'a', 't', 'a'};
TEST_F(DnsResponseResultExtractorTest, ExtractsSingleARecord) {
constexpr char kName[] = "address.test";
const IPAddress kExpected(192, 168, 0, 1);
DnsResponse response = BuildTestDnsAddressResponse(kName, kExpected);
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::A,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalDataResult(
kName, DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
ElementsAre(IPEndPoint(kExpected, 0))))));
histogram_tester_.ExpectUniqueSample(
DnsResponseResultExtractor::kHasValidCnameRecordsHistogram, false, 1);
}
TEST_F(DnsResponseResultExtractorTest, ExtractsSingleAAAARecord) {
constexpr char kName[] = "address.test";
IPAddress expected;
CHECK(expected.AssignFromIPLiteral("2001:4860:4860::8888"));
DnsResponse response = BuildTestDnsAddressResponse(kName, expected);
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::AAAA,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalDataResult(
kName, DnsQueryType::AAAA, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
ElementsAre(IPEndPoint(expected, 0))))));
histogram_tester_.ExpectUniqueSample(
DnsResponseResultExtractor::kHasValidCnameRecordsHistogram, false, 1);
}
TEST_F(DnsResponseResultExtractorTest, ExtractsSingleARecordWithCname) {
const IPAddress kExpected(192, 168, 0, 1);
constexpr char kName[] = "address.test";
constexpr char kCanonicalName[] = "alias.test";
DnsResponse response =
BuildTestDnsAddressResponseWithCname(kName, kExpected, kCanonicalName);
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::A,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(
Pointee(ExpectHostResolverInternalDataResult(
kCanonicalName, DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
ElementsAre(IPEndPoint(kExpected, 0)))),
Pointee(ExpectHostResolverInternalAliasResult(
kName, DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), kCanonicalName))));
histogram_tester_.ExpectUniqueSample(
DnsResponseResultExtractor::kHasValidCnameRecordsHistogram, true, 1);
}
TEST_F(DnsResponseResultExtractorTest, ExtractsARecordsWithCname) {
constexpr char kName[] = "addresses.test";
DnsResponse response = BuildTestDnsResponse(
"addresses.test", dns_protocol::kTypeA,
{
BuildTestAddressRecord("alias.test", IPAddress(74, 125, 226, 179)),
BuildTestAddressRecord("alias.test", IPAddress(74, 125, 226, 180)),
BuildTestCnameRecord(kName, "alias.test"),
BuildTestAddressRecord("alias.test", IPAddress(74, 125, 226, 176)),
BuildTestAddressRecord("alias.test", IPAddress(74, 125, 226, 177)),
BuildTestAddressRecord("alias.test", IPAddress(74, 125, 226, 178)),
});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::A,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(
Pointee(ExpectHostResolverInternalDataResult(
"alias.test", DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
UnorderedElementsAre(
IPEndPoint(IPAddress(74, 125, 226, 179), 0),
IPEndPoint(IPAddress(74, 125, 226, 180), 0),
IPEndPoint(IPAddress(74, 125, 226, 176), 0),
IPEndPoint(IPAddress(74, 125, 226, 177), 0),
IPEndPoint(IPAddress(74, 125, 226, 178), 0)))),
Pointee(ExpectHostResolverInternalAliasResult(
kName, DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "alias.test"))));
histogram_tester_.ExpectUniqueSample(
DnsResponseResultExtractor::kHasValidCnameRecordsHistogram, true, 1);
}
TEST_F(DnsResponseResultExtractorTest, ExtractsNxdomainAResponses) {
constexpr char kName[] = "address.test";
constexpr auto kTtl = base::Hours(2);
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeA, {},
{BuildTestDnsRecord(kName, dns_protocol::kTypeSOA,
fake_test_rdata, kTtl)},
{}, dns_protocol::kRcodeNXDOMAIN);
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::A,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalErrorResult(
kName, DnsQueryType::A, kDnsSource,
Eq(tick_clock_.NowTicks() + kTtl),
Eq(clock_.Now() + kTtl),
ERR_NAME_NOT_RESOLVED))));
}
TEST_F(DnsResponseResultExtractorTest, ExtractsNodataAResponses) {
constexpr char kName[] = "address.test";
constexpr auto kTtl = base::Minutes(15);
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeA, {},
{BuildTestDnsRecord(kName, dns_protocol::kTypeSOA,
fake_test_rdata, kTtl)});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::A,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalErrorResult(
kName, DnsQueryType::A, kDnsSource,
Eq(tick_clock_.NowTicks() + kTtl),
Eq(clock_.Now() + kTtl),
ERR_NAME_NOT_RESOLVED))));
}
TEST_F(DnsResponseResultExtractorTest, ExtractsNodataAResponsesWithoutTtl) {
constexpr char kName[] = "address.test";
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeA, {});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::A,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(), IsEmpty());
}
TEST_F(DnsResponseResultExtractorTest, RejectsMalformedARecord) {
constexpr char kName[] = "address.test";
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeA,
{BuildTestDnsRecord(kName, dns_protocol::kTypeA,
malformed_test_rdata)} );
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
EXPECT_EQ(extractor
.ExtractDnsResults(DnsQueryType::A,
kName,
0)
.error_or(ExtractionError::kOk),
ExtractionError::kMalformedRecord);
}
TEST_F(DnsResponseResultExtractorTest, RejectsWrongNameARecord) {
constexpr char kName[] = "address.test";
DnsResponse response = BuildTestDnsAddressResponse(
kName, IPAddress(1, 2, 3, 4), "different.test");
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
EXPECT_EQ(extractor
.ExtractDnsResults(DnsQueryType::A,
kName,
0)
.error_or(ExtractionError::kOk),
ExtractionError::kNameMismatch);
}
TEST_F(DnsResponseResultExtractorTest, IgnoresWrongTypeRecordsInAResponse) {
constexpr char kName[] = "address.test";
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeA,
{BuildTestTextRecord("address.test", {"foo"} )});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::A,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(), IsEmpty());
}
TEST_F(DnsResponseResultExtractorTest,
IgnoresWrongTypeRecordsMixedWithARecords) {
constexpr char kName[] = "address.test";
const IPAddress kExpected(8, 8, 8, 8);
constexpr auto kTtl = base::Days(3);
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeA,
{BuildTestTextRecord(kName, {"foo"}, base::Hours(2)),
BuildTestAddressRecord(kName, kExpected, kTtl)});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::A,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalDataResult(
kName, DnsQueryType::A, kDnsSource,
Eq(tick_clock_.NowTicks() + kTtl),
Eq(clock_.Now() + kTtl),
ElementsAre(IPEndPoint(kExpected, 0))))));
}
TEST_F(DnsResponseResultExtractorTest, ExtractsMinATtl) {
constexpr char kName[] = "name.test";
constexpr base::TimeDelta kMinTtl = base::Minutes(4);
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeA,
{BuildTestAddressRecord(kName, IPAddress(1, 2, 3, 4), base::Hours(3)),
BuildTestAddressRecord(kName, IPAddress(2, 3, 4, 5), kMinTtl),
BuildTestAddressRecord(kName, IPAddress(3, 4, 5, 6),
base::Minutes(15))});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::A,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalDataResult(
kName, DnsQueryType::A, kDnsSource,
Eq(tick_clock_.NowTicks() + kMinTtl),
Eq(clock_.Now() + kMinTtl),
SizeIs(3)))));
}
MATCHER_P(ContainsContiguousElements, elements, "") {
return std::ranges::search(arg, elements).begin() != arg.end();
}
TEST_F(DnsResponseResultExtractorTest, ExtractsTxtResponses) {
constexpr char kName[] = "name.test";
std::vector<std::string> foo_records = {"foo1", "foo2", "foo3"};
std::vector<std::string> bar_records = {"bar1", "bar2"};
std::vector<std::vector<std::string>> text_records = {foo_records,
bar_records};
DnsResponse response =
BuildTestDnsTextResponse(kName, std::move(text_records));
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::TXT,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalDataResult(
kName, DnsQueryType::TXT, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
IsEmpty(),
AllOf(UnorderedElementsAre("foo1", "foo2", "foo3", "bar1", "bar2"),
ContainsContiguousElements(foo_records),
ContainsContiguousElements(bar_records))))));
}
TEST_F(DnsResponseResultExtractorTest, ExtractsNxdomainTxtResponses) {
constexpr char kName[] = "name.test";
constexpr auto kTtl = base::Days(4);
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeTXT, {},
{BuildTestDnsRecord(kName, dns_protocol::kTypeSOA,
fake_test_rdata, kTtl)},
{}, dns_protocol::kRcodeNXDOMAIN);
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::TXT,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalErrorResult(
kName, DnsQueryType::TXT, kDnsSource,
Eq(tick_clock_.NowTicks() + kTtl),
Eq(clock_.Now() + kTtl),
ERR_NAME_NOT_RESOLVED))));
}
TEST_F(DnsResponseResultExtractorTest, ExtractsNodataTxtResponses) {
constexpr char kName[] = "name.test";
constexpr auto kTtl = base::Minutes(42);
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeTXT,
{},
{BuildTestDnsRecord(kName, dns_protocol::kTypeSOA,
fake_test_rdata, kTtl)});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::TXT,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalErrorResult(
kName, DnsQueryType::TXT, kDnsSource,
Eq(tick_clock_.NowTicks() + kTtl),
Eq(clock_.Now() + kTtl),
ERR_NAME_NOT_RESOLVED))));
}
TEST_F(DnsResponseResultExtractorTest, RejectsMalformedTxtRecord) {
constexpr char kName[] = "name.test";
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeTXT,
{BuildTestDnsRecord(kName, dns_protocol::kTypeTXT,
malformed_test_rdata)} );
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
EXPECT_EQ(extractor
.ExtractDnsResults(DnsQueryType::TXT,
kName,
0)
.error_or(ExtractionError::kOk),
ExtractionError::kMalformedRecord);
}
TEST_F(DnsResponseResultExtractorTest, RejectsMalformedEmptyTxtRecord) {
constexpr char kName[] = "name.test";
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeTXT,
{BuildTestDnsRecord(kName, dns_protocol::kTypeTXT,
{})});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
EXPECT_EQ(extractor
.ExtractDnsResults(DnsQueryType::TXT,
kName,
0)
.error_or(ExtractionError::kOk),
ExtractionError::kMalformedRecord);
}
TEST_F(DnsResponseResultExtractorTest, RejectsWrongNameTxtRecord) {
constexpr char kName[] = "name.test";
DnsResponse response =
BuildTestDnsTextResponse(kName, {{"foo"}}, "different.test");
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
EXPECT_EQ(extractor
.ExtractDnsResults(DnsQueryType::TXT,
kName,
0)
.error_or(ExtractionError::kOk),
ExtractionError::kNameMismatch);
}
TEST_F(DnsResponseResultExtractorTest, IgnoresWrongTypeTxtResponses) {
constexpr char kName[] = "name.test";
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeTXT,
{BuildTestAddressRecord(kName, IPAddress(1, 2, 3, 4))});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::TXT,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(), IsEmpty());
}
TEST_F(DnsResponseResultExtractorTest, ExtractsMinTxtTtl) {
constexpr char kName[] = "name.test";
constexpr base::TimeDelta kMinTtl = base::Minutes(4);
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeTXT,
{BuildTestTextRecord(kName, {"foo"}, base::Hours(3)),
BuildTestTextRecord(kName, {"bar"}, kMinTtl),
BuildTestTextRecord(kName, {"baz"}, base::Minutes(15))});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::TXT,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalDataResult(
kName, DnsQueryType::TXT, kDnsSource,
Eq(tick_clock_.NowTicks() + kMinTtl),
Eq(clock_.Now() + kMinTtl),
IsEmpty(),
SizeIs(3)))));
}
TEST_F(DnsResponseResultExtractorTest, ExtractsPtrResponses) {
constexpr char kName[] = "name.test";
DnsResponse response =
BuildTestDnsPointerResponse(kName, {"foo.com", "bar.com"});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::PTR,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalDataResult(
kName, DnsQueryType::PTR, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
IsEmpty(),
IsEmpty(),
UnorderedElementsAre(HostPortPair("foo.com", 0),
HostPortPair("bar.com", 0))))));
}
TEST_F(DnsResponseResultExtractorTest, ExtractsNxdomainPtrResponses) {
constexpr char kName[] = "name.test";
constexpr auto kTtl = base::Hours(5);
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypePTR, {},
{BuildTestDnsRecord(kName, dns_protocol::kTypeSOA,
fake_test_rdata, kTtl)},
{}, dns_protocol::kRcodeNXDOMAIN);
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::PTR,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalErrorResult(
kName, DnsQueryType::PTR, kDnsSource,
Eq(tick_clock_.NowTicks() + kTtl),
Eq(clock_.Now() + kTtl),
ERR_NAME_NOT_RESOLVED))));
}
TEST_F(DnsResponseResultExtractorTest, ExtractsNodataPtrResponses) {
constexpr char kName[] = "name.test";
constexpr auto kTtl = base::Minutes(50);
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypePTR, {},
{BuildTestDnsRecord(kName, dns_protocol::kTypeSOA,
fake_test_rdata, kTtl)});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::PTR,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalErrorResult(
kName, DnsQueryType::PTR, kDnsSource,
Eq(tick_clock_.NowTicks() + kTtl),
Eq(clock_.Now() + kTtl),
ERR_NAME_NOT_RESOLVED))));
}
TEST_F(DnsResponseResultExtractorTest, RejectsMalformedPtrRecord) {
constexpr char kName[] = "name.test";
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypePTR,
{BuildTestDnsRecord(kName, dns_protocol::kTypePTR,
malformed_test_rdata)} );
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
EXPECT_EQ(extractor
.ExtractDnsResults(DnsQueryType::PTR,
kName,
0)
.error_or(ExtractionError::kOk),
ExtractionError::kMalformedRecord);
}
TEST_F(DnsResponseResultExtractorTest, RejectsWrongNamePtrRecord) {
constexpr char kName[] = "name.test";
DnsResponse response = BuildTestDnsPointerResponse(
kName, {"foo.com", "bar.com"}, "different.test");
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
EXPECT_EQ(extractor
.ExtractDnsResults(DnsQueryType::PTR,
kName,
0)
.error_or(ExtractionError::kOk),
ExtractionError::kNameMismatch);
}
TEST_F(DnsResponseResultExtractorTest, IgnoresWrongTypePtrResponses) {
constexpr char kName[] = "name.test";
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypePTR,
{BuildTestAddressRecord(kName, IPAddress(1, 2, 3, 4))});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::PTR,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(), IsEmpty());
}
TEST_F(DnsResponseResultExtractorTest, ExtractsSrvResponses) {
constexpr char kName[] = "name.test";
const TestServiceRecord kRecord1 = {2, 3, 1223, "foo.com"};
const TestServiceRecord kRecord2 = {5, 10, 80, "bar.com"};
const TestServiceRecord kRecord3 = {5, 1, 5, "google.com"};
const TestServiceRecord kRecord4 = {2, 100, 12345, "chromium.org"};
DnsResponse response = BuildTestDnsServiceResponse(
kName, {kRecord1, kRecord2, kRecord3, kRecord4});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::SRV,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalDataResult(
kName, DnsQueryType::SRV, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
IsEmpty(),
IsEmpty(),
UnorderedElementsAre(HostPortPair("foo.com", 1223),
HostPortPair("bar.com", 80),
HostPortPair("google.com", 5),
HostPortPair("chromium.org", 12345))))));
std::vector<HostPortPair> result_hosts =
(*results.value().begin())->AsData().hosts();
auto priority2 =
std::vector<HostPortPair>(result_hosts.begin(), result_hosts.begin() + 2);
EXPECT_THAT(priority2, testing::UnorderedElementsAre(
HostPortPair("foo.com", 1223),
HostPortPair("chromium.org", 12345)));
auto priority5 =
std::vector<HostPortPair>(result_hosts.begin() + 2, result_hosts.end());
EXPECT_THAT(priority5,
testing::UnorderedElementsAre(HostPortPair("bar.com", 80),
HostPortPair("google.com", 5)));
}
TEST_F(DnsResponseResultExtractorTest, ExtractsZeroWeightSrvResponses) {
constexpr char kName[] = "name.test";
const TestServiceRecord kRecord1 = {5, 0, 80, "bar.com"};
const TestServiceRecord kRecord2 = {5, 0, 5, "google.com"};
DnsResponse response =
BuildTestDnsServiceResponse(kName, {kRecord1, kRecord2});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::SRV,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalDataResult(
kName, DnsQueryType::SRV, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
IsEmpty(),
IsEmpty(),
UnorderedElementsAre(HostPortPair("bar.com", 80),
HostPortPair("google.com", 5))))));
}
TEST_F(DnsResponseResultExtractorTest, ExtractsNxdomainSrvResponses) {
constexpr char kName[] = "name.test";
constexpr auto kTtl = base::Days(7);
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeSRV, {},
{BuildTestDnsRecord(kName, dns_protocol::kTypeSOA,
fake_test_rdata, kTtl)},
{}, dns_protocol::kRcodeNXDOMAIN);
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::SRV,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalErrorResult(
kName, DnsQueryType::SRV, kDnsSource,
Eq(tick_clock_.NowTicks() + kTtl),
Eq(clock_.Now() + kTtl),
ERR_NAME_NOT_RESOLVED))));
}
TEST_F(DnsResponseResultExtractorTest, ExtractsNodataSrvResponses) {
constexpr char kName[] = "name.test";
constexpr auto kTtl = base::Hours(12);
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeSRV, {},
{BuildTestDnsRecord(kName, dns_protocol::kTypeSOA,
fake_test_rdata, kTtl)});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::SRV,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalErrorResult(
kName, DnsQueryType::SRV, kDnsSource,
Eq(tick_clock_.NowTicks() + kTtl),
Eq(clock_.Now() + kTtl),
ERR_NAME_NOT_RESOLVED))));
}
TEST_F(DnsResponseResultExtractorTest, RejectsMalformedSrvRecord) {
constexpr char kName[] = "name.test";
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeSRV,
{BuildTestDnsRecord(kName, dns_protocol::kTypeSRV,
malformed_test_rdata)} );
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
EXPECT_EQ(extractor
.ExtractDnsResults(DnsQueryType::SRV,
kName,
0)
.error_or(ExtractionError::kOk),
ExtractionError::kMalformedRecord);
}
TEST_F(DnsResponseResultExtractorTest, RejectsWrongNameSrvRecord) {
constexpr char kName[] = "name.test";
const TestServiceRecord kRecord = {2, 3, 1223, "foo.com"};
DnsResponse response =
BuildTestDnsServiceResponse(kName, {kRecord}, "different.test");
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
EXPECT_EQ(extractor
.ExtractDnsResults(DnsQueryType::SRV,
kName,
0)
.error_or(ExtractionError::kOk),
ExtractionError::kNameMismatch);
}
TEST_F(DnsResponseResultExtractorTest, IgnoresWrongTypeSrvResponses) {
constexpr char kName[] = "name.test";
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeSRV,
{BuildTestAddressRecord(kName, IPAddress(1, 2, 3, 4))});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::SRV,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(), IsEmpty());
}
TEST_F(DnsResponseResultExtractorTest, ExtractsBasicHttpsResponses) {
constexpr char kName[] = "https.test";
constexpr auto kTtl = base::Hours(12);
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeHttps,
{BuildTestHttpsServiceRecord(kName,
4,
".",
{}, kTtl)});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalMetadataResult(
kName, DnsQueryType::HTTPS, kDnsSource,
Eq(tick_clock_.NowTicks() + kTtl), Eq(clock_.Now() + kTtl),
ElementsAre(
Pair(4, ExpectConnectionEndpointMetadata(
ElementsAre(dns_protocol::kHttpsServiceDefaultAlpn),
IsEmpty(), kName)))))));
}
TEST_F(DnsResponseResultExtractorTest, ExtractsComprehensiveHttpsResponses) {
constexpr char kName[] = "https.test";
constexpr char kAlpn[] = "foo";
constexpr uint8_t kEchConfig[] = "EEEEEEEEECH!";
constexpr auto kTtl = base::Hours(12);
const std::vector<std::vector<uint8_t>> kTrustAnchorIDs = {{0x01, 0x02, 0x03},
{0x02, 0x02}};
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeHttps,
{BuildTestHttpsServiceRecord(
kName, 4,
".",
{BuildTestHttpsServiceAlpnParam({kAlpn}),
BuildTestHttpsServiceEchConfigParam(kEchConfig),
BuildTestHttpsServiceTrustAnchorIDsParam(kTrustAnchorIDs)},
kTtl),
BuildTestHttpsServiceRecord(
kName, 3,
".",
{BuildTestHttpsServiceAlpnParam({kAlpn}),
{dns_protocol::kHttpsServiceParamKeyNoDefaultAlpn, ""}},
base::Days(3))});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalMetadataResult(
kName, DnsQueryType::HTTPS, kDnsSource,
Eq(tick_clock_.NowTicks() + kTtl), Eq(clock_.Now() + kTtl),
ElementsAre(
Pair(3, ExpectConnectionEndpointMetadata(
ElementsAre(kAlpn),
IsEmpty(), kName)),
Pair(4, ExpectConnectionEndpointMetadata(
ElementsAre(kAlpn,
dns_protocol::kHttpsServiceDefaultAlpn),
ElementsAreArray(kEchConfig), kName,
ElementsAreArray(kTrustAnchorIDs))))))));
}
TEST_F(DnsResponseResultExtractorTest, IgnoresHttpsResponseWithJustAlias) {
constexpr char kName[] = "https.test";
constexpr base::TimeDelta kTtl = base::Days(5);
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeHttps,
{BuildTestHttpsAliasRecord(kName, "alias.test", kTtl)});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalMetadataResult(
kName, DnsQueryType::HTTPS, kDnsSource,
Optional(tick_clock_.NowTicks() + kTtl),
Optional(clock_.Now() + kTtl),
IsEmpty()))));
}
TEST_F(DnsResponseResultExtractorTest, IgnoresHttpsResponseWithAlias) {
constexpr char kName[] = "https.test";
constexpr base::TimeDelta kLowestTtl = base::Minutes(32);
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeHttps,
{BuildTestHttpsServiceRecord(kName,
4,
".",
{}, base::Days(1)),
BuildTestHttpsAliasRecord(kName, "alias.test", kLowestTtl)});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalMetadataResult(
kName, DnsQueryType::HTTPS, kDnsSource,
Optional(tick_clock_.NowTicks() + kLowestTtl),
Optional(clock_.Now() + kLowestTtl),
IsEmpty()))));
}
TEST_F(DnsResponseResultExtractorTest, IgnoresHttpsResponseWithNoDefaultAlpn) {
constexpr char kName[] = "https.test";
constexpr base::TimeDelta kLowestTtl = base::Hours(3);
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeHttps,
{BuildTestHttpsServiceRecord(
kName, 4,
".",
{BuildTestHttpsServiceAlpnParam({"foo1"}),
{dns_protocol::kHttpsServiceParamKeyNoDefaultAlpn, ""}},
kLowestTtl),
BuildTestHttpsServiceRecord(
kName, 5,
".",
{BuildTestHttpsServiceAlpnParam({"foo2"}),
{dns_protocol::kHttpsServiceParamKeyNoDefaultAlpn, ""}},
base::Days(3))});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalMetadataResult(
kName, DnsQueryType::HTTPS, kDnsSource,
Optional(tick_clock_.NowTicks() + kLowestTtl),
Optional(clock_.Now() + kLowestTtl),
IsEmpty()))));
}
TEST_F(DnsResponseResultExtractorTest, IgnoresUnsupportedParamsInHttpsRecord) {
constexpr char kName[] = "https.test";
constexpr uint16_t kMadeUpParamKey = 65500;
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeHttps,
{BuildTestHttpsServiceRecord(kName, 4,
".",
{{kMadeUpParamKey, "foo"}})});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalMetadataResult(
kName, DnsQueryType::HTTPS, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
ElementsAre(
Pair(4, ExpectConnectionEndpointMetadata(
ElementsAre(dns_protocol::kHttpsServiceDefaultAlpn),
IsEmpty(), kName)))))));
}
TEST_F(DnsResponseResultExtractorTest,
IgnoresHttpsRecordWithUnsupportedMandatoryParam) {
constexpr char kName[] = "https.test";
constexpr uint16_t kMadeUpParamKey = 65500;
constexpr base::TimeDelta kTtl = base::Days(5);
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeHttps,
{BuildTestHttpsServiceRecord(
kName, 4,
".",
{BuildTestHttpsServiceAlpnParam({"ignored_alpn"}),
BuildTestHttpsServiceMandatoryParam({kMadeUpParamKey}),
{kMadeUpParamKey, "foo"}},
base::Hours(2)),
BuildTestHttpsServiceRecord(
kName, 5,
".",
{BuildTestHttpsServiceAlpnParam({"foo"})}, kTtl)});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalMetadataResult(
kName, DnsQueryType::HTTPS, kDnsSource,
Optional(tick_clock_.NowTicks() + kTtl),
Optional(clock_.Now() + kTtl),
ElementsAre(Pair(
5, ExpectConnectionEndpointMetadata(
ElementsAre("foo", dns_protocol::kHttpsServiceDefaultAlpn),
IsEmpty(), kName)))))));
}
TEST_F(DnsResponseResultExtractorTest,
ExtractsHttpsRecordWithMatchingServiceName) {
constexpr char kName[] = "https.test";
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeHttps,
{BuildTestHttpsServiceRecord(kName, 4,
kName,
{BuildTestHttpsServiceAlpnParam({"foo"})})});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalMetadataResult(
kName, DnsQueryType::HTTPS, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
ElementsAre(Pair(
4, ExpectConnectionEndpointMetadata(
ElementsAre("foo", dns_protocol::kHttpsServiceDefaultAlpn),
IsEmpty(), kName)))))));
}
TEST_F(DnsResponseResultExtractorTest,
ExtractsHttpsRecordWithMatchingDefaultServiceName) {
constexpr char kName[] = "https.test";
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeHttps,
{BuildTestHttpsServiceRecord(kName, 4,
".",
{BuildTestHttpsServiceAlpnParam({"foo"})})});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalMetadataResult(
kName, DnsQueryType::HTTPS, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
ElementsAre(Pair(
4, ExpectConnectionEndpointMetadata(
ElementsAre("foo", dns_protocol::kHttpsServiceDefaultAlpn),
IsEmpty(), kName)))))));
}
TEST_F(DnsResponseResultExtractorTest,
ExtractsHttpsRecordWithPrefixedNameAndMatchingServiceName) {
constexpr char kName[] = "https.test";
constexpr char kPrefixedName[] = "_444._https.https.test";
DnsResponse response = BuildTestDnsResponse(
kPrefixedName, dns_protocol::kTypeHttps,
{BuildTestHttpsServiceRecord(kPrefixedName, 4,
kName,
{BuildTestHttpsServiceAlpnParam({"foo"})})});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalMetadataResult(
kPrefixedName, DnsQueryType::HTTPS, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
ElementsAre(Pair(
4, ExpectConnectionEndpointMetadata(
ElementsAre("foo", dns_protocol::kHttpsServiceDefaultAlpn),
IsEmpty(), kName)))))));
}
TEST_F(DnsResponseResultExtractorTest,
ExtractsHttpsRecordWithAliasingAndMatchingServiceName) {
constexpr char kName[] = "https.test";
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeHttps,
{BuildTestCnameRecord(kName, "alias.test"),
BuildTestHttpsServiceRecord("alias.test", 4,
kName,
{BuildTestHttpsServiceAlpnParam({"foo"})})});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(
Pointee(ExpectHostResolverInternalAliasResult(
kName, DnsQueryType::HTTPS, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "alias.test")),
Pointee(ExpectHostResolverInternalMetadataResult(
"alias.test", DnsQueryType::HTTPS, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
ElementsAre(Pair(
4, ExpectConnectionEndpointMetadata(
ElementsAre("foo",
dns_protocol::kHttpsServiceDefaultAlpn),
IsEmpty(), kName)))))));
}
TEST_F(DnsResponseResultExtractorTest,
IgnoreHttpsRecordWithNonMatchingServiceName) {
constexpr char kName[] = "https.test";
constexpr base::TimeDelta kTtl = base::Hours(14);
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeHttps,
{BuildTestHttpsServiceRecord(
kName, 4,
"other.service.test",
{BuildTestHttpsServiceAlpnParam({"ignored"})}, base::Hours(3)),
BuildTestHttpsServiceRecord("https.test", 5,
".",
{BuildTestHttpsServiceAlpnParam({"foo"})},
kTtl)});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalMetadataResult(
kName, DnsQueryType::HTTPS, kDnsSource,
Optional(tick_clock_.NowTicks() + kTtl),
Optional(clock_.Now() + kTtl),
ElementsAre(Pair(
5, ExpectConnectionEndpointMetadata(
ElementsAre("foo", dns_protocol::kHttpsServiceDefaultAlpn),
IsEmpty(), kName)))))));
}
TEST_F(DnsResponseResultExtractorTest,
ExtractsHttpsRecordWithPrefixedNameAndDefaultServiceName) {
constexpr char kPrefixedName[] = "_445._https.https.test";
DnsResponse response = BuildTestDnsResponse(
kPrefixedName, dns_protocol::kTypeHttps,
{BuildTestHttpsServiceRecord(kPrefixedName, 4,
".",
{BuildTestHttpsServiceAlpnParam({"foo"})})});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
"https.test",
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalMetadataResult(
kPrefixedName, DnsQueryType::HTTPS, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
ElementsAre(Pair(
4,
ExpectConnectionEndpointMetadata(
ElementsAre("foo", dns_protocol::kHttpsServiceDefaultAlpn),
IsEmpty(), kPrefixedName)))))));
}
TEST_F(DnsResponseResultExtractorTest,
ExtractsHttpsRecordWithAliasingAndDefaultServiceName) {
constexpr char kName[] = "https.test";
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeHttps,
{BuildTestCnameRecord(kName, "alias.test"),
BuildTestHttpsServiceRecord("alias.test", 4,
".",
{BuildTestHttpsServiceAlpnParam({"foo"})})});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(
Pointee(ExpectHostResolverInternalAliasResult(
kName, DnsQueryType::HTTPS, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "alias.test")),
Pointee(ExpectHostResolverInternalMetadataResult(
"alias.test", DnsQueryType::HTTPS, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
ElementsAre(Pair(
4, ExpectConnectionEndpointMetadata(
ElementsAre("foo",
dns_protocol::kHttpsServiceDefaultAlpn),
IsEmpty(),
"alias.test")))))));
}
TEST_F(DnsResponseResultExtractorTest, ExtractsHttpsRecordWithMatchingPort) {
constexpr char kName[] = "https.test";
constexpr uint16_t kPort = 4567;
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeHttps,
{BuildTestHttpsServiceRecord(kName, 4,
".",
{BuildTestHttpsServiceAlpnParam({"foo"}),
BuildTestHttpsServicePortParam(kPort)})});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
kPort);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(Pointee(ExpectHostResolverInternalMetadataResult(
kName, DnsQueryType::HTTPS, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
ElementsAre(Pair(
4, ExpectConnectionEndpointMetadata(
ElementsAre("foo", dns_protocol::kHttpsServiceDefaultAlpn),
IsEmpty(), kName)))))));
}
TEST_F(DnsResponseResultExtractorTest, IgnoresHttpsRecordWithMismatchingPort) {
constexpr char kName[] = "https.test";
constexpr base::TimeDelta kTtl = base::Days(14);
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeHttps,
{BuildTestHttpsServiceRecord(kName, 4,
".",
{BuildTestHttpsServiceAlpnParam({"ignored"}),
BuildTestHttpsServicePortParam(1003)},
base::Hours(12)),
BuildTestHttpsServiceRecord(kName, 4,
".",
{BuildTestHttpsServiceAlpnParam({"foo"})},
kTtl)});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
55);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(Pointee(ExpectHostResolverInternalMetadataResult(
kName, DnsQueryType::HTTPS, kDnsSource,
Optional(tick_clock_.NowTicks() + kTtl),
Optional(clock_.Now() + kTtl),
ElementsAre(Pair(
4, ExpectConnectionEndpointMetadata(
ElementsAre("foo", dns_protocol::kHttpsServiceDefaultAlpn),
IsEmpty(), kName)))))));
}
TEST_F(DnsResponseResultExtractorTest, IgnoresHttpsRecordWithNoAlpn) {
constexpr char kName[] = "https.test";
constexpr base::TimeDelta kTtl = base::Minutes(150);
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeHttps,
{BuildTestHttpsServiceRecord(
kName, 4,
".",
{{dns_protocol::kHttpsServiceParamKeyNoDefaultAlpn, ""}},
base::Minutes(10)),
BuildTestHttpsServiceRecord(kName, 4,
".",
{BuildTestHttpsServiceAlpnParam({"foo"})},
kTtl)});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
55);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(Pointee(ExpectHostResolverInternalMetadataResult(
kName, DnsQueryType::HTTPS, kDnsSource,
Optional(tick_clock_.NowTicks() + kTtl),
Optional(clock_.Now() + kTtl),
ElementsAre(Pair(
4, ExpectConnectionEndpointMetadata(
ElementsAre("foo", dns_protocol::kHttpsServiceDefaultAlpn),
IsEmpty(), kName)))))));
}
TEST_F(DnsResponseResultExtractorTest,
IgnoresHttpsResponseWithNoCompatibleDefaultAlpn) {
constexpr char kName[] = "https.test";
constexpr uint16_t kMadeUpParamKey = 65500;
constexpr base::TimeDelta kLowestTtl = base::Days(2);
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeHttps,
{BuildTestHttpsServiceRecord(
kName, 4,
".",
{BuildTestHttpsServiceAlpnParam({"foo1"}),
{dns_protocol::kHttpsServiceParamKeyNoDefaultAlpn, ""}},
base::Days(3)),
BuildTestHttpsServiceRecord(
kName, 5,
".",
{BuildTestHttpsServiceAlpnParam({"foo2"}),
{dns_protocol::kHttpsServiceParamKeyNoDefaultAlpn, ""}},
base::Days(4)),
BuildTestHttpsServiceRecord(kName, 3,
"other.test",
{}, kLowestTtl),
BuildTestHttpsServiceRecord(
kName, 6,
".",
{BuildTestHttpsServiceMandatoryParam({kMadeUpParamKey}),
{kMadeUpParamKey, "foo"}},
base::Hours(1)),
BuildTestHttpsServiceRecord(
kName, 10,
".",
{BuildTestHttpsServicePortParam(1005)}, base::Days(5))});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(Pointee(ExpectHostResolverInternalMetadataResult(
kName, DnsQueryType::HTTPS, kDnsSource,
Optional(tick_clock_.NowTicks() + kLowestTtl),
Optional(clock_.Now() + kLowestTtl),
IsEmpty()))));
}
TEST_F(DnsResponseResultExtractorTest, ExtractsNxdomainHttpsResponses) {
constexpr char kName[] = "https.test";
constexpr auto kTtl = base::Minutes(45);
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeHttps, {},
{BuildTestDnsRecord(kName, dns_protocol::kTypeSOA,
fake_test_rdata, kTtl)},
{}, dns_protocol::kRcodeNXDOMAIN);
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalErrorResult(
kName, DnsQueryType::HTTPS, kDnsSource,
Eq(tick_clock_.NowTicks() + kTtl),
Eq(clock_.Now() + kTtl),
ERR_NAME_NOT_RESOLVED))));
}
TEST_F(DnsResponseResultExtractorTest, ExtractsNodataHttpsResponses) {
constexpr char kName[] = "https.test";
constexpr auto kTtl = base::Hours(36);
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeHttps, {},
{BuildTestDnsRecord(kName, dns_protocol::kTypeSOA,
fake_test_rdata, kTtl)});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalErrorResult(
kName, DnsQueryType::HTTPS, kDnsSource,
Eq(tick_clock_.NowTicks() + kTtl),
Eq(clock_.Now() + kTtl),
ERR_NAME_NOT_RESOLVED))));
}
TEST_F(DnsResponseResultExtractorTest, ExtractsNodataHttpsResponsesWithoutTtl) {
constexpr char kName[] = "https.test";
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeHttps, {});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(), IsEmpty());
}
TEST_F(DnsResponseResultExtractorTest, RejectsMalformedHttpsRecord) {
constexpr char kName[] = "https.test";
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeHttps,
{BuildTestDnsRecord(kName, dns_protocol::kTypeHttps,
malformed_test_rdata)} );
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
EXPECT_EQ(extractor
.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0)
.error_or(ExtractionError::kOk),
ExtractionError::kMalformedRecord);
}
TEST_F(DnsResponseResultExtractorTest, RejectsWrongNameHttpsRecord) {
constexpr char kName[] = "https.test";
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeHttps,
{BuildTestHttpsAliasRecord("different.test", "alias.test")});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
EXPECT_EQ(extractor
.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0)
.error_or(ExtractionError::kOk),
ExtractionError::kNameMismatch);
}
TEST_F(DnsResponseResultExtractorTest, IgnoresWrongTypeHttpsResponses) {
constexpr char kName[] = "https.test";
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeHttps,
{BuildTestAddressRecord(kName, IPAddress(1, 2, 3, 4))});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(), IsEmpty());
}
TEST_F(DnsResponseResultExtractorTest, IgnoresAdditionalHttpsRecords) {
constexpr char kName[] = "https.test";
constexpr auto kTtl = base::Days(5);
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeHttps,
{BuildTestHttpsServiceRecord(kName, 5u,
".",
{BuildTestHttpsServiceAlpnParam({"foo1"})},
kTtl)},
{},
{BuildTestHttpsServiceRecord(kName, 3u,
".",
{BuildTestHttpsServiceAlpnParam({"foo2"})},
base::Minutes(44)),
BuildTestHttpsServiceRecord(kName, 2u,
".",
{BuildTestHttpsServiceAlpnParam({"foo3"})},
base::Minutes(30))});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::HTTPS,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(Pointee(ExpectHostResolverInternalMetadataResult(
kName, DnsQueryType::HTTPS, kDnsSource,
Eq(tick_clock_.NowTicks() + kTtl), Eq(clock_.Now() + kTtl),
ElementsAre(Pair(
5,
ExpectConnectionEndpointMetadata(
ElementsAre("foo1", dns_protocol::kHttpsServiceDefaultAlpn),
IsEmpty(), kName)))))));
}
TEST_F(DnsResponseResultExtractorTest, IgnoresUnsolicitedHttpsRecords) {
constexpr char kName[] = "name.test";
constexpr auto kTtl = base::Minutes(45);
const uint8_t test_rdata[] = {0x03, 'f', 'o', 'o'};
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeTXT,
{BuildTestDnsRecord(kName, dns_protocol::kTypeTXT, test_rdata, kTtl)},
{},
{BuildTestHttpsServiceRecord(
"https.test", 3u, ".",
{BuildTestHttpsServiceAlpnParam({"foo2"})}, base::Minutes(44)),
BuildTestHttpsServiceRecord("https.test", 2u,
".",
{BuildTestHttpsServiceAlpnParam({"foo3"})},
base::Minutes(30))});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::TXT,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalDataResult(
kName, DnsQueryType::TXT, kDnsSource,
Eq(tick_clock_.NowTicks() + kTtl),
Eq(clock_.Now() + kTtl),
IsEmpty(), ElementsAre("foo")))));
}
TEST_F(DnsResponseResultExtractorTest, HandlesInOrderCnameChain) {
constexpr char kName[] = "first.test";
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeTXT,
{BuildTestCnameRecord(kName, "second.test"),
BuildTestCnameRecord("second.test", "third.test"),
BuildTestCnameRecord("third.test", "fourth.test"),
BuildTestTextRecord("fourth.test", {"foo"}),
BuildTestTextRecord("fourth.test", {"bar"})});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::TXT,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(
Pointee(ExpectHostResolverInternalAliasResult(
kName, DnsQueryType::TXT, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "second.test")),
Pointee(ExpectHostResolverInternalAliasResult(
"second.test", DnsQueryType::TXT, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "third.test")),
Pointee(ExpectHostResolverInternalAliasResult(
"third.test", DnsQueryType::TXT, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "fourth.test")),
Pointee(ExpectHostResolverInternalDataResult(
"fourth.test", DnsQueryType::TXT, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
IsEmpty(),
UnorderedElementsAre("foo", "bar")))));
}
TEST_F(DnsResponseResultExtractorTest, HandlesInOrderCnameChainTypeA) {
constexpr char kName[] = "first.test";
const IPAddress kExpected(192, 168, 0, 1);
IPEndPoint expected_endpoint(kExpected, 0 );
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeA,
{BuildTestCnameRecord(kName, "second.test"),
BuildTestCnameRecord("second.test", "third.test"),
BuildTestCnameRecord("third.test", "fourth.test"),
BuildTestAddressRecord("fourth.test", kExpected)});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::A,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(
Pointee(ExpectHostResolverInternalAliasResult(
kName, DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "second.test")),
Pointee(ExpectHostResolverInternalAliasResult(
"second.test", DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "third.test")),
Pointee(ExpectHostResolverInternalAliasResult(
"third.test", DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "fourth.test")),
Pointee(ExpectHostResolverInternalDataResult(
"fourth.test", DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
ElementsAre(expected_endpoint)))));
histogram_tester_.ExpectUniqueSample(
DnsResponseResultExtractor::kHasValidCnameRecordsHistogram, true, 1);
}
TEST_F(DnsResponseResultExtractorTest, HandlesReverseOrderCnameChain) {
constexpr char kName[] = "first.test";
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeTXT,
{BuildTestTextRecord("fourth.test", {"foo"}),
BuildTestCnameRecord("third.test", "fourth.test"),
BuildTestCnameRecord("second.test", "third.test"),
BuildTestCnameRecord(kName, "second.test")});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::TXT,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(
Pointee(ExpectHostResolverInternalAliasResult(
kName, DnsQueryType::TXT, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "second.test")),
Pointee(ExpectHostResolverInternalAliasResult(
"second.test", DnsQueryType::TXT, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "third.test")),
Pointee(ExpectHostResolverInternalAliasResult(
"third.test", DnsQueryType::TXT, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "fourth.test")),
Pointee(ExpectHostResolverInternalDataResult(
"fourth.test", DnsQueryType::TXT, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
IsEmpty(), ElementsAre("foo")))));
}
TEST_F(DnsResponseResultExtractorTest, HandlesReverseOrderCnameChainTypeA) {
constexpr char kName[] = "first.test";
const IPAddress kExpected(192, 168, 0, 1);
IPEndPoint expected_endpoint(kExpected, 0 );
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeA,
{BuildTestAddressRecord("fourth.test", kExpected),
BuildTestCnameRecord("third.test", "fourth.test"),
BuildTestCnameRecord("second.test", "third.test"),
BuildTestCnameRecord(kName, "second.test")});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::A,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(
Pointee(ExpectHostResolverInternalAliasResult(
kName, DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "second.test")),
Pointee(ExpectHostResolverInternalAliasResult(
"second.test", DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "third.test")),
Pointee(ExpectHostResolverInternalAliasResult(
"third.test", DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "fourth.test")),
Pointee(ExpectHostResolverInternalDataResult(
"fourth.test", DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
ElementsAre(expected_endpoint)))));
}
TEST_F(DnsResponseResultExtractorTest, HandlesArbitraryOrderCnameChain) {
constexpr char kName[] = "first.test";
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeTXT,
{BuildTestCnameRecord("second.test", "third.test"),
BuildTestTextRecord("fourth.test", {"foo"}),
BuildTestCnameRecord("third.test", "fourth.test"),
BuildTestCnameRecord(kName, "second.test")});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::TXT,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(
Pointee(ExpectHostResolverInternalAliasResult(
kName, DnsQueryType::TXT, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "second.test")),
Pointee(ExpectHostResolverInternalAliasResult(
"second.test", DnsQueryType::TXT, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "third.test")),
Pointee(ExpectHostResolverInternalAliasResult(
"third.test", DnsQueryType::TXT, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "fourth.test")),
Pointee(ExpectHostResolverInternalDataResult(
"fourth.test", DnsQueryType::TXT, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
IsEmpty(), ElementsAre("foo")))));
}
TEST_F(DnsResponseResultExtractorTest, HandlesArbitraryOrderCnameChainTypeA) {
constexpr char kName[] = "first.test";
const IPAddress kExpected(192, 168, 0, 1);
IPEndPoint expected_endpoint(kExpected, 0 );
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeA,
{BuildTestCnameRecord("qsecond.test", "athird.test"),
BuildTestAddressRecord("zfourth.test", kExpected),
BuildTestCnameRecord("athird.test", "zfourth.test"),
BuildTestCnameRecord(kName, "qsecond.test")});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::A,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(
Pointee(ExpectHostResolverInternalAliasResult(
kName, DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "qsecond.test")),
Pointee(ExpectHostResolverInternalAliasResult(
"qsecond.test", DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "athird.test")),
Pointee(ExpectHostResolverInternalAliasResult(
"athird.test", DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "zfourth.test")),
Pointee(ExpectHostResolverInternalDataResult(
"zfourth.test", DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
ElementsAre(expected_endpoint)))));
}
TEST_F(DnsResponseResultExtractorTest,
IgnoresNonResultTypesMixedWithCnameChain) {
constexpr char kName[] = "first.test";
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeTXT,
{BuildTestCnameRecord("second.test", "third.test"),
BuildTestTextRecord("fourth.test", {"foo"}),
BuildTestCnameRecord("third.test", "fourth.test"),
BuildTestAddressRecord("third.test", IPAddress(1, 2, 3, 4)),
BuildTestCnameRecord(kName, "second.test"),
BuildTestAddressRecord("fourth.test", IPAddress(2, 3, 4, 5))});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::TXT,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(
Pointee(ExpectHostResolverInternalAliasResult(
kName, DnsQueryType::TXT, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "second.test")),
Pointee(ExpectHostResolverInternalAliasResult(
"second.test", DnsQueryType::TXT, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "third.test")),
Pointee(ExpectHostResolverInternalAliasResult(
"third.test", DnsQueryType::TXT, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "fourth.test")),
Pointee(ExpectHostResolverInternalDataResult(
"fourth.test", DnsQueryType::TXT, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
IsEmpty(), ElementsAre("foo")))));
}
TEST_F(DnsResponseResultExtractorTest,
IgnoresNonResultTypesMixedWithCnameChainTypeA) {
constexpr char kName[] = "first.test";
const IPAddress kExpected(192, 168, 0, 1);
IPEndPoint expected_endpoint(kExpected, 0 );
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeA,
{BuildTestCnameRecord("second.test", "third.test"),
BuildTestTextRecord("fourth.test", {"foo"}),
BuildTestCnameRecord("third.test", "fourth.test"),
BuildTestCnameRecord(kName, "second.test"),
BuildTestAddressRecord("fourth.test", kExpected)});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::A,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(
Pointee(ExpectHostResolverInternalAliasResult(
kName, DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "second.test")),
Pointee(ExpectHostResolverInternalAliasResult(
"second.test", DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "third.test")),
Pointee(ExpectHostResolverInternalAliasResult(
"third.test", DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "fourth.test")),
Pointee(ExpectHostResolverInternalDataResult(
"fourth.test", DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
ElementsAre(expected_endpoint)))));
}
TEST_F(DnsResponseResultExtractorTest, HandlesCnameChainWithoutResult) {
constexpr char kName[] = "first.test";
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeTXT,
{BuildTestCnameRecord("second.test", "third.test"),
BuildTestCnameRecord("third.test", "fourth.test"),
BuildTestCnameRecord(kName, "second.test")});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::TXT,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(
Pointee(ExpectHostResolverInternalAliasResult(
kName, DnsQueryType::TXT, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "second.test")),
Pointee(ExpectHostResolverInternalAliasResult(
"second.test", DnsQueryType::TXT, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "third.test")),
Pointee(ExpectHostResolverInternalAliasResult(
"third.test", DnsQueryType::TXT, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "fourth.test"))));
}
TEST_F(DnsResponseResultExtractorTest, HandlesCnameChainWithoutResultTypeA) {
constexpr char kName[] = "first.test";
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeA,
{BuildTestCnameRecord("second.test", "third.test"),
BuildTestCnameRecord("third.test", "fourth.test"),
BuildTestCnameRecord(kName, "second.test")});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::A,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(
Pointee(ExpectHostResolverInternalAliasResult(
kName, DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "second.test")),
Pointee(ExpectHostResolverInternalAliasResult(
"second.test", DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "third.test")),
Pointee(ExpectHostResolverInternalAliasResult(
"third.test", DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "fourth.test"))));
}
TEST_F(DnsResponseResultExtractorTest, RejectsCnameChainWithLoop) {
constexpr char kName[] = "first.test";
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeTXT,
{BuildTestCnameRecord("second.test", "third.test"),
BuildTestTextRecord("third.test", {"foo"}),
BuildTestCnameRecord("third.test", "second.test"),
BuildTestCnameRecord(kName, "second.test")});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
EXPECT_EQ(extractor
.ExtractDnsResults(DnsQueryType::TXT,
kName,
0)
.error_or(ExtractionError::kOk),
ExtractionError::kBadAliasChain);
}
TEST_F(DnsResponseResultExtractorTest, RejectsCnameChainWithLoopToBeginning) {
constexpr char kName[] = "first.test";
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeTXT,
{BuildTestCnameRecord("second.test", "third.test"),
BuildTestTextRecord("third.test", {"foo"}),
BuildTestCnameRecord("third.test", "first.test"),
BuildTestCnameRecord(kName, "second.test")});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
EXPECT_EQ(extractor
.ExtractDnsResults(DnsQueryType::TXT,
kName,
0)
.error_or(ExtractionError::kOk),
ExtractionError::kBadAliasChain);
}
TEST_F(DnsResponseResultExtractorTest,
RejectsCnameChainWithLoopToBeginningWithoutResult) {
constexpr char kName[] = "first.test";
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeTXT,
{BuildTestCnameRecord("second.test", "third.test"),
BuildTestCnameRecord("third.test", "first.test"),
BuildTestCnameRecord(kName, "second.test")});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
EXPECT_EQ(extractor
.ExtractDnsResults(DnsQueryType::TXT,
kName,
0)
.error_or(ExtractionError::kOk),
ExtractionError::kBadAliasChain);
}
TEST_F(DnsResponseResultExtractorTest, RejectsCnameChainWithWrongStart) {
constexpr char kName[] = "test.test";
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeTXT,
{BuildTestCnameRecord("second.test", "third.test"),
BuildTestTextRecord("fourth.test", {"foo"}),
BuildTestCnameRecord("third.test", "fourth.test"),
BuildTestCnameRecord("first.test", "second.test")});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
EXPECT_EQ(extractor
.ExtractDnsResults(DnsQueryType::TXT,
kName,
0)
.error_or(ExtractionError::kOk),
ExtractionError::kBadAliasChain);
}
TEST_F(DnsResponseResultExtractorTest, RejectsCnameChainWithWrongResultName) {
constexpr char kName[] = "first.test";
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeTXT,
{BuildTestCnameRecord("second.test", "third.test"),
BuildTestTextRecord("third.test", {"foo"}),
BuildTestCnameRecord("third.test", "fourth.test"),
BuildTestCnameRecord(kName, "second.test")});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
EXPECT_EQ(extractor
.ExtractDnsResults(DnsQueryType::TXT,
kName,
0)
.error_or(ExtractionError::kOk),
ExtractionError::kNameMismatch);
}
TEST_F(DnsResponseResultExtractorTest, RejectsCnameSharedWithResult) {
constexpr char kName[] = "first.test";
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeTXT,
{BuildTestCnameRecord("second.test", "third.test"),
BuildTestTextRecord(kName, {"foo"}),
BuildTestCnameRecord("third.test", "fourth.test"),
BuildTestCnameRecord(kName, "second.test")});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
EXPECT_EQ(extractor
.ExtractDnsResults(DnsQueryType::TXT,
kName,
0)
.error_or(ExtractionError::kOk),
ExtractionError::kNameMismatch);
}
TEST_F(DnsResponseResultExtractorTest, RejectsDisjointCnameChain) {
constexpr char kName[] = "first.test";
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeTXT,
{BuildTestCnameRecord("second.test", "third.test"),
BuildTestTextRecord("fourth.test", {"foo"}),
BuildTestCnameRecord("third.test", "fourth.test"),
BuildTestCnameRecord("other1.test", "other2.test"),
BuildTestCnameRecord(kName, "second.test"),
BuildTestCnameRecord("other2.test", "other3.test")});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
EXPECT_EQ(extractor
.ExtractDnsResults(DnsQueryType::TXT,
kName,
0)
.error_or(ExtractionError::kOk),
ExtractionError::kBadAliasChain);
}
TEST_F(DnsResponseResultExtractorTest, RejectsDoubledCnames) {
constexpr char kName[] = "first.test";
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeTXT,
{BuildTestCnameRecord("second.test", "third.test"),
BuildTestTextRecord("fourth.test", {"foo"}),
BuildTestCnameRecord("third.test", "fourth.test"),
BuildTestCnameRecord("third.test", "fifth.test"),
BuildTestCnameRecord(kName, "second.test")});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
EXPECT_EQ(extractor
.ExtractDnsResults(DnsQueryType::TXT,
kName,
0)
.error_or(ExtractionError::kOk),
ExtractionError::kMultipleCnames);
}
TEST_F(DnsResponseResultExtractorTest, IgnoresTtlFromNonResultType) {
constexpr char kName[] = "name.test";
constexpr base::TimeDelta kMinTtl = base::Minutes(4);
DnsResponse response = BuildTestDnsResponse(
kName, dns_protocol::kTypeTXT,
{BuildTestTextRecord(kName, {"foo"}, base::Hours(3)),
BuildTestTextRecord(kName, {"bar"}, kMinTtl),
BuildTestAddressRecord(kName, IPAddress(1, 2, 3, 4), base::Seconds(2)),
BuildTestTextRecord(kName, {"baz"}, base::Minutes(15))});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::TXT,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
ElementsAre(Pointee(ExpectHostResolverInternalDataResult(
kName, DnsQueryType::TXT, kDnsSource,
Eq(tick_clock_.NowTicks() + kMinTtl), Eq(clock_.Now() + kMinTtl),
IsEmpty(),
UnorderedElementsAre("foo", "bar", "baz")))));
}
TEST_F(DnsResponseResultExtractorTest, ExtractsTtlFromCname) {
constexpr char kName[] = "name.test";
constexpr char kAlias[] = "alias.test";
constexpr base::TimeDelta kTtl = base::Minutes(4);
DnsResponse response =
BuildTestDnsResponse("name.test", dns_protocol::kTypeTXT,
{BuildTestCnameRecord(kName, kAlias, kTtl)});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::TXT,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(Pointee(ExpectHostResolverInternalAliasResult(
kName, DnsQueryType::TXT, kDnsSource,
Eq(tick_clock_.NowTicks() + kTtl), Eq(clock_.Now() + kTtl),
kAlias))));
}
TEST_F(DnsResponseResultExtractorTest, ValidatesAliasNames) {
constexpr char kName[] = "first.test";
const IPAddress kExpected(192, 168, 0, 1);
IPEndPoint expected_endpoint(kExpected, 0 );
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeA,
{BuildTestCnameRecord(kName, "second.test"),
BuildTestCnameRecord("second.test", "localhost"),
BuildTestCnameRecord("localhost", "fourth.test"),
BuildTestAddressRecord("fourth.test", kExpected)});
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
EXPECT_EQ(extractor
.ExtractDnsResults(DnsQueryType::A,
kName,
0)
.error_or(ExtractionError::kOk),
ExtractionError::kMalformedRecord);
}
TEST_F(DnsResponseResultExtractorTest, CanonicalizesAliasNames) {
const IPAddress kExpected(192, 168, 0, 1);
constexpr char kName[] = "address.test";
constexpr const uint8_t kCname[] = {0x05, 'A', 'L', 'I', 'A', 'S',
0x04, 't', 'e', 's', 't', 0x00};
std::vector<DnsResourceRecord> answers = {
BuildTestDnsRecord(kName, dns_protocol::kTypeCNAME, kCname),
BuildTestAddressRecord("alias.test", kExpected)};
DnsResponse response =
BuildTestDnsResponse(kName, dns_protocol::kTypeA, answers);
DnsResponseResultExtractor extractor(response, clock_, tick_clock_);
ResultsOrError results =
extractor.ExtractDnsResults(DnsQueryType::A,
kName,
0);
ASSERT_TRUE(results.has_value());
EXPECT_THAT(
results.value(),
UnorderedElementsAre(
Pointee(ExpectHostResolverInternalAliasResult(
kName, DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt), "alias.test")),
Pointee(ExpectHostResolverInternalDataResult(
"alias.test", DnsQueryType::A, kDnsSource,
Ne(std::nullopt),
Ne(std::nullopt),
ElementsAre(IPEndPoint(kExpected, 0))))));
}
}
}